2011-12-26 2 views
8

У меня есть класс, который нужно сделать с каждым оператором, как __add__, __sub__ и так далее.Перехват поиска оператора на метаклассе

Вместо создания каждой функции в классе у меня есть метакласс, который определяет каждый оператор в модуле оператора.

import operator 
class MetaFuncBuilder(type): 
    def __init__(self, *args, **kw): 
     super().__init__(*args, **kw) 
     attr = '__{0}{1}__' 
     for op in (x for x in dir(operator) if not x.startswith('__')): 
      oper = getattr(operator, op) 

      # ... I have my magic replacement functions here 
      # `func` for `__operators__` and `__ioperators__` 
      # and `rfunc` for `__roperators__` 

      setattr(self, attr.format('', op), func) 
      setattr(self, attr.format('r', op), rfunc) 

Подход работает нормально, но я думаю, было бы лучше, если бы я сгенерировал оператор замены только тогда, когда это необходимо.

Поиск операторов должно быть на метакласса, потому что x + 1 сделано как type(x).__add__(x,1) вместо x.__add__(x,1), но не попадитесь __getattr__ ни __getattribute__ методами.

Это не работает:

class Meta(type): 
    def __getattr__(self, name): 
      if name in ['__add__', '__sub__', '__mul__', ...]: 
       func = lambda:... #generate magic function 
       return func 

Кроме того, «функция», в результате должен быть способ, связанный с экземпляром используется.

Любые идеи о том, как я могу перехватить этот поиск? Я не знаю, ясно ли, что я хочу делать.


Для тех сомнению, почему мне нужно сделать такого рода вещи, проверить полный код here. Это инструмент для генерации функций (только для удовольствия), который может работать как замена для lambda s.

Пример:

>>> f = FuncBuilder() 
>>> g = f ** 2 
>>> g(10) 
100 
>>> g 
<var [('pow', 2)]> 

Просто для записи, я не хочу знать, еще один способ сделать то же самое (я не буду объявлять каждый оператор в классе ... что будет скучно, и подход у меня работает очень хорошо :). Я хочу знать, как перехватить поиск атрибута от оператора.

+0

«У меня есть класс, который должен сделать магию с каждым оператором» - Почему? Похоже, вы лаяете очень сложное дерево ... –

+0

@ LennartRegebro Я пишу генератор функций, используя операторы на каком-то объекте. 'f = FuncBuilder(); g = f ** 2 + 1; g (10) == 101'. Это не очень полезно (много вызовов функций), но несколько полезно использовать: D – JBernardo

+0

@ LennartRegebro Я разместил полный код. – JBernardo

ответ

5

Некоторые черная магия позволяет вам достичь своей цели:

operators = ["add", "mul"] 

class OperatorHackiness(object): 
    """ 
    Use this base class if you want your object 
    to intercept __add__, __iadd__, __radd__, __mul__ etc. 
    using __getattr__. 
    __getattr__ will called at most _once_ during the 
    lifetime of the object, as the result is cached! 
    """ 

    def __init__(self): 
    # create a instance-local base class which we can 
    # manipulate to our needs 
    self.__class__ = self.meta = type('tmp', (self.__class__,), {}) 


# add operator methods dynamically, because we are damn lazy. 
# This loop is however only called once in the whole program 
# (when the module is loaded) 
def create_operator(name): 
    def dynamic_operator(self, *args): 
    # call getattr to allow interception 
    # by user 
    func = self.__getattr__(name) 
    # save the result in the temporary 
    # base class to avoid calling getattr twice 
    setattr(self.meta, name, func) 
    # use provided function to calculate result 
    return func(self, *args) 
    return dynamic_operator 

for op in operators: 
    for name in ["__%s__" % op, "__r%s__" % op, "__i%s__" % op]: 
    setattr(OperatorHackiness, name, create_operator(name)) 


# Example user class 
class Test(OperatorHackiness): 
    def __init__(self, x): 
    super(Test, self).__init__() 
    self.x = x 

    def __getattr__(self, attr): 
    print "__getattr__(%s)" % attr 
    if attr == "__add__": 
     return lambda a, b: a.x + b.x 
    elif attr == "__iadd__": 
     def iadd(self, other): 
     self.x += other.x 
     return self 
     return iadd 
    elif attr == "__mul__": 
     return lambda a, b: a.x * b.x 
    else: 
     raise AttributeError 

## Some test code: 

a = Test(3) 
b = Test(4) 

# let's test addition 
print a + b # this first call to __add__ will trigger 
      # a __getattr__ call 
print a + b # this second call will not! 

# same for multiplication 
print a * b 
print a * b 

# inplace addition (getattr is also only called once) 
a += b 
a += b 
print a.x # yay! 

Выход

__getattr__(__add__) 
7 
7 
__getattr__(__mul__) 
12 
12 
__getattr__(__iadd__) 
11 

Теперь вы можете использовать свой второй образец кода буквально унаследовав от моего OperatorHackiness базового класса.Вы даже получите дополнительное преимущество: __getattr__ будет вызываться только один раз для каждого экземпляра и оператора, и нет дополнительного уровня рекурсии для кэширования. Мы, таким образом, обходим проблему медленности методов вызова методов по сравнению с поиском метода (как правильно заметил Пол Ханкин).

ПРИМЕЧАНИЕ: цикл для добавления методов оператора выполняется только один раз во всей программе, поэтому подготовка требует постоянных накладных расходов в диапазоне миллисекунд.

+0

Ну, похоже, что ваш цикл 'for' добавляет все операторы в класс (посмотрите на мой код, я тоже это сделаю). Я хочу ** не ** иметь их :). Кстати, я думаю, что это уже улучшилось. – JBernardo

+0

@ Дж. Бернардо: Посмотрите еще раз. Он полностью отличается от вашего кода. Добавлены не созданные функции оператора, а только мелкие обертки вокруг вызова '__getattr__'. Это необходимо, потому что, как вы сказали, вы не можете перехватывать эти вызовы методов с помощью пользовательской функции '__getattr__'. Поскольку цикл выполняется только _once во всей вашей программе_, а число операторов является конечным, оно требует постоянных накладных расходов в диапазоне миллисекунд. В принципе, это взломать, чтобы вы могли использовать '__getattr__' для перехвата операторов, как и любые другие методы (именно это вы запросили). –

+0

Я понимаю ваш код (также вы должны добавить эти комментарии к ответу), но вы делаете это: 'x + y -> x .__ add__ -> x .__ getattr __ ('__ add __')'. Это интересная идея, но похоже, что у операторов нет возможности как-то. – JBernardo

1

Похоже, вы делаете вещи слишком сложными. Вы можете определить класс mixin и наследовать его. Это проще, чем использование метаклассов и будет работать быстрее, чем при использовании __getattr__.

class OperatorMixin(object): 
    def __add__(self, other): 
     return func(self, other) 
    def __radd__(self, other): 
     return rfunc(self, other) 
    ... other operators defined too 

Тогда каждый класс, который вы хотите иметь с этими операторами, наследуется от OperatorMixin.

class Expression(OperatorMixin): 
    ... the regular methods for your class 

Создание методов оператора, когда они нужны не очень хорошая идея: __getattr__ медленно по сравнению с регулярным поиском метода, и так как эти методы сохраняются только один раз (по классу подмешать), это не спасает почти ничего ,

+0

Да, существует не менее 10 операторов (плюс внутри и наоборот), и я не хочу писать их вручную и вызывать одну и ту же функцию (смену оператора) для каждого из них. – JBernardo

+0

Теперь моя идея состоит в том, чтобы * создавать * func' или 'rfunc' при вызове оператора. – JBernardo

+0

Что создаст функции лениво даст вам? –

3

Проблема в том, что стороны Python смотрит __xxx__ методы класса объекта, а не на сам объект - и если он не найден, он не упал обратно __getattr__ ни __getattribute__.

Единственный способ перехвата таких вызовов - иметь уже существующий метод. Это может быть функция заглушки, как в ответе Никласа Баумстарка, или это может быть полноценная функция замены; в любом случае, однако должен быть чем-то уже там, или вы не сможете перехватить такие звонки.

Если вы внимательно читаете, вы заметите, что ваше требование о том, чтобы окончательный метод был привязан к экземпляру, не является возможным решением - вы можете это сделать, но Python никогда не назовет его, поскольку Python смотрит на класс экземпляра, а не экземпляр, для методов __xxx__. Решение Niklas Baumstark о создании уникального темпового класса для каждого экземпляра так же близко, как вы можете достичь этого требования.

 Смежные вопросы

  • Нет связанных вопросов^_^