python magic methods

英文原文:http://www.rafekettler.com/magicmethods.html#conclusion

1.__new__ 是静态方法(__new__ is a static method),实例化时以类作对象为第一个参数(The first argument to __new__ must be a class; the remainingarguments are the arguments as seen by the constructor call.)调用__new__返回实例对象,然后再调用__init__对实例对象做进一步初始化;对于一些不可变对象的类(int,str,tuple),其__init__是无效的,要想在初始化过程中改变其值,需要override __new__方法。改写__new__时还是需要调用父类的__new__方法的,只是我们可以在之前改变参数或者在之后改变返回的实例对象。改写的__new__内部调用父类__new__方法时不可把父类作为第一个参数传入,否则会得到父类的实例对象,应该传入新类(A __new__ method that overrides a base class's __new__ methodmay call that base class's __new__ method. The first argument to thebase class's __new__ method call should be the class argument to theoverriding __new__ method, not the base class; if you were to pass inthe base class, you would get an instance of the base class)。官网对__new__的描述非常详细http://www.python.org/download/releases/2.2/descrintro/#__new__

2.__del__相当于析构函数(对应的,__new__和__init__就是构造函数了),但并不是在del x的时候调用,而是在对象被垃圾回收的时候调用。其作用主要是处理一些额外的清理工作,比如关闭socket和文件描述符。__del__最好别用,或谨慎使用,因为不能保证__del__在什么时候执行。最好的习惯是自己手动做必要的善后工作,比如连接不用了就自己去把它关掉,不要交给__del__去做。

3.用于比较的魔法函数,__cmp__,__eq__,__ne__,__lt__,__gt__,__le__,__ge__(__cmp__根据</==/>返回负数/0/整数)

4.__str__由内建函数str()调用,__repr__由内建函数repr()调用(前者主要给人看,后者主要给机器看);__unicode__由内建函数unicode()调用,跟str()类似,只是只是返回的是unicode string。

5.__hash__由内建函数hash()调用,必须返回整数,用于在字典中做快速的key比较;改写该函数一般也要改写__eq__,a==b会包含hash(a)==hash(b)。

6.__dir__由内建函数dir()调用,返回一个包含所有属性的list。一般没必要改写,但是在改写__getattr__/__getattribute__或者动态生成属性的时候这个函数是很重要的。

7.属性访问的控制,从其他语言过来的人抱怨python没有真正意义上类的封装(没法定义私有属性;且可以随意的设置和获取属性)。事实并非如此,python通过魔法方法实现类的封装,而不是显示的修改类的方法和字段。

     __getattr__(self,name),当访问不存在的属性时会调用该方法(不是真正的做类封装的解决方案)
    __setattr__(self,name,value),不管属性是否已经存在,你可以通过该方法定义/设置一个属性的行为。也就是说你可以定制自己的规则使其在类的属性发生改变时发挥作用(确实是一种类的封装)
    __delattr__,跟__setattr__类似,只不过它是删除属性,而非设置属性。调用del self.name时在__delattr__的实现中可能会导致无穷递归,需谨慎
     __getattribute__(self, name),不推荐使用(用途很少且很难做到没BUG),只能用于新风格的类(最新版本python里面都是新风格的类)。当被显式调用或者__getattribute__返回AttributeError时,__getattr__会被调用
def __setattr__(self, name, value):
    self.name = value
    # since every time an attribute is assigned, __setattr__() is called, this
    # is recursion.
    # so this really means self.__setattr__('name', value). Since the method
    # keeps calling itself, the recursion goes on forever causing a crash

def __setattr__(self, name, value):
    self.__dict__[name] = value # assigning to the dict of names in the class
    # define custom behavior here
    
class AccessCounter(object):
    '''A class that contains a value and implements an access counter.
    The counter increments each time the value is changed.'''

    def __init__(self, val):
        super(AccessCounter, self).__setattr__('counter', 0)
        super(AccessCounter, self).__setattr__('value', val)

    def __setattr__(self, name, value):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        # Make this unconditional.
        # If you want to prevent other attributes to be set, raise AttributeError(name)
        super(AccessCounter, self).__setattr__(name, value)

    def __delattr__(self, name):
        if name == 'value':
            super(AccessCounter, self).__setattr__('counter', self.counter + 1)
        super(AccessCounter, self).__delattr__(name)]

8.定制序列:自定义不可变容器,只用定义__len__和__getitem__方法;自定义可变容器,需要定义__len__/__getitem__/__setitem__/__delitem__。如果需要具备可迭代的属性,还要定义__iter__(__iter__返回迭代器,该迭代器必须遵守迭代协议,即,需要迭代器有__iter__和next方法)

    __getitem__(self,key),使用self[key]的时候被调用,不可变容器和可变容器都支持该方法

    __setitem__(self,key,value),使用self[key]=value的时候被调用,可变容器才支持该方法

    __delitem__(self,key),使用del self[key]的时候被调用,可变容器才支持该方法

    __iter__(self),返回一个容器的迭代器,有很多方式会触发该方法调用,常见的是内建函数iter()和“for x in container:”语法。迭代器需要定义方法__inter__返回自身

    __reversed__(self),使用内建函数reversed()的时候被调用,返回一个逆序序列,实现该方法需要序列有序(如tuple和list)

    __contains__(self,item),使用“in”和“not in”语法的时候触发该方法调用,如果不定义该方法python会迭代这个序列并在遇到目标(item)时候返回True

    __missing__(self,key),为dict的子类使用,当请求的key不存在的时候会调用该方法

9.reflection,很少会用到这些,但是如果需要你会发现python给了你这个功能

    __instancecheck__(self,instance),使用isinstance(instance,class)的时候会触发该函数调用

    __subclasscheck__(self,subclass),使用issubclass(subclass,class)的时候会触发该函数调用

10.callable objects,python中函数也是一种对象

    __call__(self, [args...]),该方法允许一个类的实例对象可以像函数一样被调用。x()等价于x.__call__(),参数随意,但是把实例对象当函数调用的时候参数个数必须跟__call__定义时一致;如果在某个class中重复定义了多个__call__方法,最后一个会覆盖之前的,不论参数形式如何(python中没有C++里面那种函数名相同参数形式不同的重载)

11.Context Managers,上下文管理器,当对象的创建被with语法包装时,上下文管理器可以对对象做设置和清理的动作(contextlib模块有类似功能)

    __enter__(self),with语法控制的代码块执行前会调用被with包装的对象的__enter__方法,该方法的返回值被绑定到“with语法的目标”上或者“with语法中as后面的变量”上

    __exit__(self,exception_type,exception_value,traceback),with控制的代码块执行完毕或终止的时候调用对象的__eixt__方法,该方法用于处理异常、执行清理、或者其他在with代码块之后需要做的事情。如果代码块执行成功,exception_type、exception_value和traceback是None。否则,你可以选择处理exception或者丢出去让用户处理它:如果你要处理它应该保证处理完以后让__exit__返回True,如果你不想让exception被上下文管理器__exit__处理,就什么也别管让它去吧

class Closer:
    '''A context manager to automatically close an object with a close method
    in a with statement.'''

    def __init__(self, obj):
        self.obj = obj

    def __enter__(self):
        return self.obj # bound to target

    def __exit__(self, exception_type, exception_val, trace):
        try:
           self.obj.close()
        except AttributeError: # obj isn't closable
           print 'Not closable.'
           return True # exception handled successfully
           
>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
# output omitted for brevity
>>> conn.dir()
# long AttributeError message, can't use a connection that's closed
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6
12.Building Descriptor Objects

    __get__(self, instance, owner)
    __set__(self, instance, value)
    __delete__(self, instance)

http://onlypython.group.iteye.com/group/wiki/1362-python-39-s-descriptor <descriptor讲解>

descriptor到底是什么呢:简单的说,descriptor是对象的一个属性,只不过它存在于类的__dict__中并且有特殊方法__get__(可能还有__set__和__delete)而具有一点特别的功能,为了方便指代这样的属性,我们给它起了个名字叫descriptor属性

data descriptor和non-data descriptor:同时具有__get__和__set__方法,这样的descriptor叫做data descriptor,如果只有__get__方法,则叫做non-data descriptor

属性查找策略,对于obj.attr(注意:obj可以是一个类):

    a> 如果attr是一个Python自动产生的属性(属性可以分为两类,一类是Python自动产生的,如__class__,__hash__等,另一类是我们自定义的,如上面的hello,name。类和实例对象都有__dict__属性,里面存放它们的自定义属性(对于类,里面还存放了别的东西)),找到!(优先级非常高!)

    b> 查找obj.__class__.__dict__,如果attr存在并且是data descriptor,返回data descriptor的__get__方法的结果,如果没有继续在obj.__class__的父类以及祖先类中寻找data descriptor

    c> 在obj.__dict__中查找,这一步分两种情况,第一种情况是obj是一个普通实例,找到就直接返回,找不到进行下一步。第二种情况是obj是一个类,依次在obj和它的父类、祖先类的__dict__中查找,如果找到一个descriptor就返回descriptor的__get__方法的结果,否则直接返回attr。如果没有找到,进行下一步

    d> 在obj.__class__.__dict__中查找,如果找到了一个descriptor(插一句:这里的descriptor一定是non-data descriptor,如果它是data descriptor,第二步就找到它了)descriptor的__get__方法的结果。如果找到一个普通属性,直接返回属性值。如果没找到,进行下一步

    e> 很不幸,Python终于受不了。在这一步,它raise AttributeError

对属性赋值时的查找策略 ,对于obj.attr = value:

    a> 查找obj.__class__.__dict__,如果attr存在并且是一个data descriptor,调用attr的__set__方法,结束。如果不存在,会继续到obj.__class__的父类和祖先类中查找,找到 data descriptor则调用其__set__方法。没找到则进入下一步

    b> 直接在obj.__dict__中加入obj.__dict__['attr'] = value   

13.Copying

    __copy__(self),对某实例对象调用copy.copy()时会触发该实例的__copy__方法调用。浅拷贝,实例自身是新实例,但是它的所有数据都是对原实例的数据的引用,修改新实例的数据可能会引起原始实例的数据变化

    __deepcopy__(self, memodict={}),对某实例对象调用copy.deepcopy()时会触发该实例的__deepcopy__方法调用。深拷贝,实例自身和它的数据都是新的。memodict是之前已拷贝对象的缓存,当拷贝递归数据结构时它可以优化拷贝和阻止无尽递归。当你要深拷贝一个单独的属性时,对该属性调用copy.deepcopy()并把memodict作为第一个参数(如果要实际使用的话还得深入研究一下~)

14.Pickling Your Objects

    Pickling是指对python的数据结构做序列化处理,可以存储和重新加载一个对象(多用于缓存)

    Pickling不是内建类型。它对于任何遵守pickle协议的类都支持。pickle协议由四个可选方法来定制pickling行为(之前的一篇文章提到了其中两个方法的使用,http://blog.csdn.net/xiarendeniao/article/details/6774520 item52)

    __getinitargs__(self)
    __getnewargs__(self)
    __getstate__(self)
    __setstate__(self, state)
    __reduce__(self)
    __reduce_ex__(self)
import time
class Slate:
    '''Class to store a string and a changelog, and forget its value when
    pickled.'''

    def __init__(self, value):
        self.value = value
        self.last_change = time.asctime()
        self.history = {}

    def change(self, new_value):
        # Change the value. Commit last value to history
        self.history[self.last_change] = self.value
        self.value = new_value
        self.last_change = time.asctime()

    def print_changes(self):
        print 'Changelog for Slate object:'
        for k, v in self.history.items():
            print '%s\t %s' % (k, v)

    def __getstate__(self):
        # Deliberately do not return self.value or self.last_change.
        # We want to have a "blank slate" when we unpickle.
        return self.history

    def __setstate__(self, state):
        # Make self.history = state and last_change and value undefined
        self.history = state
        self.value, self.last_change = None, None

15.哪些语法会触发魔法方法?

   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值