Python基础教程——魔法方法、特性和迭代器

文章目录

  • 魔法方法
      • 构造函数
  • 迭代器
  • 生成器

魔法方法

在Python中,有些名称很特别,开头和结尾都是两个下划线,如__future__。这样的拼写表示名称有特殊意义,因此绝不要在程序中创建这样的名称。在这样的名称中,很大一部分都是魔法(特殊)方法的名称。

构造函数

构造函数不同于普通方法的地方在于,将在对象创建后自动调用它们。在Python中,创建构造函数很容易,只需将方法init的名称从普通的init改为魔法版__init__即可。

class FooBar:
	def __init__(self):
		self.somevar = 42

f = FooBar()

给构造函数添加参数:

class FooBar:
	def __init__(self, value=42):
		self.somevar = value
f = FooBar('This is a constructor argument')

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

迭代器

迭代器协议

迭代(iterate)意味着重复多次,就像循环那样。本书前面只使用for循环迭代过序列和字典,但实际上也可迭代其他对象:实现了方法__iter__的对象。方法__iter__返回一个迭代器,它是包含方法__next__的对象,而调用这个方法时可不提供任何参数。当你调用方法__next__时,迭代器应返回其下一个值。如果迭代器没有可供返回的值,应引发StopIteration异常。你还可使用内置的便利函数next,在这种情况下,next(it)与it.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循环中。更正规的定义是,实现了方法__iter__的对象是可迭代的,而实现了方法__next__的对象是迭代器。

使用上面这个迭代器的方法:

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

通过对可迭代对象调用内置函数iter,可获得一个迭代器。还可使用它从函数或其他可调用对象创建可迭代对象,详情请参阅库参考手册。

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

从迭代器创建序列

除了对迭代器和可迭代对象进行迭代(通常这样做)之外,还可将它们转换为序列。在可以使用序列的情况下,大多也可使用迭代器或可迭代对象(诸如索引和切片等操作除外)。一个这样的例子是使用构造函数list显式地将迭代器转换为列表。

class TestIterator:
	value = 0
	def __next__(self):
		self.value += 1
		if self.value > 10: raise StopIteration
		return self.value
	def __iter__(self):
		return self

>>>ti = TestIterator()
>>>list(ti)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

生成器

创建生成器

创建一个将嵌套列表展开的函数。这个函数将一个类似于下面的列表作为参数:

nested = [[1, 2], [3, 4], [5]]

函数的代码:

def flatten(nested):
	for sublist in nested:
		for element in sublist:
			yield element

它首先迭代所提供嵌套列表中的所有子列表,然后按顺序迭代每个子列表的元素。倘若最后一行为print(element),这个函数将容易理解得多。在这里,你没有见过的是yield语句。包含yield语句的函数都被称为生成器。这可不仅仅是名称上的差别,生成器的行为与普通函数截然不同。差别在于,生成器不是使用return返回一个值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。

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

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

#或者
list(flatten(nested))

递归式生成器

如果要处理任意层嵌套的列表,对于每层嵌套,都需要一个for循环,但由于不知道有多少层嵌套,你必须修改解决方案,使其更灵活。该求助于递归了。

def flatten(nested):
	try:
		for sublist in nested:
			for element in flatten(sublist):
				yield element
	except TypeError: #如果展开一个数,会抛出TypeError,此时返回这个数本身
		yield nested

如果是字符串,不希望将其展开,所以可以检查一下是不是字符串(我没有检查nested是否是字符串,而只是检查其行为是否类似于字符串,即能否与字符串拼接。):

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

如果表达式nested + ''引发了TypeError异常,就忽略这种异常;如果没有引发TypeError异常,内部try语句中的else子句将引发TypeError异常,这样将在外部的excpet子句中原封不动地生成类似于字符串的对象。

通用生成器

生成器是包含关键字yield的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。每次请求值时,都将执行生成器的代码,直到遇到yield或return。yield意味着应生成一个值,而return意味着生成器应停止执行(即不再生成值;仅当在生成器调用return时,才能不提供任何参数)。

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

>>> def simple_generator():
		yield 1

>>> simple_generator #这是一个函数
<function simple_generator at 153b44>
>>> simple_generator() #这是一个生成器
<generator object at 1510b0> 

生成器的方法

在生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值。这个通信渠道包含如下两个端点。

  • 外部世界:外部世界可访问生成器的方法send,这个方法类似于next,但接受一个参数(要发送的“消息”,可以是任何对象)。
  • 生成器:在挂起的生成器内部,yield可能用作表达式而不是语句。换而言之,当生成器重新运行时,yield返回一个值——通过send从外部世界发送的值。如果使用的是next,yield将返回None。请注意,仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义。要在此之前向生成器提供信息,可使用生成器的函数的参数。
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:用于停止生成器,调用时无需提供任何参数。

有关生成器的方法以及它们是如何将生成器变成简单协同程序(coroutine)的详细信息,请参阅“PEP 342”(www.python.org/dev/peps/pep-0342/)。

你可能感兴趣的:(编程语言)