魔术方法,运算符和getattribute - 更多的故事?

时间:2014-12-04 00:00:07

标签: python python-3.x

两个相关问题。首先是上下文:

来自python documentation

  

这些是所谓的“丰富比较”方法。运算符符号和方法名称之间的对应关系如下:xlt(y),x< = y调用x。 le (y),x == y调用x。 eq (y),x!= y调用x。 ne (y),x> y调用x。 gt (y),x> = y调用x。的 GE (Y)。

请考虑以下事项:

class Foo:
    def __getattribute__(self, attr):
        print(attr)
        return super(Foo, self).__getattribute__(attr)

foo = Foo()

我希望打印出来的任何函数都应该打印出来,对吗?从文档中,我希望如果我执行

foo < 1

然后这应该等同于

foo.__lt__(1)

反过来应该调用foo.__getattribute__('__lt__'),对吗?

但事实并非如此。我看到的是对__class__属性的两个请求:

In [135]: foo < 1
__class__
__class__
Traceback (most recent call last):

  File "<ipython-input-135-ee30676e9187>", line 1, in <module>
    foo < 1

TypeError: unorderable types: Foo() < int()

如果我exectute foo.__lt__(1)我得到了我期望的行为:

In [137]: foo.__lt__(1)
__lt__
Out[137]: NotImplemented

1)有人可以解释一下吗?

我想编写一个捕获所有属性请求(包括函数调用)的类,并在执行之前略微修改请求。本质上,我想写一个类统一修改所有方法(继承或不存在),但我不想为每个方法重写一个重载。我的想法是拦截方法调用,做我的事,然后继续。

2)可以这样做吗?

1 个答案:

答案 0 :(得分:1)

ekhumoro提供的link有我的问题的答案。为了完整起见,我将在这里总结一下。来自文档:

  

对于自定义类,仅对特殊方法进行隐式调用   如果在对象的类型上定义,则保证正常工作,而不是在   对象的实例字典。

虽然这并没有完全解释我的特定场景(其中包括类声明中的特殊方法将启用隐式功能),但它确实引入了这样的想法:特殊方法的隐式调用绑定到对象元类(即类型) 。这种理解有助于理解下一个片段的含义:

  

隐式特殊方法查找通常也会绕过   __getattribute__()方法甚至是对象的元类。

因此,特殊方法的隐式调用不会在类或元类上使用__getattribute__方法。换句话说,你无法隐式拦截隐式特殊方法查找

重申这一结论,文件指出:

  

以这种方式绕过__getattribute__()机器   解释器内速度优化的重要范围,at   处理特殊方法的一些灵活性的成本(   必须在类对象本身上设置特殊方法才能成为   一直由解释者调用。)

因此,解决方案是必须在类对象上显式定义特殊方法才能隐式调用。

在上面的示例中,我在类对象上显式定义了特殊方法__lt__,因此通过<运算符的隐式调用有效。但是,如上所述,由于在解释器中进行了特殊优化,因此在进程中都没有调用__getattribute__方法(在类或元类上)。

最后,如果我想创建所需的行为,我需要在类对象上显式声明所有支持的特殊方法。没有that many of them,但没有简单的“全能”解决方案。

修改 对于那些等待这个故事结束的人,我通过在实例化之前以编程方式定义类对象的特殊方法找到了一个很好的折衷方案。以下代码片段显示了对pw函数的调用如何返回一个截取指定运算符的特殊对象,并返回一个容器,其中运算符对其成员执行:

def pw(container):
    '''Return a PairWise object, which intercepts operations and performs them 
    on the associated container.'''

    # Define the supported functions
    # Functions that must return a specific type (i.e. __str__) 
    #  cannot be implemented
    funs = ['__lt__','__le__','__eq__','__ne__','__gt__','__ge__']
    for f in ['add','sub','mul','truediv', 'floordiv', 
              'mod', 'divmod', 'pow', 'lshift', 'rshift',
              'and','or','xor']:
        funs.extend(['__{}__'.format(f), '__r{}__'.format(f)])
    for f in ['neg','pos','abs','invert']:
        funs.append('__{}__'.format(f))

    # Define the pairwise execution wrapper             
    def pairwise(fun, cont):
        def fun2(self, *args, **kwargs):
            t = type(cont)
            return t(type(c).__getattribute__(c, fun)(*args, **kwargs) \
                              for c in cont)
        return fun2

    # Define the PairWise class from the type constructor
    # i.e. type(name, bases, dict)
    PW = type('PW', (object,), 
              {fun: pairwise(fun, container) for fun in funs})

    # Return an instance of the pairwise class              
    return PW()

我现在可以通过语法上对pw的简单调用对包含(列表,集合,元组)执行成对运算:

In [266]: pw([1,2,3,4]) + 1
Out[266]: [2, 3, 4, 5]

In [267]: -pw({7,8,9,10})
Out[267]: {-10, -9, -8, -7}