Python的神奇方法指南2
2014-04-08 18:41
309 查看
5.属性访问控制
有许多从其他语言阵营转到Python来的人抱怨Python对类缺乏真正的封装(比如,没有办法自定义private属性,已经给出public的getter和setter)。这可不是真相哟:Python通过神奇的方法实现了大量的封装,而不是通过明确的方法或字段修饰符。请看:
__getattr__(self, name)
你可以为用户在试图访问不存在(不论是存在或尚未建立)的类属性时定义其行为。这对捕捉和重定向常见的拼写错误,给出使用属性警告是有用的(只要你愿意,你仍旧可选计算,返回那个属性)或抛出一个AttributeError异常。这个方法只适用于访问一个不存在的属性,所以,这不算一个真正封装的解决之道。
__setattr__(self, name, value)
不像__getattr__,__setattr__是一个封装的解决方案。它允许你为一个属性赋值时候的行为,不论这个属性是否存在。这意味着你可以给属性值的任意变化自定义规则。然而,你需要在意的是你要小心使用__setattr__,在稍后的列表中会作为例子给出。
__delattr__
这等价于__setattr__,但是作为删除类属性而不是set它们。它需要相同的预防措施,就像__setattr__,防止无限递归(当在__delattr__中调用del self.name会引起无限递归)。
__getattribute__(self, name)
__getattribute__良好地适合它的同伴们__setattr__和__delattr__。可我却不建议你使用它。__getattribute__只能在新式类中使用(在Python的最新版本中,所有的类都是新式类,在稍旧的版本中你可以通过继承object类来创建一个新式类。它允许你定规则,在任何时候不管一个类属性的值那时候是否可访问的。)它会因为他的同伴中的出错连坐受到某些无限递归问题的困扰(这时你可以通过调用基类的__getattribute__方法来防止发生)。当__getattribute__被实现而又只调用了该方法如果__getattribute__被显式调用或抛出一个AttributeError异常,同时也主要避免了对__getattr__的依赖。这个方法可以使用(毕竟,这是你自己的选择),不过我不推荐它是因为它有一个小小的用例(虽说比较少见,但我们需要特殊行为以获取一个值而不是赋值)以及它真的很难做到实现0bug。
你可以很容易地在你自定义任何类属性访问方法时引发一个问题。参考这个例子:
再一次,Python的神奇方法向我们展示了其难以置信的能力,同时巨大的力量也伴随着重大的责任。重要的是让你明白正确使用神奇方法,这样你就不会破坏其他代码。
那么,我们在关于定制类属性访问中学习了什么?不要轻易地使用,事实上它过于强大以及反直觉。这也是它为何存在的理由:Python寻求干坏事的可能性,但会把它们弄得很难。自由是至高无上的,所以你可以做任何你想做的事情。以下是一个关于特殊属性访问方法的实际例子(注意,我们使用super因为并非所有类都有__dict__类属性):
6.制作自定义序列
很有多种方式可以让你的类表现得像内建序列(字典,元组,列表,字符串等)。这些是我迄今为止最喜欢的神奇方法了,因为不合理的控制它们赋予了你一种魔术般地让你的类实例整个全局函数数组漂亮工作的方式。在我们开始讲解这个内容之前,让我们先快速理清需求。
需求
现在我们正在谈论如何创建你自己的序列。也是什么谈一谈protocol了。protocol在某些地方跟接口很相似。接口在其他语言中,是一组给定的方法,而你必须定义它们。然而,在Python中protocol是完全非正式的,而且不要求显式声明去实现。更进一步说,它们更像是准则。
为何我们现在要谈论protocol?因为在Python中要实现自定义容器类型会涉及使用到这其中某些protocol。首先,有一个protocol是为定义不可变容器的:为了制作一个不可变容器,你只需要定义__len__和__getitem__(稍后详述)。可变容器protocol要求所有不可变容器增加__setitem__和__delitem__。然后,如果你希望你的对象是可迭代的,那你还得定义一个会返回迭代器iterator的__iter__方法。并且这个迭代器必须遵守一个迭代protocol,也就是要求迭代器有回调方法__iter__(返回自身)和next。
隐藏在容器背后的魔法
已经迫不及待了?以下便是容器使用的神奇魔法:
__len__(self)
返回容器的长度。部分protocol同时支持可变和不可变容器
__getitem__(self, key)
定义当某一个item被访问时的行为,使用self[key]表示法。这个同样也是部分可变和不可变容器protocol。这也可抛出适当的异常:TypeError
当key的类型错误,或没有值对应Key时。
__setitem__(self, key, value)
定义当某一个item被赋值时候的行为,使用self[key]=value表示法。这也是部分可变和不可变容器protocol。再一次重申,你应当在适当之处抛出KeyError和TypeError异常。
__delitem__(self, key)
定义当某一个item被删除(例如 del self[key])时的行为。这仅是部分可变容器的protocol。在一个无效key被使用后,你必须抛出一个合适的异常。
__iter__(self)
应该给容器返回一个迭代器。迭代器会返回若干内容,大多使用内建函数iter()表示。当一个容器使用形如for x in container:的循环。迭代器本身就是其对象,同时也要定义好一个__iter__方法来返回自身。
__reversed__(self)
当定义调用内建函数reversed()时的行为。应该返回一个反向版本的列表。
__contains__(self, item)
__contains__为成员关系,用in和not in测试时定义行为。那你会问这个为何不是一个序列的protocol的一部分?这是因为当__contains__未定义,Python就会遍历序列,如果遇到正在寻找的item就会返回True。
__concat__(self, other)
最后,你可通过__concat__定义你的序列和另外一个序列的连接。应该从self和other返回一个新构建的序列。当调用2个序列时__concat__涉及操作符+
一个例子
在我们的例子中,让我们看一下一个list实现的某些基础功能性的构建。可能会让你想起你使用的其他语言(比如Haskell)。
通过这个(轻量的)有用的例子你知道了如何实现你自己的序列。当然,还有很多更有用的应用,但是它们其中的很多已经被标准库实现了,像Counter, OrderedDict, NamedTuple
有许多从其他语言阵营转到Python来的人抱怨Python对类缺乏真正的封装(比如,没有办法自定义private属性,已经给出public的getter和setter)。这可不是真相哟:Python通过神奇的方法实现了大量的封装,而不是通过明确的方法或字段修饰符。请看:
__getattr__(self, name)
你可以为用户在试图访问不存在(不论是存在或尚未建立)的类属性时定义其行为。这对捕捉和重定向常见的拼写错误,给出使用属性警告是有用的(只要你愿意,你仍旧可选计算,返回那个属性)或抛出一个AttributeError异常。这个方法只适用于访问一个不存在的属性,所以,这不算一个真正封装的解决之道。
__setattr__(self, name, value)
不像__getattr__,__setattr__是一个封装的解决方案。它允许你为一个属性赋值时候的行为,不论这个属性是否存在。这意味着你可以给属性值的任意变化自定义规则。然而,你需要在意的是你要小心使用__setattr__,在稍后的列表中会作为例子给出。
__delattr__
这等价于__setattr__,但是作为删除类属性而不是set它们。它需要相同的预防措施,就像__setattr__,防止无限递归(当在__delattr__中调用del self.name会引起无限递归)。
__getattribute__(self, name)
__getattribute__良好地适合它的同伴们__setattr__和__delattr__。可我却不建议你使用它。__getattribute__只能在新式类中使用(在Python的最新版本中,所有的类都是新式类,在稍旧的版本中你可以通过继承object类来创建一个新式类。它允许你定规则,在任何时候不管一个类属性的值那时候是否可访问的。)它会因为他的同伴中的出错连坐受到某些无限递归问题的困扰(这时你可以通过调用基类的__getattribute__方法来防止发生)。当__getattribute__被实现而又只调用了该方法如果__getattribute__被显式调用或抛出一个AttributeError异常,同时也主要避免了对__getattr__的依赖。这个方法可以使用(毕竟,这是你自己的选择),不过我不推荐它是因为它有一个小小的用例(虽说比较少见,但我们需要特殊行为以获取一个值而不是赋值)以及它真的很难做到实现0bug。
你可以很容易地在你自定义任何类属性访问方法时引发一个问题。参考这个例子:
1 2 3 4 5 6 7 8 9 | def __setattr__(self, name, value): self.name = value # 当每次给一个类属性赋值时,会调用__setattr__(),这就形成了递归 # 因为它真正的含义是 self.__setattr__('name', value) # 所以这方法不停地调用它自己,变成了一个无法退出的递归最终引发crash def __setattr__(self, name, value): self.__dict__[name] = value # 给字典中的name赋值 # 在此自定义行为 |
那么,我们在关于定制类属性访问中学习了什么?不要轻易地使用,事实上它过于强大以及反直觉。这也是它为何存在的理由:Python寻求干坏事的可能性,但会把它们弄得很难。自由是至高无上的,所以你可以做任何你想做的事情。以下是一个关于特殊属性访问方法的实际例子(注意,我们使用super因为并非所有类都有__dict__类属性):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class AccessCounter: '''一个类包含一个值和实现了一个访问计数器。 当值每次发生变化时,计数器+1''' 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. # 如果你想阻止其他属性被创建,抛出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) |
很有多种方式可以让你的类表现得像内建序列(字典,元组,列表,字符串等)。这些是我迄今为止最喜欢的神奇方法了,因为不合理的控制它们赋予了你一种魔术般地让你的类实例整个全局函数数组漂亮工作的方式。在我们开始讲解这个内容之前,让我们先快速理清需求。
需求
现在我们正在谈论如何创建你自己的序列。也是什么谈一谈protocol了。protocol在某些地方跟接口很相似。接口在其他语言中,是一组给定的方法,而你必须定义它们。然而,在Python中protocol是完全非正式的,而且不要求显式声明去实现。更进一步说,它们更像是准则。
为何我们现在要谈论protocol?因为在Python中要实现自定义容器类型会涉及使用到这其中某些protocol。首先,有一个protocol是为定义不可变容器的:为了制作一个不可变容器,你只需要定义__len__和__getitem__(稍后详述)。可变容器protocol要求所有不可变容器增加__setitem__和__delitem__。然后,如果你希望你的对象是可迭代的,那你还得定义一个会返回迭代器iterator的__iter__方法。并且这个迭代器必须遵守一个迭代protocol,也就是要求迭代器有回调方法__iter__(返回自身)和next。
隐藏在容器背后的魔法
已经迫不及待了?以下便是容器使用的神奇魔法:
__len__(self)
返回容器的长度。部分protocol同时支持可变和不可变容器
__getitem__(self, key)
定义当某一个item被访问时的行为,使用self[key]表示法。这个同样也是部分可变和不可变容器protocol。这也可抛出适当的异常:TypeError
当key的类型错误,或没有值对应Key时。
__setitem__(self, key, value)
定义当某一个item被赋值时候的行为,使用self[key]=value表示法。这也是部分可变和不可变容器protocol。再一次重申,你应当在适当之处抛出KeyError和TypeError异常。
__delitem__(self, key)
定义当某一个item被删除(例如 del self[key])时的行为。这仅是部分可变容器的protocol。在一个无效key被使用后,你必须抛出一个合适的异常。
__iter__(self)
应该给容器返回一个迭代器。迭代器会返回若干内容,大多使用内建函数iter()表示。当一个容器使用形如for x in container:的循环。迭代器本身就是其对象,同时也要定义好一个__iter__方法来返回自身。
__reversed__(self)
当定义调用内建函数reversed()时的行为。应该返回一个反向版本的列表。
__contains__(self, item)
__contains__为成员关系,用in和not in测试时定义行为。那你会问这个为何不是一个序列的protocol的一部分?这是因为当__contains__未定义,Python就会遍历序列,如果遇到正在寻找的item就会返回True。
__concat__(self, other)
最后,你可通过__concat__定义你的序列和另外一个序列的连接。应该从self和other返回一个新构建的序列。当调用2个序列时__concat__涉及操作符+
一个例子
在我们的例子中,让我们看一下一个list实现的某些基础功能性的构建。可能会让你想起你使用的其他语言(比如Haskell)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | class FunctionalList: '''类覆盖了一个list的某些额外的功能性魔法,像head, tail,init,last,drop,and take''' def __init__(self, values=None): if values is None: self.values = [] else: self.values = values def __len__(self): return len(self.values) def __getitem__(self, key): # 如果key是非法的类型和值,那么list valuse会抛出异常 return self.values[key] def __setitem__(self, key, value): self.values[key] = value def __delitem__(self, key): del self.values[key] def __iter__(self): return iter(self.values) def __reversed__(self): return reversed(self.values) def append(self, value): self.values.append(value) def head(self): # 获得第一个元素 return self.values[0] def tail(self): # 获得在第一个元素后的其他所有元素 return self.values[1:] def init(self): # 获得除最后一个元素的序列 return self.values[:-1] def last(last): # 获得最后一个元素 return self.values[-1] def drop(self, n): # 获得除前n个元素的序列 return self.values[n:] def take(self, n): # 获得前n个元素 return self.values[:n] |
相关文章推荐
- Python 的神奇方法指南
- python的神奇方法指南
- Python的神奇方法指南1
- Python 的神奇方法指南
- Python的神奇方法指南
- Python的神奇方法指南
- Python的神奇方法指南3
- Python的神奇方法指南
- Python 的神奇方法指南
- python中的reduce内建函数使用方法指南
- Python 魔术方法指南
- Python 的对象天生拥有的一些神奇方法
- 手把手教你学python第十三讲(MRO详解和神奇的魔法方法)
- Python中的reduce内建函数使用方法指南
- Python 魔术方法指南
- Python 魔术方法指南
- Python 魔术方法指南
- Python 魔术方法指南
- Python漫谈-比较运算符和类的神奇方法
- 一波神奇的Python语句、函数与方法的使用技巧总结