在元类上拦截运算符查找

发布于 2021-01-29 17:25:20

我有一堂课,需要让每个运算符(例如__add____sub__等等)发挥一些魔力。

我没有在类中创建每个函数,而是有一个元类,它定义了operator模块中的每个运算符。

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

同样,结果“函数”必须是绑定到所用实例的方法。

关于如何拦截此查询的任何想法?我不知道我想做什么。


对于那些质疑为什么我需要这种东西的人,请在此处查看完整的代码。那是一个生成函数的工具(
只是为了好玩
),可以代替lambdas。

例:

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

仅作记录,我不想知道另一种做同一件事的方法(我不会在类上声明每个运算符……这将很无聊,而我所采用的方法也相当不错:)。
我想知道如何拦截来自运算符的属性查找

关注者
0
被浏览
46
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    一些黑魔法可以帮助您实现目标:

    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__每个实例和每个运算符仅被调用一次,并且缓存不涉及额外的递归层。我们在此规避了方法调用比方法查找慢的问题(正如Paul
    Hankin正确注意到的那样)。

    注意 :添加运算符方法的循环在整个程序中仅执行一次,因此准备工作在毫秒范围内会产生恒定的开销。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看