بيثون: فئة على الطراز الجديد
سؤال
أريد أن "يعمل فقط":
def main():
c = Castable()
print c/3
print 2-c
print c%7
print c**2
print "%s" % c
print "%i" % c
print "%f" % c
بالطبع ، الطريق السهل هو الكتابة int(c)/3
, ، لكني أود تمكين بناء جملة بيرل-أيش أبسط من أجل اللغة المصغرة للتكوين.
من الجدير بالذكر أنه إذا استخدمت فئة "على الطراز القديم" (لا ترث من كائن) يمكنني القيام بذلك ببساطة عن طريق تحديد أ __coerce__
الطريقة ، ولكن يتم إهمال الفصول الدراسية على الطراز القديم وسيتم إزالتها في Python3.
عندما أفعل نفس الشيء مع فصل دراسي جديد ، أحصل على هذا الخطأ:
TypeError: unsupported operand type(s) for /: 'Castable' and 'int'
أعتقد أن هذا حسب التصميم ، ولكن كيف يمكنني محاكاة النمط القديم __coerce__
السلوك مع فصل على الطراز الجديد؟ يمكنك العثور على الحل الحالي أدناه ، لكنه قبيح للغاية وطويل.
هذه هي الوثائق ذات الصلة: (أعتقد)
نقاط المكافأة:
print pow(c, 2, 100)
المحلول 2
يعمل هذا ، وهو أقل إجماعًا بعد العديد من التحسينات (الدعائم إلى jchl) ، ولكن لا يزال يبدو أنه يجب أن يكون غير ضروري ، خاصة بالنظر إلى أنك تحصل على هذا مجانًا مع فصول "الطراز القديم".
ما زلت أبحث عن إجابة أفضل. إذا لم تكن هناك طريقة أفضل ، يبدو لي هذا مثل الانحدار في لغة بيثون.
def ops_list():
"calculate the list of overloadable operators"
#<type 'object'> has functions but no operations
not_ops = dir(object)
#calculate the list of operation names
ops = set()
for mytype in (int, float, str):
for op in dir(mytype):
if op.endswith("__") and op not in not_ops:
ops.add(op)
return sorted(ops)
class MetaCastable(type):
__ops = ops_list()
def __new__(mcs, name, bases, dict):
#pass any undefined ops to self.__op__
def add_op(op):
if op in dict:
return
fn = lambda self, *args: self.__op__(op, args)
fn.__name__ = op
dict[op] = fn
for op in mcs.__ops:
add_op( op )
return type.__new__(mcs, name, bases, dict)
class Castable(object):
__metaclass__ = MetaCastable
def __str__(self):
print "str!"
return "<Castable>"
def __int__(self):
print "int!"
return 42
def __float__(self):
print "float!"
return 2.718281828459045
def __op__(self, op, args):
try:
other = args[0]
except IndexError:
other = None
print "%s %s %s" % (self, op, other)
self, other = coerce(self, other)
return getattr(self, op)(*args)
def __coerce__(self, other):
print "coercing like %r!" % other
if other is None: other = 0.0
return (type(other)(self), other)
نصائح أخرى
تحتاج إلى تحديد __div__
إن أردت c/3
للعمل. لن يقوم Python بتحويل كائنك إلى رقم أولاً لك.
class MetaCastable(type):
__binary_ops = (
'add', 'sub', 'mul', 'floordiv', 'mod', 'divmod', 'pow', 'lshift',
'rshift', 'and', 'xor', 'or', 'div', 'truediv',
)
__unary_ops = ( 'neg', 'pos', 'abs', 'invert', )
def __new__(mcs, name, bases, dict):
def make_binary_op(op):
fn = lambda self, other: self.__op__(op, other)
fn.__name__ = op
return fn
for opname in mcs.__binary_ops:
for op in ( '__%s__', '__r%s__' ):
op %= opname
if op in dict:
continue
dict[op] = make_binary_op(op)
def make_unary_op(op):
fn = lambda self: self.__op__(op, None)
fn.__name__ = op
return fn
for opname in mcs.__unary_ops:
op = '__%s__' % opname
if op in dict:
continue
dict[op] = make_unary_op(op)
return type.__new__(mcs, name, bases, dict)
class Castable(object):
__metaclass__ = MetaCastable
def __str__(self):
print "str!"
return "<Castable>"
def __int__(self):
print "int!"
return 42
def __float__(self):
print "float!"
return 2.718281828459045
def __op__(self, op, other):
if other is None:
print "%s(%s)" % (op, self)
self, other = coerce(self, 0.0)
return getattr(self, op)()
else:
print "%s %s %s" % (self, op, other)
self, other = coerce(self, other)
return getattr(self, op)(other)
def __coerce__(self, other):
print "coercing like %r!" % other
return (type(other)(self), other)
class Castable(object):
def __div__(self, other):
return 42 / other
تعمل فصول الأناقة الجديدة بشكل أسرع وأكثر دقة من فصول النمط القديمة. وبالتالي ليس أكثر تكلفة __getattr__
, __getattribute__
, __coerce__
يدعو إلى أي أسباب رخيصة وبترتيب مشكوك فيه.
النمط القديم __coerce__
واجهت أيضًا المشكلة ، والتي تم استدعاؤها حتى عندما تكون قد أدت بالفعل إلى زيادة تحميل طريقة المشغل لغرض خاص. ويطالب الصب بالأنواع المشتركة المتساوية ، ويقتصر على بعض العمليات الثنائية. فكر في جميع الأساليب والخصائص الأخرى لـ int / float / string - وحول POW (). بسبب كل هذه القيود coerce
مفقود في PY3. تهدف أمثلة السؤال إلى المحاكاة الافتراضية الواسعة إلى حد ما.
من خلال فئات النمط الجديدة ، فإن حلقة ما هي مجرد حلقة لتوفير العديد من الأساليب "المماثلة" مع القليل من التعليمات البرمجية ، أو توجيه تلك المكالمات إلى معالج افتراضي ثم سريعًا ومحددًا بدقة وتصنيفه بطريقة صحيح وغرامة. هذا ليس "الانحدار في لغة بيثون".
ومع ذلك ، لن أستخدم فصلًا ميتا كما هو موضح في إجابات أخرى لمثل هذه الحلقة أو لتوفير نوع من السلوك الأساسي البسيط. من شأنه أن يكسر الجوز مع مطرقة ثقيلة.
هنا مثال مساعد للمحاكاة الافتراضية لـ "البديل":
def Virtual(*methods):
"""Build a (new style) base or mixin class, which routes method or
operator calls to one __virtualmeth__ and attribute lookups to
__virtualget__ and __virtualset__ optionally.
*methods (strings, classes): Providing method names to be routed
"""
class VirtualBase(object):
def __virtualmeth__(self, methname, *args, **kw):
raise NotImplementedError
def _mkmeth(methname, thing):
if not callable(thing):
prop = property(lambda self:self.__virtualget__(methname),
lambda self, v:self.__virtualset__(methname, v))
return prop
def _meth(self, *args, **kw):
return self.__virtualmeth__(methname, *args, **kw)
_meth.__name__ = methname
return _meth
for m in methods:
for name, thing in (isinstance(m, str) and
{m:lambda:None} or m.__dict__).items():
if name not in ('__new__', '__init__', '__setattr__', ##'__cmp__',
'__getattribute__', '__doc__', ): ##'__getattr__',
setattr(VirtualBase, name, _mkmeth(name, thing))
return VirtualBase
وهنا مثال استخدام حالة: Anaphor! (PY2 و PY3):
import operator
class Anaphor(Virtual(int, float, str)):
"""remember a sub-expression comfortably:
A = Anaphor() # at least per thread / TLS
if re.search(...) >> A:
print(A.groups(), +A)
if A(x % 7) != 0:
print(A, 1 + A, A < 3.0, A.real, '%.2f' % A, +A)
"""
value = 0
def __virtualmeth__(self, methname, *args, **kw):
try: r = getattr(self.value, methname)(*args, **kw)
except AttributeError:
return getattr(operator, methname)(self.value, *args, **kw)
if r is NotImplemented: # simple type -> coerce
try: tcommon = type(self.value + args[0]) # PY2 coerce
except: return NotImplemented
return getattr(tcommon(self.value), methname)(*args, **kw)
return r
def __call__(self, value):
self.value = value
return value
__lshift__ = __rrshift__ = __call__ # A << x; x >> A
def __pos__(self): # real = +A
return self.value
def __getattr__(self, name):
return getattr(self.value, name)
def __repr__(self):
return '<Anaphor:%r>' % self.value
بسلاسة يتولى أيضا Opertor 3-arg pow()
:-) :
>>> A = Anaphor()
>>> x = 1
>>> if x + 11 >> A:
... print repr(A), A, +A, 'y' * A, 3.0 < A, pow(A, 2, 100)
...
<Anaphor:12> 12 12 yyyyyyyyyyyy True 44