Python: Coerce класс нового стиля
Вопрос
Я хочу этот код «просто работать»:
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
, но я хотел бы включить более простой синтаксис Perl-ISH для настройки мини-языка.
Примечательно, что если я использую класс «старого стиля» (не наследую от объекта), я могу сделать это просто, определяя __coerce__
Метод, но классы старого стиля устаревают и будут удалены в Python3.
Когда я делаю то же самое с классом нового стиля, я получаю эту ошибку:
TypeError: unsupported operand type(s) for /: 'Castable' and 'int'
Я верю, что это по дизайну, но тогда как я могу имитировать старый стиль __coerce__
поведение с классом нового стиля? Вы можете найти мое текущее решение ниже, но это довольно безобразно и давно завезено.
Это соответствующая документация: (я думаю)
Бонусные очки:
print pow(c, 2, 100)
Решение 2
Это работает, и стоит менее валовой после нескольких улучшений (реквизит к @JCHL), но все же кажется, что он должен быть нешественным, особенно учитывая, что вы получите это бесплатно с классами «Старый стиль».
Я все еще ищу лучший ответ. Если нет лучшего метода, это кажется мне как регрессия на языке Python.
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. Примеры вопроса направлены на достаточно широкую виртуализацию.
С новым стилем классов его практически о цикле, чтобы обеспечить множество «подобных» методов с небольшим количеством кода, или направить эти вызовы на виртуальный обработчик, а затем его быстро и точно определяют и подклассуют в правильном и мелкозернистом. Это не «регрессия на языке Python».
Однако я бы не настроил мета класса, как показано в других ответах только для такого цикла или для предоставления простого базового класса поведения. Это было бы взломать гайку с кувалдой.
Вот пример помощника для виртуализации «варианта»:
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
А вот пример используют корпус: анафора! (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
Бесшовно это также обрабатывает 3-аргумент 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