啊上次说要写个 Python 的 magic method 的东西的, 刚好刚刚睡醒什么都不想做, 脑子里一片空白, 写这个好了…
-
首先最熟悉的, 应该是
__init__了. 每个开始写 Python 的人都会无数次的用到它. 它是做什么的呢? 初始化一个对象的实例. 比如:class WTF(object): def __init__(self): self.a = 'a' self.b = 'b' w = WTF() print w.a # a print w.b # b -
然后会想到,
__init__是不是就是构造函数了呢? 其实不是的… 真正负责新建一个实例的方法是__new__. 比如:class A(object): def __new__(self, *a, **kw): print 'new called' print a print kw return super(self, A).__new__(self, *a, **kw) def __init__(self, a, b): print 'init called' self.a = a self.b = b In [1]: a = A(3, b=4) new called init called (3, ) {'b': 4} In [2]: a.a Out[2]: 3 In [3]: a.b Out[3]: 4啊, 所以其实是实例化一个类的时候会调用
__new__, 并且把参数传给__init__再调用一次. 也就是c = A(*a, **kw)等价于c = A.__new__(*a, **kw)+c.__init__(*a, **kw)稍微改一下可以实现单例模式, 虽然其实 Python 里单例并不需要这么复杂… 比如这样:
class A(object): __instance = None def __new__(self, *a, **kw): if not self.__instance: self.__instance = super(self, A).__new__(self, *a, **kw) return self.__instance In [1]: a = A() In [2]: b = A() In [3]: a is b Out[3]: True不过还是少搞这种幺蛾子为好¬ ¬
-
既然 init 和 new 都有了, 那么也少不了
__del__, 不过这里有一点是,del xxx并不会调用xxx.__del__(), 后者的调用是在引用计数为0的时候, 另外这个里面最好不要再跟self这个变量挂钩了, 很可能这时候self已经被 gc 了. 其实我没玩过这个方法, 一般都比较信赖系统自己的 gc… -
__str__和__repr__. 分别被str和repr调用. 在print的格式化串中他们分别为%s和%r. 官方对__repr__的定义是, 它应该返回一个合法的 Python 表达式, 如果不是一个表达式, 那么需要返回一个格式为”<…有效信息…>”的字符串. 这个表达式可以通过eval求值再次得到这个实例. 也就是说o == eval(repr(o))在__repr__返回表达式的时候应该成立. 例如:class A(object): def __repr__(self): return 'repr' def __str__(self): return 'str' In [1]: a = A() In [2]: a Out[2]: repr In [3]: print a Out[3]: str In [4]: print '%s' % a Out[4]: str In [5]: print '%r' % a Out[5]: repr -
然后是一些比较大小的方法… 有:
object.__lt__(self, other) object.__le__(self, other) object.__eq__(self, other) object.__ne__(self, other) object.__gt__(self, other) object.__ge__(self, other) object.__cmp__(self, other) object.__hash__(self, other)上面那些字面意思已经很明白了, 他们分别被<, <=, ==, !=或者<>, >, >=调用. 返回值是True/False.
__cmp__稍微不同点, 如果self < other, 返回负数; 如果self == other, 0;self > other, 正数. 其实跟 C/C++ 里的 cmp 函数差不多.__hash__被hash调用. 当有需要求哈希值的时候, 他都会被调用, 比如往一个 set 或者 dict 里塞的时候. 返回值是一个整数. 所以我们现在有好几个方法判断俩实例是不是相等了. 可以实现eq方法, 可以实现cmp, 也可以实现hash. 不过其实一般情况下我们用系统帮我们生成的就好了… -
自定义的属性方法, 有:
object.__getattr__(self, name) object.__getattribute__(self, name) object.__delattr__(self, name)他们分别被
getattr,setattr,delattr调用. 也就是我们的表达式里的x.y,x.y = 1,del x.y.__getattr__只有在属性没有被正常方式找到的时候才被调用. 之后会提到属性的查找顺序…object.__setattr__(self, name, value)是一个更加强的属性查找方法, 如果定义了这个, 那么__getattr__就哭瞎了, 不仅它哭了, 其他的查找方式都哭瞎了… 因为丫是最高优先级拦截的. 所以一般情况下我们不需要定义这个方法, 如果定义了, 要注意这个方法内不能有任何self.xxx的表达式, 否则很快爆栈了… 要使用object.__getattribute__(self, name) -
Descriptor:
object.__get__(self, obj, obj_type) object.__set__(self, obj, value) object.__delete__(self, obj)定义这三个方法的就是一个 descriptor 啦. 要说这个… 可以扯到更多的东西上… 比如 unbound method 和 bound method, 以及方法的调用, 和那个神奇的
self参数, 还有@classmethod,@staticmethod,@property,@xxx.setter之类的装饰器… 啊又犯懒了, 先不写这些了. 大概就简单说下吧.如果一个类A定义了至少是
__get__的描述符方法, 那么丫就是一个描述符. 那么如果A的实例a被某实例的属性查找的时候命中了(不能是在实例的__dict__里), 那么这时候会去调用a.__get__. 代码说就是这样:class A(object): def __get__(self, obj, obj_type): print obj, obj_type return 'x' class B(object): a = A() class C(object): def __init__(self): self.a = A()上面的代码, A 就是一个描述符. 只有 B 里的描述符才会起作用. 当我们说
B().a的时候, 其实是a.__get__(B(), B)酱, 而C().a只是简单返回了a. 啊说到这里又要说那个属性查找顺序了. 那么实际上, 在一个o里查找属性n, 顺序是是酱的:1. __getattribute__ 2. o.__dict__ 3. type(o).__dict__ 也就是 O.__dict__ 4. O 的父类的 __dict__ 5. __getattr__从第三步开始, 如果找到的是一个至少实现了
__get__方法的描述符, 那么会调用描述符而不是简单返回属性. 嗯这也是为什么__getattribute__那么猛了… 还是少碰这种奇异的生物的好¬ ¬哦刚刚还说方法绑定什么的, descriptor 的用法和特性一搜一大把, 我就不瞎逼逼了… 其实就是我懒了… 写起来真的好累的好么!
-
slots.
__slots__这属性可以节省你大把的内存! 当一个实例挂什么属性上去的时候, 会在__dict__里写一个记录. 这尼玛也是要内存的啊! 如果你定义了__slots__, 那么你只能往实例上挂slots里的属性. 同时也不生成__dict__了. 你可以写个只有一个属性的类, 用slots和不用的区别是, 一个用sys.getsizeof出来是56, 一个是64. 如果你有大量这样的实例, 同时属性又不会随意乱挂, 那么slots是你最佳选择, 省好多内存的哪! 你看是不是巨小气会过日子! 其实就是穷, 买不起服务器的表现… (已哭瞎…) -
state.
__getstate__和__setstate__, 被 pickle 的时候会调用__getstate__, 而在 unpickle 的时候会调用__setstate__, 对于需要自定义哪些要序列化哪些不要序列化的场景有很好的效果. 有些地方可能比较穷, memcache 都没多少内存可以用, 那么就只把需要丢缓存的东西给用state来返回, 反序列化的时候恢复回来…
擦我真的没动力写下去了… 本来脑子就一片空白还想睡觉, 写了半天居然还有那么多的 magic method 没写… 已经弃坑, 留个链接想看的自己去看吧¬ ¬ 按顺序看下来真的是可以看到我失去了耐心… 谁 tm 要 Python 那么多 magic method 的我日… 嗯算了, 反正会的人就是会了, 不会的人怎么写怎么看也不会… 这么一想开心多了, 安心弃坑!
饿了都… 觅食去…