python基础——第9章 魔法方法、特性和迭代器

9.2.1 构造函数: init(self)

注意 Python提供了魔法方法__del__,也称作析构函数(destructor)。这个方法在对象被销毁(作为垃圾被收集)前被调用,但鉴于你无法知道准确的调用时间,建议尽可能不要使用__del__。
重写是继承机制的一个重要方面,对构造函数来说尤其重要。构造函数用于初始化新建对象的状态,而对大多数子类来说,除超类的初始化代码外,还需要有自己的初始化代码。虽然所有方法的重写机制都相同,但与重写普通方法相比,重写构造函数时更有可能遇到一个特别的问题:重写构造函数时,必须调用超类(继承的类)的构造函数,否则可能无法正确地初始化对象。
调用其超类(Bird)的构造函数,以确保基本的初始化得以执行。为此,有两种方法:调用未关联的超类构造函数,以及使用函数super。

9.2.2 调用未关联的超类构造函数

注意 在Python中,协议通常指的是规范行为的规则,有点类似于第7章提及的接口。协议指定应实现哪些方法以及这些方法应做什么。在Python中,多态仅仅基于对象的行为(而不基于祖先,如属于哪个类或其超类等),因此这个概念很重要:其他的语言可能要求对象属于特定的类或实现了特定的接口,而Python通常只要求对象遵循特定的协议。因此,要成为序列,只需遵循序列协议即可。

9.3 元素访问

9.3.1 基本的序列和映射协议

序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。

  • len(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为键-值对数。如果__len__返回零(且没有实现覆盖这种行为的__nonzero__),对象在布尔上下文中将被视为假(就像空的列表、元组、字符串和字典一样)。
  • getitem(self, key):这个方法应返回与指定键相关联的值。对序列来说,键应该是0~n-1的整数(也可以是负数,这将在后面说明),其中n为序列的长度。对映射来说,键可以是任何类型。
  • setitem(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够使用__getitem__来获取。当然,仅当对象可变时才需要实现这个方法。
  • delitem(self, key):这个方法在对对象的组成部分使用__del__语句时被调用,应删除与key相关联的值。同样,仅当对象可变(且允许其项被删除)时,才需要实现这个方法。
    对于这些方法,还有一些额外的要求。
  • 对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
  • 如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
  • 对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。
    要了解更复杂的接口和使用的抽象基类(Sequence),请参阅有关模块collections的文档。
    下面来试一试,看看能否创建一个无穷序列。
def check_index(key): 
   """ 
   指定的键是否是可接受的索引? 
   键必须是非负整数,才是可接受的。如果不是整数,将引发TypeError异常;
   如果是负数,将引发Index Error异常(因为这个序列的长度是无穷的) 
   """ 
	if not isinstance(key, int): raise TypeError 
 	if key < 0: raise IndexError 
class ArithmeticSequence: 
	def __init__(self, start=0, step=1): 
 		""" 
 		初始化这个算术序列 
 		start   -序列中的第一个值 
		step    -两个相邻值的差 
 		changed -一个字典,包含用户修改后的值 
 		""" 
		self.start = start       # 存储起始值 
   		self.step = step      # 存储步长值
   		self.changed = {}   # 没有任何元素被修改 
   	def __getitem__(self, key): 
 		""" 
 		从算术序列中获取一个元素 
 		""" 
 		check_index(key) 
 		try: return self.changed[key]     # 修改过? 
 		except KeyError:      # 如果没有修改过, 
  			return self.start + key * self.step    # 就计算元素的值 
   	def __setitem__(self, key, value): 
 		""" 
 		修改算术序列中的元素 
 		""" 
 		check_index(key) 
 		self.changed[key] = value     # 存储修改后的值 

这些代码实现的是一个算术序列,其中任何两个相邻数字的差都相同。第一个值是由构造函
数的参数start(默认为0)指定的,而相邻值之间的差是由参数step(默认为1)指定的。你允
许用户修改某些元素,这是通过将不符合规则的值保存在字典changed中实现的。如果元素未被
修改,就使用公式self.start + key * self.step来计算它的值。

9.3.2 从 list、dict 和 str 派生

在标准库中,模块collections提供了抽象和具体的基类,但你也可以继承内置类型。因此,如果要实现一种
行为类似于内置列表的序列类型,可直接继承list。

9.5 特性

9.5.1 函数property

9.7

  • 包含yield语句的函数都被称为生成器。

,生成器的行为与普通函数截然不同。差别在于,生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。
在Python 2.4中,引入了一个类似于列表推导(参见第5章)的概念:生成器推导(也叫生成器表达式)。其工作原理与列表推导相同,但不是创建一个列表(即不立即执行循环),而是返回一个生成器,让你能够逐步执行计算。

>>> g = ((i + 2) ** 2 for i in range(2, 27)) 
>>> next(g) 
16 

如你所见,不同于列表推导,这里使用的是圆括号。在像这样的简单情形下,还不如使用列表推导;但如果要包装可迭代对象(可能生成大量的值),使用列表推导将立即实例化一个列表,从而丧失迭代的优势。
另一个好处是,直接在一对既有的圆括号内(如在函数调用中)使用生成器推导时,无需再添加一对圆括号。换而言之,可编写下面这样非常漂亮的代码:
sum(i ** 2 for i in range(10))

猜你喜欢

转载自blog.csdn.net/acktomas/article/details/84778793
今日推荐