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)
¿Fue útil?

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top