Python: принудительный класс в новом стиле - PullRequest
8 голосов
/ 12 августа 2010

Я хочу, чтобы этот код "просто работал":

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

Конечно, самый простой выход - написать int(c)/3, но я бы хотел включить более простой синтаксис perl-ish для мини-языка конфигурации.

Примечательно, что если я использую класс «старого стиля» (не наследую от объекта), я могу сделать это довольно просто, определив метод __coerce__, но классы старого стиля устарели и будут удалены в python3.

Когда я делаю то же самое с классом нового стиля, я получаю эту ошибку:

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

Я полагаю, что это сделано специально, но тогда как я могу имитировать поведение __coerce__ старого стиля с классом нового стиля? Вы можете найти мое текущее решение ниже, но оно довольно уродливо и многословно.

Это соответствующая документация: (я думаю)

Бонусные баллы:

    print pow(c, 2, 100)

Ответы [ 5 ]

8 голосов
/ 12 августа 2010

Вам нужно определить __div__, если вы хотите, чтобы c/3 работал. Python не будет сначала преобразовывать ваш объект в число.

5 голосов
/ 12 августа 2010

Это работает, и менее брутто после нескольких улучшений (добавлено @jchl), но все равно кажется, что это не нужно, особенно если учесть, что вы получаете это бесплатно с классами "старого стиля".

Я все еще ищу лучший ответ.Если нет лучшего способа, мне кажется, что это регрессия в языке 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)
3 голосов
/ 20 августа 2010
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)
0 голосов
/ 04 мая 2017

Новые классы стиля работают быстрее и точнее, чем классы старого стиля. Таким образом, не более дорогие __getattr__, __getattribute__, __coerce__ звонки по любым дешевым причинам и в сомнительном порядке.

В старом стиле __coerce__ также была проблема, что он вызывался, даже если вы уже перегружали операторный метод для какой-то специальной цели. И он требует приведения к одинаковым общим типам и ограничен определенными двоичными операциями. Подумайте обо всех других методах и свойствах int / float / string - и о pow (). Из-за всех этих ограничений coerce отсутствует в PY3. Примеры вопросов направлены на довольно широкую виртуализацию.

В новых классах стилей речь идет о цикле, который предоставляет множество «похожих» методов с небольшим количеством кода или направляет эти вызовы в виртуальный обработчик, а затем его быстро и точно определяют и разбивают на подклассы корректным и детальным образом. Это не «регрессия в языке Python».

Однако я бы не использовал метакласс, как показано в других ответах, только для такого цикла или для обеспечения простого поведения базового класса. Это было бы треск ореха кувалдой.


Вот пример помощника для виртуализации «варианта»:

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

А вот пример использования: Анафор! (PY2 и 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

Плавно обрабатывает и 3-аргументный оператор 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
0 голосов
/ 12 августа 2010
class Castable(object):
    def __div__(self, other):
        return 42 / other
...