Python: Coerce New-Style Class
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)
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