Pergunta

Eu quero que este código "apenas trabalhe":

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

Claro, a saída mais fácil é escrever int(c)/3, mas eu gostaria de ativar uma sintaxe Perl-ish mais simples para uma mini-linguagem de configuração.

É notável que, se eu usar uma classe de "estilo antigo" (não herdar do objeto), posso fazer isso simplesmente simplesmente definindo um __coerce__ Método, mas as classes de estilo antigo são descontinuadas e serão removidas no Python3.

Quando faço a mesma coisa com uma aula de estilo novo, recebo este erro:

TypeError: unsupported operand type(s) for /: 'Castable' and 'int'

Eu acredito que isso é por design, mas como posso simular o estilo antigo __coerce__ comportamento com uma aula de novo estilo? Você pode encontrar minha solução atual abaixo, mas é bastante feia e longa.

Esta é a documentação relevante: (eu acho)

Pontos bônus:

    print pow(c, 2, 100)
Foi útil?

Solução 2

Isso funciona e é menos grosseiro após várias melhorias (adereços para @JCHL), mas ainda parece que deve ser desnecessário, especialmente considerando que você recebe isso de graça com classes "antigas".

Ainda estou procurando uma resposta melhor. Se não existe um método melhor, isso me parece uma regressão na língua 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)

Outras dicas

Você precisa definir __div__ Se você quiser c/3 trabalhar. O Python não converterá seu objeto em um número primeiro para você.

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

Novas classes de estilo operam mais rápido e mais preciso do que as aulas de estilo antigo. Portanto, não mais caro __getattr__, __getattribute__ , __coerce__ exige motivos baratos e em uma ordem questionável.

O estilo antigo __coerce__ Também teve o problema, que foi chamado, mesmo quando você já sobrecarregou um método do operador para algum objetivo especial. E exige fundição para iguais tipos comuns e é limitado a certas operações binárias. Pense em todos os outros métodos e propriedades de um Int / Float / String - e sobre Pow (). Devido a todas essas limitações coerce está faltando no PY3. Os exemplos de perguntas visam uma ampla virtualização.

Com as novas classes de estilo, é apenas um loop para fornecer muitos métodos "semelhantes" com pouco código, ou rotear essas chamadas para um manipulador virtual e, em seguida, sua maneira rápida e precisamente definida e subclassável de maneira correta e fina. Isso não é uma "regressão na língua python".

No entanto, eu não empregaria uma meta -classe, como mostrado em outras respostas, apenas para esse loop ou para fornecer um tipo de comportamento simples de classe base. Isso estaria quebrando uma noz com uma marreta.


Aqui um exemplo ajudante para virtualização de uma "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

E aqui um exemplo de uso de uso: uma anáfora! (PY2 e 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

Perfeitamente, ele também lida com o Opertor de 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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top