Python中的method_missing和*get*钩子方法族

刚才实验了一下,发现Python里面声明类的时候是否选择继承objects还是有很大区别的。只有继承了objects,才可以使用钩子方法如’__get__’, ‘__set__’, ‘__getattr__’, ‘__getattribute__’这些方法。也就是说这些有用的钩子方法是所谓的new object里面的东西。今天我在项目的代码里面尝试了一下类似Ruby method_missing的写法,实验在Python里面加点元编程的东西。发现很相似的三个方法’__get__’, ‘__getattr__’, ‘__getattribute__’方法区别挺大。注意,一定要继承object才可以享用三个钩子方法。

  • 访问对象方法的时候首先会访问’__getattribute__’,它是访问类里面所有属性的时候都要经过的方法,包括创建对象的时候访问’__init__’, ‘_meta’这些都回经过’__getattribute__’访问。如果你什么异常都不抛出,它就不会访问’__getattr__’方法。
  • 如果’__getattribute__’方法抛出’AttributeError’,那么会继续尝试访问’__getattr__’方法。再有异常抛出,那么这个类就没有钩子再接住异常了。所以从这个角度来说它的工作方式非常相似于Ruby的method_missing。
  • 我本以为’__getattr__’是系统内建函数hasattr(object, property)优先访问的方法。不过实验证明,实际上还是先走’__getattribute__’后走’__getattr__’的。也就是说hasattr这样的函数没有优先绑定’__getattr__’。
  • ‘__get__’方法是用来监视自己的类作为其它类的成员的时候被访问的钩子。对应的是’__set__’,是相应属性被赋值时的钩子。这个方法与’__getattr__’和’__getattribute__’完全不是一会儿事。刚才看《Python核心编程一书》完全没有解释清楚。《How-To Guide for Descriptors》这篇文章对解释’__get__’帮助很大,有兴趣可以看看,不过我倒是没有想到什么是合理的应用场合。

我目前还没有调用方法的method_missing,目前只是访问一些属性。我们实际处理的是一个可以直接用属性名读取/修改对象里面的持久化json属性的方法,就是类持有一个{‘property’: ‘value’}的json文本属性,我们就可以直接用Model.property访问和修改里面的方法,而不用特别的去生命json结构过来。是一个在Python中做meta programming的尝试。

测试刚才说的几个*get*方法的测试如下:

# -*- coding: utf8 -*-
import unittest

class A(object):
    def __init__(self):
        print 'init A'

    def __get__(self, *args):
        print '__get__ A', args

    def __set__(self, *args):
        print '__set__ A', args

class B(object):
    a = A()
    
    def __init__(self):
        print 'init B'

    def __getattr__(self, *args):
        print '__getattr__ B', args

    def __getattribute__(self, *args):
        print '__getattribute__ B', args
        raise AttributeError

    def __get__(self, *args):
        print '__get__ B', args

    def __set__(self, *args):
        print '__set__ B', args

class MeTest(unittest.TestCase):
    def test_simple(self):
        b = B()
        print b.a
        b.a = A()
        b.c
        hasattr(b, 'e')