08. Python-魔法方法、特性和迭代器

目录

1. 构造函数

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

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

1.3 使用函数super

2. 元素访问

2.1 基本的序列和映射协议

 3. 特性

3.1 函数property

3.2 静态方法和类方法

3.3 __getattr__ 、__setattr__ 等方法

4. 迭代器

4.1 迭代器协议

4.2 从迭代器创建序列

5. 生成器

5.1 创建生成器

5.2 递归生成器

5.3 通用生成器

5.4 生成器的方法


在Python中,有些名称很特别,开头和结尾都是两个下划线。这样的拼写表示名称有特殊意义,因此绝不要在程序中创建这样的名称。在这样的名称中,很大一部分都是魔法 (特殊)方法的名称。如果你的对象实现了这些方法,它们将在特定情况下(具体是哪种情况取决于方法的名称)被Python调用,而几乎不需要直接调用。

1. 构造函数

构造函数 (constructor),它其实就是本书前面一些示例中使用的初始化方法,只是命名为__init__ 。然而,构造函数不同于普通方法的地方在于,将在对象创建后自动调用它们。

在Python中,创建构造函数很容易,只需将方法init 的名称从普通的init 改为魔法版__init__ 即可。

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

>> f = FooBar()
>>> f.somevar
42

# 给构造函数添加几个参数
class FooBar:
    def __init__(self, value=42):
        self.somevar = value

# 指定这个参数(或者说如果这个参数不是可选的)
>>> f = FooBar('This is a constructor argument')
>>> f.somevar
'This is a constructor argument'

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

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

A 定义了一个名为hello 的方法,并被类B 继承。

class A:
    def hello(self):
        print("Hello, I'm A.")
class B(A):
    pass
# 由于类B自己没有定义方法hello ,因此对其调用方法hello时,打印的是消息"Hello, I'm A." 。
>>> a = A()
>>> b = B()
>>> a.hello()
Hello, I'm A.
>>> b.hello()
Hello, I'm A.

要在子类中添加功能,一种基本方式是添加方法。然而,你可能想重写超类的某些方法,以定制继承而来的行为。

# B重写hello方法
class B(A):
    def hello(self):
        print("Hello, I'm B.")
>>> b = B()
>>> b.hello()
Hello, I'm B.

重写是继承机制的一个重要方面,对构造函数来说尤其重要。构造函数用于初始化新建对象的状态,而对大多数子类来说,除超类的初始化代码外,还需要有自己的初始化代码。虽然所有方法的重写机制都相同,但与重写普通方法相比,重写构造函数时更有可能遇到一个特别的问题:重写构造函数时,必须调用超类(继承的类)的构造函数,否则可能无法正确地初始化对象。

class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaah ...')
            self.hungry = False
        else:
            print('No, thanks!')

>>> b = Bird()
>>> b.eat()
Aaaah ...
>>> b.eat()
No, thanks!

SongBird 是Bird 的子类,继承了方法eat  

class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)

>>> sb = SongBird()
>>> sb.sing()
Squawk!
>>> sb.eat()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "birds.py", line 6, in eat
     if self.hungry:
AttributeError: SongBird instance has no attribute 'hungry'

SongBird 没有属性hungry 。因为在SongBird 中重写了构造函数,但新的构造函数没有包含任何初始化属性hungry 的代码。要消除这种错误,SongBird 的构造函数必须调用其超类(Bird )的构造函数,以确保基本的初始化得以执行。为此,有两种方法:调用未关联的超类构造函数,以及使用函数super 。

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

class SongBird(Bird):
    def __init__(self):
        Bird.__init__(self)
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)
>>> sb = SongBird()
>>> sb.sing()
Squawk!
>>> sb.eat()
Aaaah ...
>>> sb.eat()
No, thanks!

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

通过将这个未关联方法的self 参数设置为当前实例,将使用超类的构造函数来初始化SongBird 对象。这意味着将设置其属性hungry 。

1.3 使用函数super

这个函数只适用于新式类,而你无论如何都应使用新式类。调用这个函数时,将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法。因此,在SongBird 的构造函数中,可不使用Bird ,而是使用super(SongBird, self) 。另外,可像通常那样(也就是像调用关联的方法那样)调用方法__init__ 。在Python 3中调用函数super 时,可不提供任何参数(通常也应该这样做),而它将像变魔术一样完成任务。

class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaah ...')
            self.hungry = False
        else:
            print('No, thanks!')
            
class SongBird(Bird):
    def __init__(self):
        super().__init__()
        self.sound = 'Squawk'
    def sing(self):
        print(self.sound)

相比于直接对超类调用未关联方法,使用函数super 更直观,但这并非其唯一的优点。实际上,函数super 很聪明,因此即便有多个超类,也只需调用函数super 一次(条件是所有超类的构造函数也使用函数super )。另外,对于使用旧式类时处理起来很棘手的问题(如两个超类从同一个类派生而来),在使用新式类和函数super 时将自动得到处理。你无需知道函数super 的内部工作原理,但必须知道的是,使用函数super 比调用超类的未关联构造函数(或其他方法)要好得多。

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

2. 元素访问

基本的序列和映射协议非常简单,但要实现序列和映射的所有功能,需要实现很多魔法方法。

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

2.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 异常。

创建一个无穷序列

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 来计算它的值。

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

要禁止删除元素,因此没有实现__del__。这个类没有方法__len__ ,因为其长度是无穷的。

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

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

# 一个带访问计数器的列表
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)

CounterList 类深深地依赖于其超类(list )的行为。CounterList 没有重写的方法(如append 、extend 、index 等)都可直接使用。在两个被重写的方法中,使用super 来调用超类的相应方法,并添加了必要的行为:初始化属性counter (在__init__ 中)和更新属性counter (在__getitem__ 中)。

>>> cl = CounterList(range(10))
>>> cl
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> cl.reverse()
>>> cl
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> del cl[3:6]
>>> cl
[9, 8, 7, 3, 2, 1, 0]
>>> cl.counter
0
>>> cl[4] + cl[2]
9
>>> cl.counter
2

CounterList 的行为在大多数方面都类似于列表,但它有一个counter 属性(其初始值为0)。每当你访问列表元素时,这个属性的值都加1。执行加法运算cl[4] + cl[2] 后,counter 的值递增两次,变成了2。

 3. 特性

Python能够替你隐藏存取方法,让所有的属性看起来都一样。通过存取方法定义的属性通常称为特性 (property)。

3.1 函数property

通过调用函数property 并将存取方法作为参数(获取方法在前,设置方法在后)创建了一个特性,然后将名称size 关联到这个特性。

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)
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.size
(10, 5)
>>> r.size = 150, 100
>>> r.width
150

属性size 依然受制于get_size 和set_size 执行的计算,但看起来就像普通属性一样。

如果特性的行为怪异,务必确保你使用的是新式类(通过直接或间接地继承object 或直接设置__metaclass__ )。不然,特性的获取方法 依然正常,但设置方法 可能不正常(是否如此取决于使用的Python版本)。这可能有点令人迷惑。

实际上,调用函数property 时,还可不指定参数、指定一个参数、指定三个参数或指定四个参数。如果没有指定任何参数,创建的特性将既不可读也不可写。如果只指定一个参数(获取方法),创建的特性将是只读的。第三个参数是可选的,指定用于删除属性的方法(这个方法不接受任何参数)。第四个参数也是可选的,指定一个文档字符串。这些参数分别名为fget 、fset 、fdel 和doc 。如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参数来实现。

函数property 的工作原理

property 其实并不是函数,而是一个类。它的实例包含一些魔法方法,而所有的魔法都是由这些方法完成的。这些魔法方法为__get__ 、__set__ 和__delete__ ,它们一道定义了所谓的描述符协议。只要对象实现了这些方法中的任何一个,它就是一个描述符。描述符的独特之处在于其访问方式。例如,读取属性(具体来说,是在实例中访问类中定义的属性)时,如果它关联的是一个实现了__get__ 的对象,将不会返回这个对象,而是调用方法__get__ 并将其结果返回。实际上,这是隐藏在特性、关联的方法、静态方法和类方法以及super 后面的机制。

3.2 静态方法和类方法

静态方法和类方法是这样创建的:将它们分别包装在staticmethod 和classmethod 类的对象中。静态方法的定义中没有参数self ,可直接通过类来调用。类方法的定义中包含类似于self 的参数,通常被命名为cls 。对于类方法,也可通过对象直接调用,但参数cls 将自动关联到类。

class MyClass:
    
    def smeth():
        print('This is a static method')
    smeth = staticmethod(smeth)
    
    def cmath(cls):
        print('This is a class method od', 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)
>>> MyClass.smeth()
This is a static method
>>> MyClass.cmeth()
This is a class method of <class '__main__.MyClass'>

3.3 __getattr__ 、__setattr__ 等方法

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

相比函数property ,这些魔法方法使用起来要棘手些(从某种程度上说,效率也更低),但它们很有用,因为你可在这些方法中编写处理多个特性的代码。然而,在可能的情况下,还是使用函数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
    def __getattr__(self, name):
        if name == 'size':
            return self.width, self.height
        else:
            raise AttributeError()
  • 即便涉及的属性不是size ,也将调用方法__setattr__ 。因此这个方法必须考虑如下两种情形:如果涉及的属性为size ,就执行与以前一样的操作;否则就使用魔法属性__dict__ 。__dict__ 属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性赋值,是因为旨在避免再次调用__setattr__ ,进而导致无限循环。
  • 仅当没有找到指定的属性时,才会调用方法__getattr__ 。这意味着如果指定的名称不是size ,这个方法将引发AttributeError 异常。这在要让类能够正确地支持hasattr 和getattr 等内置函数时很重要。如果指定的名称为size ,就使用前一个实现中的表达式。

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

4. 迭代器

4.1 迭代器协议

迭代 (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 = Fibs()
# 找出第一个大于1000的斐波那契数
>>> for f in fibs:
...     if f > 1000:
...         print(f)
...         break
...
1597

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

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

4.2 从迭代器创建序列

除了对迭代器和可迭代对象进行迭代(通常这样做)之外,还可将它们转换为序列。在可以使用序列的情况下,大多也可使用迭代器或可迭代对象(诸如索引和切片等操作除外)。

使用构造函数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]

5. 生成器

5.1 创建生成器

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

生成器的行为与普通函数截然不同。差别在于,生成器不是使用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))

5.2 递归生成器

def flatten(nested):
    try:
        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]

这个解决方案存在一个问题。如果nested 是字符串或类似于字符串的对象,它就属于序列,因此不会引发TypeError 异常,可你并不想对其进行迭代。

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

要处理这种问题,必须在生成器开头进行检查。要检查对象是否类似于字符串,最简单、最快捷的方式是,尝试将对象与一个字符串拼接起来,并检查这是否会引发TypeError 异常。

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
>>> list(flatten(['foo', ['bar', ['baz']]]))
['foo', 'bar', 'baz']

这里没有执行类型检查:没有检查nested 是否字符串,而只是检查其行为是否类似于字符串,即能否与字符串拼接。对于这种检查,一种更自然的替代方案是,使用isinstance 以及字符串和类似于字符串的对象的一些抽象超类,但遗憾的是没有这样的标准类。另外,即便是对UserString 来说,也无法检查其类型是否为str 。

5.3 通用生成器

生成器是包含关键字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>

5.4 生成器的方法

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

  • 外部世界 :外部世界可访问生成器的方法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!"

注意到使用圆括号将yield 表达式括起来了。在有些情况下,并非必须这样做,但小心驶得万年船。如果要以某种方式使用返回值,就不管三七二十一,将其用圆括号括起吧。

生成器还包含另外两个方法。 

 方法throw :用于在生成器中(yield 表达式处)引发异常,调用时可提供一个异常类型、一个可选值和一个traceback 对象。

方法close :用于停止生成器,调用时无需提供任何参数。 

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南河Aure

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值