Question

Je veux ce code "fonctionne":

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

Bien sûr, l'out moyen facile est de int(c)/3 d'écriture, mais je voudrais permettre une syntaxe plus simple de Perl-ish pour un mini-langage configuration.

Il est à noter que si j'utilise une classe « ancienne » (ne pas hériter de l'objet) Je peux le faire tout simplement en définissant une méthode __coerce__, mais les classes de style ancien sont dépréciées et seront supprimés dans python3.

Quand je fais la même chose avec une nouvelle classe de style, je reçois cette erreur:

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

Je crois que c'est par la conception, mais comment puis-je simuler l'ancien style de comportement __coerce__ avec une nouvelle classe de style? Vous pouvez trouver ma solution actuelle ci-dessous, mais il est assez laid et de longue haleine.

Ceci est la documentation pertinente: (je pense)

Points Bonus:

    print pow(c, 2, 100)
Était-ce utile?

La solution 2

Cela fonctionne, et est moins brut après plusieurs améliorations (accessoires pour @jchl), mais semble encore comme il devrait être unecessary, d'autant plus que vous obtenez ce gratuitement avec des classes « style ancien ».

Je suis toujours à la recherche d'une meilleure réponse. S'il n'y a pas de méthode meilleure, cela me semble comme une régression dans le langage 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)

Autres conseils

Vous devez définir __div__ si vous voulez c/3 au travail. Python ne convertit pas votre objet à un premier numéro pour vous.

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

Les nouvelles classes de style fonctionnent plus rapide et plus précis que les classes de style ancien. Donc pas __getattr__ plus cher, __getattribute__, appels __coerce__ pour des raisons économiques et dans un ordre discutable.

L'ancien style __coerce__ avait aussi le problème, qu'il a été appelé, même si vous avez déjà surchargé une méthode d'opérateur pour un but spécial. Elle exige coulée à l'égalité des types communs, et se limite à certaines opérations binaires. Pensez à toutes les autres méthodes et propriétés d'un int / flotteur / string - et environ pow (). En raison de toutes ces limitations coerce manque dans PY3. Les exemples de question visent à la virtualisation assez large.

Avec de nouvelles classes de style est juste sur une boucle pour fournir de nombreuses méthodes avec peu de code « similaires », ou l'itinéraire les appels vers un gestionnaire virtuel, puis son rapide et définie avec précision et dérivables de manière correcte et à grains fins. Cest pas une « régression dans le langage Python ».

Cependant, je ne serais pas employer une classe méta comme indiqué dans d'autres réponses juste pour une telle boucle ou pour fournir une simple classe de base genre de comportement. Ce serait cassant une noix avec un marteau de forgeron.


Voici un exemple d'aide pour la virtualisation d'une « 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

Et voici un exemple de cas d'utilisation: une anaphore! (AP2 et 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

il gère également de manière transparente le 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top