Python基础教程 学习笔记 第九章 魔法方法,特性,迭代器



一,构造函数:__init__


析构函数:__del__
这个方法在对象被销毁(作为垃圾被收集)前被调用,但鉴于你无法知道准确的调用时间,建议尽可能不要使
用 __del__ 。


二,重写普通方法和特殊的构造函数

子类可以直接重写普通方法

重写构造函数时要注意调用 超类 的构造函数

①多用于旧版

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

超类.__init__(self)

通过类调用方法(如 Bird.init ),就没有实例与其相关联。在这种情况下,你可随便设置参数 self 。这样的方法称为未关联的。

②使用函数 super 只适用于新式类。
调用这个函数时,将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法。在Python3中调用super可以不添加任何参数。

super()..__init__()

即便有多个超类,也只需调用一遍super


拓展

函数 super 返回的到底是什么呢?通常,你无需关心这个问题,只管假定它返回你所需的超类即可。实际上,它返回的是一个 super 对象,这个对象将负责为你执行方法解析。当你访问它的属性时,它将在所有的超类(以及超类的超类,等等)中查找,直到找到指定的属性或引发 AttributeError 异常。


三,元素访问

3.1.基本的序列和映射协议。
不可变对象要实现2个方法,可变对象需要实现4个。
①__len__(self)

②__getitem__(self,key)
对序列来说键是整数,对字典来说键可以是任何类型。

③__setitem__(self,key,value)
(对象可变时)

④__delitem__(self,key).
(对象可变时)

⑤额外要求
对于序列,如果键为负整数,应从末尾往前数。换而言之, x[-n] 应与 x[len(x)-n] 等效。
如果键的类型不合适(如对序列使用字符串键),可能引发TypeError 异常。
对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发 IndexError 异常。

试着创建无穷序列:

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  # 存储修改后的值

使用:

>>> s = ArithmeticSequence(1, 2)
>>> s[4]
9
>>> s[4] = 2
>>> s[4]
2
>>> s[5]
11

禁止删除 因为没有实现__del__

长度无限,所以没有方法 __len__

如果所使用索引的类型非法,将引发 TypeError 异常;如果索引的类型正确,但不在允许的范围内(即为负数),将引发 IndexError 异常。

索引检查是由为此编写的辅助函数 check_index 负责的。


3.2.从list,dict 和 str 派生
重写部分行为时注意调用或返回超类的原方法

#带计数器的列表
class CounterList(list):
	def __init__(self, *args):
		super().__init__(*args)
		self.counter = 0
	def __getitem__(self, index):
		self.counter += 1
		return super(CounterList, self).__getitem__(index
注意

重写 __getitem__ 并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表内容的方式,如通过方法 pop 。


五,函数property

5.1 property特性

新类提倡使用property特性,而不是存取器

属性 = property(fget,fset,fdel,doc)  #这四个参数都是可选的
#第三个参数指定用于删除属性的方法,这个方法不接受任何参数。
#第四个参数指定一个文档字符串
class Rectangle:
	def __init__ (self):
		self.width = 0
		self.height = 0
	def set_size(self, size):
		self.width, self.height = size
	def get_size(self):
		return self.width, self.height
	size = property(get_size, set_size)

通过调用函数 property 并将存取方法作为参数(获取方法在前,设置方法在后)创建了一个特性,然后将名称 size 关联到这个特性。这样,你就能以同样的方式对待 width 、 height 和 size ,而无需关心它们是如何实现的。

>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.size
(10, 5)
>>> r.size = 150, 100
>>> r.width
150

5.2静态方法和类方法

静态方法 staticmethod
没有参数self,直接通过类来调用

类方法 classmethod
类似于self的参数,通常命名为cls,但是关联到类,可通过类或对象来调用。

class MyClass:
	def smeth():
		print('This is a static method')
	smeth = staticmethod(smeth)
	def cmeth(cls):
		print('This is a class method of', cls)
	cmeth = classmethod(cmeth)

可用装饰器:@

class MyClass:
	@staticmethod
	def smeth():
		print('This is a static method')
	@classmethod
	def cmeth(cls):
		print('This is a class method of', cls)

实际上,装饰器可用于包装任何可调用的对象,并且可用于方法和函
数。)可指定一个或多个装饰器,为此可在方法(或函数)前面使用运算符 @ 列出这些装饰器(指定了多个装饰器时,应用的顺序与列出的顺序相反。


5.3 __getattr__ 、 __setattr__ 等方法

拦截对对象属性的所有访问企图
用途之一是在旧式类中实现特性

 __getattribute__(self, name) :在属性被访问时自动调用(只适用于新式类)。
 __getattr__(self, name) :在属性被访问而对象没有这样的属性时自动调用。
 __setattr__(self, name, value) :试图给属性赋值时自动调用。
 __delattr__(self, name) :试图删除属性时自动调用

前面property的例子

class Rectangle:
	def __init__ (self):
		self.width = 0
		self.height = 0
	def __setattr__(self, name, value):
		if name == 'size':
			self.width, self.height = value
		else:
			self. __dict__[name] = value
	def __getattr__(self, name):
		if name == 'size':
			return self.width, self.height
		else:
			raise AttributeError()
注意

 即便涉及的属性不是 size ,也将调用方法 setattr 。因此这个方法必须考虑如下两种情形:如果涉及的属性为 size ,就执行与以前一样的操作;否则就使用魔法属性 dictdict 属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用 setattr ,进而导致无限循环
 仅当没有找到指定的属性时,才会调用方法 getattr 。这意味着如果指定的名称不是size ,这个方法将引发 AttributeError 异常。这在要让类能够正确地支持 hasattr 和 getattr等内置函数时很重要。如果指定的名称为 size ,就使用前一个实现中的表达式。

编写方法 __setattr__ 时需要避开无限循环陷阱,编写__getattribute__ 时亦如此。由于它拦截对所有属性的访问(在新式类中),因此将拦截对 __dict__ 的访问!在 __getattribute__ 中访问当前实例的属性时,唯一安全的方式是使用超类的方
法 __getattribute__ (使用 super )


六,迭代器iter

6.1 迭代器协议

方法__iter__返回一个迭代器,它是包含方法__next__的对象,可不提供任何参数.

调用方法__next__ 时,迭代器应返回其下一个值。

如果迭代器没有可供返回的值,应引发 StopIteration 异常。

你还可使用内置的便利函数 next ,在这种情况下, next(it) 与it.__next__() 等效。

正规的定义是,实现了方法 __iter__ 的对象是 可迭代的,而实现了方法 __next__ 的对象是 迭代器。

class Fibs:
	def __init__(self):
		self.a = 0
		self.b = 1
	def __next__(self):
		self.a, self.b = self.b, self.a + self.b
		return self.a
	def __iter__(self):
		return self

这个迭代器实现了方法 __iter__ ,而这个方法返回迭代器本身。在很多情况下,都在另一个对象中实现返回迭代器的方法 __iter__ ,并在 for 循环中使用这个对象。但推荐在迭代器中也实现方法 __iter__ (并像刚才那样让它返回 self ),这样迭代器就可直接用于 for 循环中。

>>> fibs = Fibs()
>>> for f in fibs:
...  if f > 1000:
...  print(f)
...  break
...
1597

通过对可迭代对象调用内置函数 iter ,可获得一个迭代器。

>>> it = iter([1, 2, 3])
>>> next(it)
1
>>> next(it)
2

还可使用它从函数或其他可调用对象创建可迭代对象

可从迭代器创建序列。

七,生成器

7.1简单生成器

包含 yield 语句的函数都被称为生成器。生成器的行为与普通函数截然不同。差别在于,生成器不是使用 return 返回一个值,而是可以生成多个值,每次一个。每次使用 yield 生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。

为使用所有的值,可对生成器进行迭代

>>> nested = [[1, 2], [3, 4], [5]]
>>> for num in flatten(nested):
... print(num)
...
1
2
3
4
5>>> list(flatten(nested))
[1, 2, 3, 4, 5]

生成器推导:

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

不同于列表推导,这里使用的是圆括号

直接在一对既有的圆括号内(如在函数调用中)使用生成器推导时,无需再添加一对圆括号

sum(i ** 2 for i in range(10))

7.2递归式生成器
def flatten(nested):
	try:
		# 不迭代类似于字符串的对象:
		try: nested + ''
		except TypeError: pass
		else: raise TypeError
		for sublist in nested:
			for element in flatten(sublist):
				yield element
	except TypeError:
		yield nested

调用 flatten 时,有两种可能性(处理递归时都如此):基线条件和递归条件。
在基线条件下,要求这个函数展开单个元素(如一个数)。在这种情况下, for 循环将引发 TypeError 异常(因为你试图迭代一个数),而这个生成器只生成一个元素。
然而,如果要展开的是一个列表(或其他任何可迭代对象),你就需要做些工作:遍历所有的子列表(其中有些可能并不是列表)并对它们调用 flatten ,然后使用另一个 for 循环生成展开后的子列表中的所有元素。这可能看起来有点不可思议,但确实可行。

>>> list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8]))
[1, 2, 3, 4, 5, 6, 7, 8]

注意

在函数 flatten 中,不应该对类似于字符串的对象进行迭代,主要原因有两个。首先,你想将类似于字符串的对象视为原子值,而不是应该展开的序列。其次,对这样的对象进行迭代会导致无穷递归,因为字符串的第一个元素是一个长度为1的字符串,而长度为1的字符串的第一个元素是字符串本身!


7.3通用生成器

生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。生成器的函数是由 def 语句定义的,其中包含 yield 。生成器的迭代器是这个函数返回的结果。用不太准确的话说,这两个实体通常被视为一个,通称为生成器。

对于生成器的函数返回的迭代器,可以像使用其他迭代器一样使用它。

7.4生成器的方法

①send
在生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值。这个通信渠道包含如下两个端点。
 外部世界:外部世界可访问生成器的方法 send ,这个方法类似于 next ,但接受一个参数(要发送的“消息”,可以是任何对象)。
 生成器:在挂起的生成器内部, yield 可能用作表达式而不是语句。换而言之,当生成器重新运行时, yield 返回一个值——通过 send 从外部世界发送的值。如果使用的是 next (自然递归),yield 将返回 None 。
请注意,仅当生成器被挂起(即遇到第一个 yield )后,使用 send (而不是 next )才有意义。要在此之前向生成器提供信息,可使用生成器的函数的参数。
注意 如果一定要在生成器刚启动时对其调用方法 send ,可向它传递参数 None 。

def repeater(value):
	while True:
		new = (yield value)
		if new is not None: value = new

使用:

>>> r = repeater(42)
>>> next(r)
42
>>> r.send("Hello, world!")
"Hello, world!"

②throw
用于在生成器中( yield 表达式处)引发异常,调用时可提供一个异常类型、一个可选值和一个 traceback 对象。
③close
用于停止生成器,调用时无需提供任何参数。

拓展:

方法 close (由Python垃圾收集器在需要时调用)也是基于异常的:在 yield 处引发GeneratorExit 异常。因此如果要在生成器中提供一些清理代码,可将 yield 放在一条 try / finally语句中。如果愿意,也可捕获 GeneratorExit 异常,但随后必须重新引发它(可能在清理后)、引发其他异常或直接返回。对生成器调用 close 后,再试图从它那里获取值将导致 RuntimeError 异常。

7.5模拟生成器

针对Python旧版本

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值