Python3.7官方向导翻译之Python类

类提供了将数据和功能捆绑在一起的手段。 创建一个新类创建一个新类型的对象,允许创建该类型的新实例。 每个类实例都可以附加属性以保持其状态。 类实例也可以有方法(由其类定义)来修改其状态。

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

**在C++术语中,通常类成员(包括数据成员)是公有的(例外见下面的Privat Variables),并且所有的成员函数都是虚的(virtual)。和Modula-3中一样,
没有快捷的办法从对象的方法中来引用对象的成员:方法的第一个显示函数代表了对象本身,对对象本身的调用是隐式的。和Smaltalk中一样,类本身也是对象。
这为导入和重命名提供了语义。不像C++和Modula-3,内置类型可以作为基类来扩展。同样,和C++中的一样,大部分内置操作符有特殊的语法(算术操作符,
下标等),它们可以在类实例中重定义。**

(由于缺乏普遍接受的术语来讨论类,我偶尔会使用Smalltalk和C ++术语,因为它的面向对象语义与Python比Python更接近,所以我会使用Modula-3术语,但我不期望有太多读者听说过。)

关于名字和对象的一个词

对象是独立的,多个名字(在多个作用域)可以绑定到同一个对象。这种特性在其他语言中称为别名(aliasing)。通常,刚接触Python是对这个别名没什么感觉(不觉得有什么用),并且在处理不可变的基本类型(数值,字符串,元组)时可以无视它。但是,当涉及到可变对象,如列表,字典及其他类型时,别名对Python代码会有一种惊喜surprising的影响。这通常用于程序的好处,因为别名在某些方面表现得像指针pointers。 例如,只有一个指针需要传递时,传递一个对象很方便; 如果一个函数修改了一个作为参数传递的对象,调用者就会看到这个变化 - 这就消除了像Pascal一样需要两个不同的参数传递机制。

Python作用域和命名空间

在介绍类之前,我首先必须告诉你一些关于Python作用域规则的内容。 类定义在命名空间中扮演一些巧妙的技巧,并且您需要知道范围和名称空间如何工作才能完全理解正在发生的事情。 顺便提一下,有关此主题的知识对于任何高级Python程序员都很有用。

让我们开始吧!!!!

名称空间是从名称到对象的映射。 大多数命名空间目前都是作为Python字典实现的,但通常不会以任何方式显示(性能除外),并且将来可能会发生变化。 名称空间的例子是:内置名称集合(包含诸如abs()函数和内置的异常名称等); 模块中的全局名称; 和函数调用(invocation)中的本地名称。 从某种意义上说,对象的一组属性也构成一个名称空间。 了解名称空间的重要之处在于,不同名称空间中的名称之间绝对没有关系; 例如,两个不同的模块都可以定义一个maximum()函数而不会混淆 - 模块的使用者必须在模块名称前加前缀。

顺便说一句,我使用了单词attribute(属性)来表示.后面的任何名字,例如,z.real中,real是对象z的一个属性。严格说来,模块中的名称引用是属性引用:在表达式modname.funcname中,modname是一个模块对象,funcname是它的一个属性。在这种情况下,模块的属性和定义在模块中的全局名称建立了一个直接的映射:他们共用同一个命名空间。

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

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

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

作用域是Python程序的有效区域,在作用域中,可以直接访问命名空间。 这里的“直接访问”意味着对名称的非限定引用会尝试在名称空间中查找名称。

尽管范围是静态确定的,但它们是动态使用的。 在执行期间的任何时候,至少有三个嵌套的作用域可直接访问:
1. 最内层范围包含本地名称,是最先搜索的
2. 任何封闭函数的范围包含非本地名称,也包含非全局名称,是从最近的封闭范围开始搜索的
3. 倒数第二个作用域包含当前模块的全局名称
4. 最后一个范围(最后搜索)是包含内置名称的名称空间

如果某个名称被声明为全局名称,则所有引用和赋值都将直接转到包含模块全局名称的中间作用域。 要重新绑定在最内层范围外发现的变量,可以使用nonlocal语句; 如果未声明为非本地变量,则这些变量是只读的(试图写入这样的变量将仅在最内层范围中创建新的局部变量,而保持相同名称的外层变量不变)。

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

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

Python的特殊之处在于 - 如果没有全局声明生效 - 对名称的分配总是进入最内层的范围。 分配不会复制数据 - 它们只是将名称绑定到对象。 删除操作也是如此:语句del x从本地作用域引用的名称空间中删除x的绑定。 实际上,所有引入新名称的操作都使用本地作用域:特别是,导入语句和函数定义将模块或函数名称绑定在本地作用域中。

global声明可以用来表明特定变量存在于全局作用域内,应该在那里rebound; nonlocal声明表明特定变量存在于封闭范围内,应该在那里rebound。

作用域和命名空间示例

这是一个演示如何引用不同范围和名称空间以及全局和非本地如何影响变量绑定的示例

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

请注意,本地分配(local)(这是默认设置)不会更改scope_test对spam的绑定。 nonlocal改变了scope_test对spam的绑定,global改变了模块级的绑定。

初探类

类引入了一点新的语法,3个新的对象类型,以及一些新的语义。

类定义语法

类定义的最简单形式如下

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

和函数定义一样,要想让类发货作用首先要定义类。在实践中,类定义中的语句通常是函数定义,但其他语句是允许的,有时很有用 - 我们稍后会回到这个。 类中的函数定义通常有一个特殊形式的参数列表,由方法的调用约定决定 - 再次,这在后面解释。

当输入类定义时,会创建一个新的名称空间,并将其用作本地作用域 - 因此,所有对局部变量的赋值都会进入这个新的名称空间。 特别是,函数定义在此绑定新函数的名称。

类对象

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

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

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

    def f(self):
        return 'hello world'

那么MyClass.i和MyClass.f是有效的属性引用,分别返回一个整数和一个函数对象。 类属性也可以赋值,所以你可以通过赋值来改变MyClass.i的值。 doc也是一个有效的属性,返回属于该类的文档字符串:“A simple example class”。

类实例化使用函数表示法。 假设类对象是一个返回类的新实例的无参数函数。 例如(假设上面的类):

x = MyClass()

创建该类的新实例并将该对象分配给局部变量x。

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

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

当一个类定义了__init __()方法时,类实例会自动为新创建的类实例调用__init __()。 所以在这个例子中,一个新的初始化实例可以通过以下方式获得:

x = MyClass()

当然,__init __()方法可能有更多灵活性的参数。 在这种情况下,给类实例化操作符的参数被传递给__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)

实例对象

现在我们可以使用实例对象做什么? 实例对象理解的唯一操作是属性引用。 有两种有效的属性名称,数据属性和方法。

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

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

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

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

方法对象

通常,一个方法在绑定后会被调用

x.f()

在MyClass示例中,这将返回字符串’hello world’。 但是,不需要立即调用一个方法:x.f是一个方法对象,可以存储并在稍后调用。 例如:

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

当一个方法被调用时究竟发生了什么? 您可能已经注意到,即使f()的函数定义指定了参数,x.f()也没有上述参数被调用。 这个参数发生了什么? 当一个需要参数的函数没有被调用时,当然Python会引发一个异常 - 即使这个参数没有被实际使用...

实际上,您可能已经猜到了答案:**关于方法的特殊之处在于实例对象作为函数的第一个参数传递**。 在我们的例子中,调用x.f()完全等同于MyClass.f(x)。 一般来说,调用带有n个参数列表的方法相当于使用通过在第一个参数之前插入方法实例对象创建的参数列表来调用相应的函数。

如果您仍然不明白方法是如何工作的,那么查看实现可能会澄清一些问题。 当引用不是数据属性的实例属性时,将搜索其类。 如果名称表示一个有效的类属性(它是一个函数对象),则通过打包(指向)实例对象以及在抽象对象中一起找到的函数对象来创建方法对象:这是方法对象。 当使用参数列表调用方法对象时,会从实例对象和参数列表构造一个新参数列表,并使用此新参数列表调用函数对象。

### 类变量和实例变量

一般来说,实例变量是针对每个实例唯一的数据,而类变量是针对类的所有实例共享的属性和方法:


```python
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




<div class="se-preview-section-delimiter"></div>
'canine'
e.kind




<div class="se-preview-section-delimiter"></div>
'canine'
d.name




<div class="se-preview-section-delimiter"></div>
'Fido'
e.name




<div class="se-preview-section-delimiter"></div>
'Buddy'

正如’关于名称和对象的单词’中所讨论的那样,共享数据可能会带来令人惊讶的影响,涉及列表和字典等可变对象。 例如,以下代码中的技巧列表不应该用作类变量,因为只有一个列表将由所有Dog实例共享

class Dog:
    tricks = []              #mistaken use of a class of 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




<div class="se-preview-section-delimiter"></div>
['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




<div class="se-preview-section-delimiter"></div>
['roll over']
e.tricks




<div class="se-preview-section-delimiter"></div>
['play dead']

随机备注

数据属性覆盖具有相同名称的方法属性; 这可能会在大型程序中导致难以发现的错误,为了避免意外的名称冲突,使用某种最小化冲突几率的约定是明智的。 可能的约定包括大写方法名称captilizing method names,用小的唯一字符串(可能只是一个下划线)为数据属性名称加前缀,或者使用动词来表示方法,使用名称来表示数据。

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

客户应小心使用数据属性 - 客户可能会通过标记其数据属性来混淆方法维护的不变量。 请注意,客户端可以将自己的数据属性添加到实例对象,而不会影响方法的有效性,只要避免名称冲突 - 再次,命名约定可以在此省很多事。

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

通常,方法的第一个参数称为self。 这只不过是一个约定:名字self对Python来说绝对没有特殊含义。 但是,请注意,通过不遵循约定,您的代码对其他Python程序员而言可能会很难读,并且写一个依赖于上述约定的类浏览程序会是很方便的。

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





<div class="se-preview-section-delimiter"></div>

# 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




<div class="se-preview-section-delimiter"></div>

现在f,g和h都是C类的所有属性,它们都是指向函数对象的,因此它们都是C实例的所有的方法 - h完全等价于g。请注意,这种做法通常只会影响程序的读者。

方法可以通过使用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)




<div class="se-preview-section-delimiter"></div>

方法可以像普通函数一样引用全局名称。 方法作用范围是包含其定义的模块。 (一个类永远不会被用作全局范围。)虽然很少有人在方法中使用全局数据,但全局范围有许多合法用途:首先,导入全局范围的函数和模块可以 被方法使用,以及在其中定义的函数和类。 通常,包含该方法的类本身是在全局范围内定义的,在下一节中,我们会找到一些很好的理由来说明方法想要引用它自己的类。

每个值都是一个对象,因此都会有一个类(叫做类型),它被存储为object.class.

继承Inheritance

当然,如果不支持继承,语言特性就不值得称为“类”。 派生类定义的语法如下所示:

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




<div class="se-preview-section-delimiter"></div>

名称BaseClassName必须在包含派生类定义的作用域中定义。 当基类在另一个模块中定义时,这可能很有用:

派生类定义的执行过程与基类相同。 当构造类对象时,基类将被记住(remember)。 这用于解析属性引用:如果在类中未找到请求的属性,则搜索继续查找基类。 如果基类本身是从其他类派生的,则此规则将递归应用。

派生类的实例化没有什么特别的:DerivedClassName()创建一个新的类实例。 方法引用解析如下:如果需要,搜索相应的类属性,沿基类链降序排列,如果遇到函数对象,则方法引用是有效的。

派生类可以重写它们的基类的方法。 由于方法在调用同一对象的其他方法时没有特殊的权限,因此,调用另一个在同一基类中定义的方法的基类方法可能最终会调用重写的派生类方法。 (对于C ++程序员:Python中的所有方法都是虚的。)

派生类中的重写方法事实上可能需要扩展而不是简单地替换同名的基类方法。 有一种简单的方法可以直接调用基类方法:只需调用BaseClassName.methodname(self,arguments)即可。 这对客户也是偶尔有用的。 (注意,这只有在基类可以在全局范围内作为BaseClassName访问时才有效。)

Python有两个与继承一起工作的内置函数:
1. 使用isinstance()来检查实例的类型:只有当obj ._ class_是int或从int派生的某个类时,isinstance(obj,int)才会为True。
2. 使用issubclass()检查类继承:由于bool是int的子类,因此issubclass(bool,int)为True。 但是,由于float不是int的子类,因此issubclass(float,int)为False。

多重继承

Python也支持多重继承。继承多个类的子类定义方式如下:

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




<div class="se-preview-section-delimiter"></div>

对于大多数用途而言,在最简单的情况下,您可以将从父类继承的属性视为深度优先,从左到右搜索,而不是在层次结构中存在重叠的相同类中搜索两次。 因此,如果在DerivedClassName中找不到属性,则在Base1中搜索它,然后(递归地)在Base1的基类中搜索它,并且如果在那里未找到它,则在Base2中搜索它,等等

事实上,它比这稍微复杂一些; 方法解析顺序动态改变以支持对super()的合作调用。 这种方法在一些其他多继承语言中称为call-next-method,并且比在单继承语言中发现的super调用更强大。

动态排序是必要的,因为所有多重继承的情况都表现出一个或多个菱形关系(其中至少有一个父类可以通过最底层的多个路径访问)。 例如,所有类都从对象继承,所以任何多重继承的情况都会提供多条路径来达到对象。 为了防止基类被多次访问,动态算法使搜索顺序线性化,以保留每个类中指定的从左到右的顺序,每个父类只调用一次,这是单调的(意思是 一个类可以被子类化,而不会影响父类的优先顺序)。 综合起来,这些属性使设计具有多继承性的可靠和可扩展的类成为可能。 有关更多详细信息,请参阅https://www.python.org/download/releases/2.3/mro/

私有变量

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

由于类私有成员有一个有效的用例(即为了避免名称与名称由子类定义的名称冲突),所以对这种称为名称修改(name mangling)的机制的支持有限。 任何__spam形式的标识符(至少两个前导下划线,最多一个尾部下划线)在文本上用_classname__spam替换,其中classname是当前类名称,前导下划线被去除。 只要它在类的定义内发生,就不会考虑标识符的语法位置。

名称修改有助于让子类重写方法而不会破坏内部方法调用。 例如:

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)




<div class="se-preview-section-delimiter"></div>

请注意,强化规则的设计主要是为了避免事故; 它仍然可以访问或修改被认为是私有的变量。 这在特殊情况下甚至是有用的,比如在调试器中。

注意传递给exec()或eval()的代码并不认为调用类的类名是当前类; 这与全局语句的效果类似,其效果同样局限于一起字节编译的代码。 getattr(),setattr()和delattr()以及直接引用dict时也有相同的限制。

Odds and Ends

有时候将数据类型与Pascal“记录”或C“结构”类似,将几个命名的数据项绑定在一起非常有用。 一个空的类定义将很好地做到:

class Employee:
    pass

john = Employee()         # Create an empty employee record





<div class="se-preview-section-delimiter"></div>

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




<div class="se-preview-section-delimiter"></div>

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

实例方法对象也具有属性:m .__ self__是方法为m()的实例对象,m .__ func__是与该方法对应的函数对象。

迭代

现在你可能已经注意到大多数容器对象可以使用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, end='')




<div class="se-preview-section-delimiter"></div>

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

s = 'ABC'
it = iter(s)
it




<div class="se-preview-section-delimiter"></div>
<str_iterator at 0xdee668>
next(it)




<div class="se-preview-section-delimiter"></div>
'A'
next(it)




<div class="se-preview-section-delimiter"></div>
'B'
next(it)




<div class="se-preview-section-delimiter"></div>
'C'
next(it)




<div class="se-preview-section-delimiter"></div>
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-11-bc1ab118995a> in <module>()
----> 1 next(it)


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]




<div class="se-preview-section-delimiter"></div>
rev = Reverse('spam')
iter(rev)




<div class="se-preview-section-delimiter"></div>
<__main__.Reverse at 0xdfc320>
for char in rev:
    print(char)




<div class="se-preview-section-delimiter"></div>
m
a
p
s

生成器Generators

生成器是创建迭代器的简单而强大的工具。 它们像常规函数一样编写,但只要想返回数据就使用yield语句。 每次next()被调用时,生成器都会从停止的地方恢复(它记住所有的数据值以及上次执行的语句)。 下面的例子表明,生成器可以轻而易举地创建:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]




<div class="se-preview-section-delimiter"></div>
for char in reverse('golf'):
    print(char)




<div class="se-preview-section-delimiter"></div>
f
l
o
g

任何可以用发生器完成的事情也可以用前面部分描述的基于类的迭代器完成。 使生成器如此紧凑的原因是__iter __()和__next __()方法是自动创建的。

另一个关键特性是本地变量和执行状态在调用之间自动保存。 这使得该函数更容易编写,并且比使用self.index和self.data等实例变量的方法更加清晰。

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

生成器表达式

一些简单的生成器可以使用与列表解析类似的语法简洁地编码为表达式,但带括号而不是方括号。 这些表达式适用于通过封闭函数立即使用生成器的情况。 生成器表达式比完整的生成器定义更紧凑但功能更少,并且倾向于比等效的列表解析更具有内存友好性。

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




<div class="se-preview-section-delimiter"></div>
285
xvec = [10, 20, 30]
yvec = [7, 5, 3]
sum(x*y for x,y in zip(xvec, yvec))    # dot product




<div class="se-preview-section-delimiter"></div>
260
from math import pi, sin
sine_table = {x: sin(x*pi/180) for x in range(0, 91)}




<div class="se-preview-section-delimiter"></div>
data = 'golf'
list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

猜你喜欢

转载自blog.csdn.net/u010132497/article/details/80709883