Python: Clase de coaccionar nuevo estilo
Pregunta
Quiero el código de "trabajar solo":
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
Por supuesto, el camino más fácil es int(c)/3
escritura, pero me gustaría para permitir una sintaxis de Perl-ish más simple para una configuración de mini-idioma.
Es notable que si uso una clase de "viejo estilo" (no heredan del objeto) que puedo hacer esto simplemente mediante la definición de un método __coerce__
, pero las clases de estilo antiguo están en desuso y se eliminará en python3.
Cuando hago lo mismo con una clase de nuevo estilo, me sale este error:
TypeError: unsupported operand type(s) for /: 'Castable' and 'int'
Creo que esto es por diseño, pero entonces ¿cómo puedo simular el comportamiento __coerce__
de estilo antiguo con una clase de nuevo estilo? Puede encontrar mi solución actual a continuación, pero es muy feo y de largo aliento.
Esta es la documentación pertinente: (creo)
Los puntos de bonificación:
print pow(c, 2, 100)
Solución 2
Estos trabajos, y es menos bruto después de varias mejoras (apoyos a @jchl), pero aún así parece que debería ser innecesario, sobre todo teniendo en cuenta que usted consigue esto de forma gratuita con clases de "viejo estilo".
Todavía estoy en busca de una respuesta mejor. Si no hay un método mejor, esto me parece como una regresión en el lenguaje 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)
Otros consejos
Es necesario definir si desea __div__
c/3
de trabajo. Python no convertirá el objeto en una serie por primera vez para usted.
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
Las nuevas clases de estilos funcionan más rápido y más preciso que las clases de estilo antiguo. Por lo tanto hay __getattr__
más caro, __getattribute__
, llamadas __coerce__
por ninguna razón baratos y en un orden cuestionable.
El viejo estilo __coerce__
también tenía el problema, que se llama incluso cuando ya se ha sobrecargado el operador un método para algún propósito especial. Y exige la fundición de tipos comunes de igualdad, y está limitado a ciertas operaciones binarias. Piense en todos los otros métodos y propiedades de un int / flotador / cadena - y sobre pow (). Debido a todas estas limitaciones coerce
no se encuentra en el AP3. Los ejemplos de preguntas tienen como objetivo bastante amplio de virtualización.
Con las nuevas clases de estilo es sólo alrededor de un bucle para proporcionar muchos métodos "similares" con poco código o ruta esas llamadas a un controlador virtual y entonces su rápido y definido con precisión y subclases de manera correcta y de grano fino. Eso no es una "regresión en el lenguaje Python".
Sin embargo, no emplearía a una clase meta como se muestra en otras respuestas sólo por un bucle de tales o para proporcionar un tipo simple clase base de la conducta. Eso sería una tuerca de craqueo con un mazo.
Aquí un ayudante ejemplo para la virtualización de una "variante":
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
Y aquí un caso de ejemplo el uso: una anáfora! (AP2 y AP3):
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
Sin problemas sino que también se encarga de la 3-arg Opertor 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