9. Classes

与其他编程语言相比,Python 的类机制以最少的新语法和语义添加了类。它是 C 和 Modula-3 中发现的类机制的混合。 Python 类提供了面向对象编程的所有标准Function:类继承机制允许多个 Base Class,派生类可以覆盖其一个或多个 Base Class 的任何方法,并且一个方法可以调用具有相同名称的 Base Class 的方法。对象可以包含任意数量和种类的数据。就像模块一样,类具有 Python 的动态特性:它们在运行时创建,并且可以在创建后进行进一步修改。

在 C 术语中,通常,类成员(包括数据成员)是* public (见下面的私有变量和类本地引用除外),而所有成员函数都是 virtual *。与 Modula-3 中一样,没有从其方法引用该对象的成员的捷径:方法函数以表示该对象的显式第一个参数语句,该参数由调用隐式提供。就像在 Smalltalk 中一样,类本身就是对象。这提供了导入和重命名的语义。与 C 和 Modula-3 不同,内置类型可以用作用户扩展的 Base Class。而且,像在 C 中一样,可以为类实例重新定义大多数具有特殊语法的内置运算符(算术运算符,下标等)。

(由于缺少用于讨论类的通用术语,我偶尔会使用 Smalltalk 和 C 术语.我将使用 Modula-3 术语,因为它的面向对象的语义比 C 更接近于 Python 的语义,但我希望 Reader 很少听说过.)

9.1. 关于名称和对象的一句话

对象具有个性,可以将多个名称(在多个作用域中)绑定到同Pair象。这在其他语言中称为别名。乍一看,这通常是不被理解的,在处理不可变的基本类型(数字,字符串,Tuples)时,可以放心地忽略它。但是,别名对涉及可变对象(如列表,字典和大多数其他类型)的 Python 代码的语义可能会产生令人惊讶的影响。通常这样做是为了使程序受益,因为别名在某些方面的行为类似于指针。例如,传递一个对象很便宜,因为实现只传递了一个指针。如果函数修改了作为参数传递的对象,则调用者将看到更改-这样就无需像 Pascal 中那样使用两种不同的参数传递机制。

9.2. Python 范围和命名空间

在介绍类之前,我首先必须告诉您一些有关 Python 范围规则的知识。类定义在命名空间上起到了一些巧妙的作用,您需要知道作用域和命名空间如何工作以完全了解正在发生的事情。顺便说一句,有关此主题的知识对于任何高级 Python 程序员都是有用的。

让我们从一些定义开始。

  • namespace *是从名称到对象的 Map。大多数名称空间当前都实现为 Python 字典,但通常不会以任何方式引起注意(性能除外),并且将来可能会发生变化。命名空间的示例包括:一组内置名称(包含诸如abs()之类的函数和内置异常名称);模块中的全局名称;以及函数调用中的本地名称。从某种意义上说,对象的属性集也形成一个名称空间。了解名称空间的重要一点是,不同名称空间中的名称之间绝对没有任何关系。例如,两个不同的模块都可以定义一个函数maximize,而不会引起混淆-模块的用户必须在其前面加上模块名称。

顺便说一下,对于点后的任何名称,我都使用单词* attribute *-例如,在表达式z.real中,real是对象z的属性。严格来说,对模块中名称的引用是属性引用:在表达式modname.funcname中,modname是模块对象,而funcname是它的属性。在这种情况下,恰好在模块的属性和模块中定义的全局名称之间存在直接 Map:它们共享相同的名称空间! [1]

属性可以是只读或可写的。在后一种情况下,可以分配属性。模块属性是可写的:您可以编写modname.the_answer = 42。也可以使用del语句删除可写属性。例如,del modname.the_answer将从modname命名的对象中删除属性the_answer

命名空间在不同的 Moment 创建并具有不同的生存期。包含内置名称的名称空间是在 Python 解释器启动时创建的,并且永远不会删除。读入模块定义后,将创建模块的全局名称空间。通常,模块名称空间也将持续到解释器退出为止。从解释器的顶层调用执行的语句(从脚本文件中读取或以交互方式读取)被视为名为main的模块的一部分,因此它们具有自己的全局名称空间。 (内置名称实际上也存在于模块中;这称为builtin。)

函数的本地名称空间是在调用函数时创建的,并在函数返回或引发函数中未处理的异常时删除。 (实际上,忘记是描述实际情况的更好方法.)当然,递归调用每个都有自己的本地名称空间。

  • scope *是 Python 程序的文本区域,可直接访问名称空间。这里的“直接访问”是指对名称的不合格引用试图在名称空间中找到该名称。

尽管范围是静态确定的,但它们是动态使用的。在执行期间的任何时间,至少有三个嵌套作用域,其名称空间可直接访问:

  • 最里面的作用域(首先搜索)包含本地名称

  • 从最接近的封闭范围开始搜索的任何封闭函数的范围都包含 nonlocal 名称,但也包含非全局名称

  • 倒数第二个范围包含当前模块的全局名称

  • 最外层范围(最后搜索)是包含内置名称的名称空间

如果名称被语句为全局名称,则所有引用和赋值将直接转到包含模块全局名称的中间范围。否则,在最内层作用域之外找到的所有变量都是只读的(try写入此类变量只会在最内层作用域内创建一个* new *局部变量,而使名称相同的外层变量保持不变)。

通常,本地范围引用(按文本形式)当前函数的本地名称。在外部函数中,本地范围引用与全局范围相同的名称空间:模块的名称空间。类定义在本地范围内放置了另一个名称空间。

重要的是要认识到范围是由文本确定的:模块中定义的函数的全局范围是该模块的名称空间,无论调用该函数的位置或别名是什么。另一方面,实际的名称搜索是在运行时动态完成的-但是,语言定义正在“编译”时向静态名称解析 Developing,所以不要依赖动态名称解析! (实际上,局部变量已经是静态确定的.)

Python 的一个特殊怪癖是–如果没有global语句生效–名称分配总是进入最内部的范围。分配不复制数据,它们只是将名称绑定到对象。删除也是如此:语句del x从本地作用域引用的名称空间中删除x的绑定。实际上,所有引入新名称的操作都使用本地范围:特别是import语句和函数定义将模块或函数名称绑定在本地范围内。 (global语句可用于指示特定变量在全局范围内。)

9.3. 初识类

类引入了一些新的语法,三种新的对象类型以及一些新的语义。

9.3.1. 类定义语法

类定义的最简单形式如下所示:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

必须先执行类定义,例如函数定义(def语句),然后才能生效。 (可以想象,您可以将类定义放在if语句的分支中或函数内部。)

在实践中,类定义中的语句通常是函数定义,但是其他语句也是允许的,并且有时是有用的,我们稍后再讲。类内部的函数定义通常具有特殊形式的参数列表,这由方法的调用约定所指定-再次对此进行说明。

Importing 类定义时,将创建一个新的名称空间,并将其用作本地作用域-因此,所有对本地变量的分配都将进入此新的名称空间。特别是,函数定义在此处绑定新函数的名称。

当正常保留类定义时(pass结尾),将创建一个* class object *。基本上,这是对由类定义创建的名称空间内容的包装。我们将在下一节中了解有关类对象的更多信息。恢复了原始的本地作用域(即在 Importing 类定义之前生效的作用域),并且在这里将类对象绑定到类定义 Headers 中给出的类名称(示例中为ClassName)。

9.3.2. 类对象

类对象支持两种操作:属性引用和实例化。

属性引用使用用于 Python 中所有属性引用的标准语法:obj.name。有效属性名称是创建类对象时在类名称空间中的所有名称。因此,如果类定义如下所示:

class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'

那么MyClass.iMyClass.f是有效的属性引用,分别返回整数和函数对象。类属性也可以分配给它,因此您可以pass赋值来更改MyClass.i的值。 __doc__也是有效的属性,返回属于"A simple example class"类的文档字符串。

  • instantiation *类使用函数符号。只是 Feign 类对象是一个无参数函数,它将返回该类的新实例。例如(假设上面的类):
x = MyClass()

创建该类的新* instance *并将该对象分配给局部变量x

实例化操作(“调用”一个类对象)创建一个空对象。许多类喜欢创建具有定制为特定初始状态的实例的对象。因此,一个类可以定义一个名为init()的特殊方法,如下所示:

def __init__(self):
    self.data = []

当类定义init()方法时,类实例化将自动为新创建的类实例调用init()。因此,在此示例中,可以pass以下方式获取新的初始化实例:

x = MyClass()

当然,init()方法可以具有更大的灵 Active。在这种情况下,提供给类实例化运算符的参数将传递给init()。例如,

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

9.3.3. 实例对象

现在我们该如何处理实例对象?实例对象只能理解的操作是属性引用。有效的属性名称有两种,数据属性和方法。

数据属性对应于 Smalltalk 中的“实例变量”,以及 C 中的“数据成员”。数据属性不需要语句;像局部变量一样,它们在首次分配时就存在。例如,如果x是上面创建的MyClass的实例,则以下代码将打印值16,而不会留下任何痕迹:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print x.counter
del x.counter

另一种实例属性引用是* method *。方法是“属于”对象的Function。 (在 Python 中,术语“方法”并不是类实例所独有的:其他对象类型也可以具有方法.例如,列表对象具有称为 append,insert,remove,sort 等的方法.但是,在下面的讨论中,我们将专门使用术语“方法”来表示类实例对象的方法,除非另有明确说明.)

实例对象的有效方法名称取决于其类。根据定义,作为函数对象的类的所有属性都定义了其实例的相应方法。因此,在我们的示例中,x.f是有效的方法引用,因为MyClass.f是一个函数,但是x.i不是,因为MyClass.i不是。但是x.fMyClass.f是不同的-它是方法对象,不是函数对象。

9.3.4. 方法对象

通常,方法在绑定后立即被调用:

x.f()

MyClass示例中,它将返回字符串'hello world'。但是,没有必要立即调用方法:x.f是方法对象,可以立即存储并在以后调用。例如:

xf = x.f
while True:
    print xf()

将 continue 打印hello world,直到时间结束。

调用方法时会发生什么?您可能已经注意到,即使f()的函数定义指定了一个自变量,但上面没有自变量的情况下调用了x.f()。Arguments 发生了什么?当没有任何参数调用需要参数的函数时,Python 当然会引发异常-即使该参数并未实际使用。

实际上,您可能已经猜到了答案:方法的特殊之处在于,对象是作为函数的第一个参数传递的。在我们的示例中,呼叫x.f()MyClass.f(x)完全等效。通常,调用带有* n *参数列表的方法等同于调用带有参数列表的函数,该参数列表是pass在第一个参数之前插入方法的对象而创建的。

如果您仍然不了解方法的工作原理,那么看一下实现也许可以澄清问题。当引用实例的非数据属性时,将搜索实例的类。如果名称表示作为函数对象的有效类属性,则pass将实例对象和刚在抽象对象中一起找到的函数对象打包(指向)来创建方法对象:这是方法对象。当使用实参列表调用方法对象时,将从实例对象和实参列表构造一个新的实参列表,并使用该新的实参列表来调用函数对象。

9.3.5. 类和实例变量

一般而言,实例变量用于每个实例唯一的数据,而类变量用于该类的所有实例共享的属性和方法:

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'

关于名称和对象的一句话中所述,共享数据可能涉及涉及列表和字典之类的mutable对象,可能会产生令人惊讶的效果。例如,以下代码中的* tricks 列表不应用作类变量,因为所有 Dog *实例只会共享一个列表:

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

正确的类设计应改用实例变量:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']

9.4. 随机备注

数据属性覆盖具有相同名称的方法属性;为避免意外的名称冲突(可能导致大型程序中难以发现的错误),明智的做法是使用某种约定,以最大程度地减少冲突的可能性。可能的约定包括大写方法名称,在数据属性名称前添加一个小的唯一字符串(也许只是一个下划线)或对方法使用动词,对数据属性使用名词。

数据属性可以由对象的方法以及普通用户(“Client 端”)引用。换句话说,类不能用于实现纯抽象数据类型。实际上,Python 中没有任何东西可以强制执行数据隐藏,而这一切都是基于约定的。 (另一方面,用 C 编写的 Python 实现可以完全隐藏实现细节,并在必要时控制对对象的访问;这可以由用 C 编写的 Python 扩展使用.)

Client 应谨慎使用数据属性-Client 可能会在数据属性上加盖戳记,从而弄乱了方法维护的不变性。请注意,只要避免名称冲突,Client 端就可以将自己的数据属性添加到实例对象,而不会影响方法的有效性-同样,命名约定可以在这里省去很多麻烦。

没有从方法内部引用数据属性(或其他方法!)的捷径。我发现这实际上提高了方法的可读性:浏览方法时,不会混淆局部变量和实例变量。

通常,方法的第一个参数称为self。这不过是一个约定:self对 Python 绝对没有特殊含义。但是请注意,如果不遵循该约定,您的代码对于其他 Python 程序员来说可能就不太可读,并且还可以想象,可能会依赖此类约定编写一个* class browser *程序。

任何作为类属性的函数对象都为该类的实例定义一个方法。不必在文本上将函数定义包含在类定义中:将函数对象分配给类中的局部变量也是可以的。例如:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1

    def g(self):
        return 'hello world'

    h = g

现在fgh都是引用函数对象的C类的属性,因此,它们都是C _ h的实例的所有方法,它们完全等效于g。请注意,这种做法通常只会使程序的 Reader 感到困惑。

方法可以使用self参数的方法属性来调用其他方法:

class Bag:
    def __init__(self):
        self.data = []

    def add(self, x):
        self.data.append(x)

    def addtwice(self, x):
        self.add(x)
        self.add(x)

方法可以以与普通函数相同的方式引用全局名称。与方法关联的全局范围是包含其定义的模块。 (永远不要将类用作全局范围.)虽然很少有人遇到在方法中使用全局数据的充分理由,但是有很多合理使用全局范围的方法:一方面,导入全局范围的函数和模块可以由方法以及其中定义的函数和类使用。通常,包含该方法的类本身是在此全局范围内定义的,在下一节中,我们将找到一些很好的理由说明为什么方法要引用其自己的类。

每个值都是一个对象,因此具有一个* class (也称为其 type *)。它存储为object.__class__

9.5. Inheritance

当然,如果不支持继承,那么语言Function就不配得上“类”这个名字。派生类定义的语法如下所示:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

名称BaseClassName必须在包含派生类定义的范围内定义。除了 Base Class 名称,还可以使用其他任意表达式。例如,当在另一个模块中定义了 Base Class 时,这可能会很有用:

class DerivedClassName(modname.BaseClassName):

派生类定义的执行与 Base Class 的执行相同。构造类对象时,将记住 Base Class。这用于解析属性引用:如果在类中找不到请求的属性,则搜索将 continue 查找 Base Class。如果 Base Class 本身是从其他类派生的,则递归应用此规则。

派生类的实例化没有什么特别的:DerivedClassName()创建该类的新实例。方法引用的解析如下:搜索相应的类属性,必要时沿 Base Class 的链降序;如果产生了函数对象,则方法引用有效。

派生类可以覆盖其 Base Class 的方法。因为方法在调用同Pair象的其他方法时没有特殊特权,所以 Base Class 的方法调用同一 Base Class 中定义的另一个方法可能finally会调用覆盖它的派生类的方法。 (对于 C 程序员:Python 中的所有方法实际上都是virtual.)

实际上,派生类中的重写方法可能想扩展而不是简单地替换相同名称的 Base Class 方法。有一种直接调用 Base Class 方法的简单方法:只需调用BaseClassName.methodname(self, arguments)即可。这有时对 Client 也很有用。 (请注意,这仅在 Base Class 可在全局范围中以BaseClassName访问的情况下有效.)

Python 具有两个可用于继承的内置函数:

  • 使用isinstance()检查实例的类型:仅当obj.__class__int或从int派生的某个类时,isinstance(obj, int)才是True

  • 使用issubclass()来检查类继承:issubclass(bool, int)True,因为boolint的子类。但是,issubclass(unicode, str)False,因为unicode不是str的子类(它们仅共享一个共同祖先basestring)。

9.5.1. 多重继承

Python 也支持有限形式的多重继承。具有多个 Base Class 的类定义如下所示:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

对于老式类,唯一的规则是深度优先,从左到右。因此,如果在DerivedClassName中找不到属性,则在Base1中搜索该属性,然后(递归地)在Base1的 Base Class 中,并且只有在该属性中找不到该属性时,才在Base2中进行搜索,依此类推。

(对于某些人来说,先广度-在Base1的 Base Class 之前搜索Base2Base3看起来更自然.但是,这需要您先知道Base1的特定属性是实际上是在Base1还是在其 Base Class 之一中定义的,可以找出属性为Base2的名称冲突的后果.深度优先规则不会对Base1的直接属性和继承的属性造成任何区别.)

对于new-style class es,方法解析 Sequences 会动态更改,以支持对super()的协作调用。这种方法在某些其他多继承语言中称为“调用下一方法”,并且比在单继承语言中找到的超级调用更强大。

对于new-style类,动态排序是必需的,因为所有多重继承的案例都具有一个或多个菱形关系(其中至少一个父类可以pass从最底层类访问的多个路径进行访问)。例如,所有新式类都继承自object,因此任何多重继承的情况都提供了多个到达object的路径。为防止 Base Class 被多次访问,动态算法以一种线性化搜索 Sequences 的方式,该方式保留了每个类中指定的从左到右的 Sequences,仅调用了每个父类一次,并且这是单调的(这意味着一个类可以被子类化而不会影响其父级的优先 Sequences)。综合考虑这些属性,可以设计具有多重继承的可靠且可扩展的类。有关更多详细信息,请参见https://www.python.org/download/releases/2.3/mro/

9.6. 私有变量和类本地引用

Python 中不存在只能从对象内部访问的“私有”实例变量。但是,大多数 Python 代码遵循一个约定:以下划线开头的名称(例如_spam)应被视为 API 的非公开部分(无论是函数,方法还是数据成员) 。它应被视为实施细节,如有更改,恕不另行通知。

由于存在类专用成员的有效用例(即避免名称与子类定义的名称发生名称冲突),因此对这种称为* name mangling *的机制的支持有限。格式为__spam的任何标识符(至少两个前导下划线,至多一个尾随下划线)在文本上均被_classname__spam替换,其中classname是当前类名,其中前导下划线被去除。只要不出现标识符的语法位置,就可以进行这种 Rewrite,只要它出现在类的定义内即可。

名称修饰有助于让子类覆盖方法而又不break类内方法调用。例如:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

即使MappingSubclass引入了__update标识符,上面的示例也将起作用,因为Mapping类中的_Mapping__updateMappingSubclass类中的_MappingSubclass__update分别替换了它。

请注意,修改规则主要是为了避免发生意外。仍然可以访问或修改被视为私有的变量。这在特殊情况下(例如在调试器中)甚至很有用。

请注意,传递给execeval()execfile()的代码不将调用类的类名视为当前类;这类似于global语句的效果,该语句的效果同样仅限于字节编译在一起的代码。相同的限制适用于getattr()setattr()delattr()以及直接引用__dict__时。

9.7. Odds and Ends

有时,将类似于 Pascal“ record”或 C“ struct”的数据类型 Binding 在一起,将一些命名的数据项 Binding 在一起很有用。一个空的类定义会很好地实现:

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

期望特定抽象数据类型的一段 Python 代码通常可以传递给类,该类模仿该数据类型的方法。例如,如果您有一个格式化文件对象中某些数据的函数,则可以使用方法read()readline()定义一个类,该方法从字符串缓冲区获取数据,然后将其作为参数传递。

实例方法对象也具有属性:m.im_self是具有方法m()的实例对象,而m.im_func是与该方法相对应的Function对象。

9.8. exception 也是类

用户定义的异常也由类标识。使用这种机制,可以创建异常的可扩展层次结构。

raise语句有两种新的有效(语义)形式:

raise Class, instance

raise instance

在第一种形式中,instance必须是Class或从其派生的类的实例。第二种形式是:

raise instance.__class__, instance

如果except子句中的类是同一类或其 Base Class,则该类与异常兼容(但不是相反的方式-列出派生类的 except 子句与 Base Class 不兼容)。例如,以下代码将按此 Sequences 打印 B,C,D:

class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B, C, D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B"

请注意,如果将 except 子句颠倒(先使用except B),则会打印出 B,B,B -触发第一个匹配的 except 子句。

当针对未处理的异常打印错误消息时,将打印异常的类名,然后打印冒号和空格,最后使用内置函数str()将实例转换为字符串。

9.9. Iterators

到目前为止,您可能已经注意到,大多数容器对象都可以使用for语句循环遍历:

for element in [1, 2, 3]:
    print element
for element in (1, 2, 3):
    print element
for key in {'one':1, 'two':2}:
    print key
for char in "123":
    print char
for line in open("myfile.txt"):
    print line,

这种访问方式清晰,简洁,方便。迭代器的使用遍布并统一了 Python。在后台,for语句在容器对象上调用iter()。该函数返回一个迭代器对象,该对象定义了方法next(),该方法一次访问一个容器中的元素。当没有更多元素时,next()引发StopIteration异常,该异常告诉for循环终止。这个例子展示了它是如何工作的:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    it.next()
StopIteration

了解了迭代器协议背后的机制后,可以很容易地将迭代器行为添加到类中。定义一个iter()方法,该方法返回一个带有next()方法的对象。如果该类定义了next(),那么iter()可以返回self

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print char
...
m
a
p
s

9.10. Generators

Generator s 是创建迭代器的简单而强大的工具。它们的编写方式与常规函数类似,但是只要要返回数据就使用yield语句。每次调用next()时,生成器都会从上次break的地方恢复(它会记住所有数据值以及上次执行的语句)。一个示例显示生成器可以很容易地创建:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
>>> for char in reverse('golf'):
...     print char
...
f
l
o
g

生成器可以完成的所有操作也可以使用上一节中所述的基于类的迭代器来完成。生成器如此紧凑的原因在于iter()next()方法是自动创建的。

另一个关键Function是局部变量和执行状态在两次调用之间自动保存。与使用实例变量如self.indexself.data的方法相比,这使函数更易于编写并且更加清晰。

除了自动创建方法和保存程序状态外,生成器终止时,它们还会自动引发StopIteration。结合使用这些Function,可以轻松编写迭代器,而无需编写常规函数。

9.11. 生成器表达式

某些简单的生成器可以使用类似于列表推导的语法简洁地编码为表达式,但是使用括号而不是方括号。这些表达式设计用于封闭函数立即使用生成器的情况。生成器表达式比完整的生成器定义更紧凑,但用途更少,并且比等效的列表理解更易于存储。

Examples:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> from math import pi, sin
>>> sine_table = dict((x, sin(x*pi/180)) for x in range(0, 91))

>>> unique_words = set(word  for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1,-1,-1))
['f', 'l', 'o', 'g']

Footnotes

  • [1]
    • 除了一件事。模块对象具有一个名为dict的 Secret 只读属性,该属性返回用于实现模块名称空间的字典。名称dict是属性,而不是全局名称。显然,使用此方法违反了名称空间实现的抽象,应仅限于事后调试器之类。