python3学习 # 第九章 魔法方法、特性和迭代器--20230826

#!usr/bin/env python  
# -*- coding:utf-8 _*-
""" 
@author:AIVision
@file: lecture9.py 
@time: 2023/08/11 
"""
# 第九章 魔法方法、特性和迭代器

# 在Python中,有些名称很特别,开头和结尾都是两个下划线。你在本书前面已经见过一些,
# 如__future__。这样的拼写表示名称有特殊意义,因此绝不要在程序中创建这样的名称。在这样
# 的名称中,很大一部分都是魔法(特殊)方法的名称。如果你的对象实现了这些方法,它们将在
# 特定情况下(具体是哪种情况取决于方法的名称)被Python调用,而几乎不需要直接调用。
# 本章讨论几个重要的魔法方法,其中最重要的是__init__以及一些处理元素访问的方法(它
# 们让你能够创建序列或映射)。本章还将讨论两个相关的主题:特性(property)和迭代器(iterator)。
# 前者以前是通过魔法方法处理的,但现在通过函数property处理,而后者使用魔法方法__iter__,
# 这让其可用于for循环中。在本章最后,将通过一个内容丰富的示例演示如何使用已有知识来解
# 决非常棘手的问题。

# 9.1 如果你使用的不是 Python 3
# 在Python 2.2中,Python对象的工作方式有了很大的变化。这种变化带来了多个方面的影响。
# 这些影响对Python编程新手来说大都不重要,但有一点需要注意:即便你使用的是较新的Python2
# 版本,有些功能(如特性和函数super)也不适用于旧式类。要让你的类是新式的,要么在模块
# 开头包含赋值语句__metaclass__ = type(这在第7章提到过),要么直接或间接地继承内置类
# object或其他新式类。请看下面两个类:
# class NewStyle(object):
#  more_code_here
# class OldStyle:
#  more_code_here
# 在这两个类中,NewStyle是一个新式类,而OldStyle是一个旧式类。如果文件开头包含赋值
# 语句__metaclass__ = type,这两个类都将是新式类。

# 注意 也可在类的作用域内给变量__metaclass__赋值,但这样做只设置当前类的元类
# (metaclass)。元类是其他类所属的类,这是一个非常复杂的主题。

# 在本书中,我并没有在所有示例中都显式地设置元类或继承object。然而,如果你的程序
# 无需与旧版Python兼容,建议将所有类都定义为新式类,并使用将在9.2.3节介绍的函数super等功能。
# 请注意,在Python 3中没有旧式类,因此无需显式地继承object或将__metaclass__设置为
# type。所有的类都将隐式地继承object。如果没有指定超类,将直接继承它,否则将间接地继承它。

# 9.2 构造函数
# 我们要介绍的第一个魔法方法是构造函数。你可能从未听说过构造函数(constructor),它其
# 实就是本书前面一些示例中使用的初始化方法,只是命名为__init__。然而,构造函数不同于普
# 通方法的地方在于,将在对象创建后自动调用它们。因此,无需采用本书前面一直使用的做法:

# >>> f = FooBar()
# >>> f.init()
# 构造函数让你只需像下面这样做:
# >>> f = FooBar()

# 在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魔法方法中,__init__绝对是你用得最多的。

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

# 9.2.1 重写普通方法和特殊的构造函数
# 第7章介绍了继承。每个类都有一个或多个超类,并从它们那里继承行为。对类B的实例调用
# 方法(或访问其属性)时,如果找不到该方法(或属性),将在其超类A中查找。请看下面两个类:

class A:
 def hello(self):
    print("Hello, I'm A.")
class B(A):
 pass

# 类A定义了一个名为hello的方法,并被类B继承。下面的示例演示了这些类是如何工作的:
# >>> a = A()
# >>> b = B()
# >>> a.hello()
# Hello, I'm A.
# >>> b.hello()
# Hello, I'm A.

# 由于类B自己没有定义方法hello,因此对其调用方法hello时,打印的是消息"Hello, I'm A."。
# 要在子类中添加功能,一种基本方式是添加方法。然而,你可能想重写超类的某些方法,以
# 定制继承而来的行为。例如,B可以重写方法hello,如下述修改后的类B定义所示:

class B(A):
    def hello(self):
        print("Hello, I'm B.")
# 这样修改定义后,b.hello()的结果将不同。
# >>> b = B()
# >>> b.hello()
# Hello, I'm B.

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

# 请看下面的Bird类:
# 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()
# print(b.eat())
# print(b.eat())

# 这个类定义了所有鸟都具备的一种基本能力:进食。下面的示例演示了如何使用这个类:
# >>> b = Bird()
# >>> b.eat()
# Aaaah ...
# >>> b.eat()
# No, thanks!


# 从这个示例可知,鸟进食后就不再饥饿。下面来看子类SongBird,它新增了鸣叫功能。

class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print("Hungry! Eat!!!")
            self.hungry = False
        else:
            print('No!Thanks!!!')

# 继承Bird的属性--eat
b = Bird()
print(b.eat())
print(b.eat())
print("*" * 60)

# 继承Bird,SongBird是Bird的子类
class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)

# 继承SongBird的属性--sing
sb = SongBird()
print(sb.sing())
print("*" * 60)
# 异常 AttributeError: 'SongBird' object has no attribute 'hungry'
# print(sb.eat())


# SongBird是Bird的子类,继承了方法eat,但如果你尝试调用它,将发现一个问题。
# >>> 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。接下来的两节将介
# 绍这两种方法。

# 9.2.2 调用未关联的超类构造函数
# 本节介绍的方法主要用于解决历史遗留问题。在较新的Python版本中,显然应使用函数
# super(这将在下一节讨论)。然而,很多既有代码使用的都是本节介绍的方法,因此你必须对
# 其有所了解。另外,这种方法也极具启迪意义,淋漓尽致地说明了关联方法和未关联方法之间的差别。

# 言归正传。如果你觉得本节的标题有点吓人,请放松心情。调用超类的构造函数实际上很容
# 易,也很有用。下面先给出前一节末尾问题的解决方案。

class SongBird(Bird):
    def __init__(self):
        Bird.__init__(self) # 添加这句可继承Bird()的属性--eat
        self.sound = 'Squawk!'
    def sing(self):
        print(self.sound)

sb = SongBird()
print(sb.sing())
print(sb.eat())
print(sb.eat())
print("*" * 60)

# 在SongBird类中,只添加了一行,其中包含代码Bird.__init__(self)。先来证明这确实管用,
# 再解释这到底意味着什么。
# >>> sb = SongBird()
# >>> sb.sing()
# Squawk!
# >>> sb.eat()
# Aaaah ...
# >>> sb.eat()
# No, thanks!

# 这样做为何管用呢?对实例调用方法时,方法的参数self将自动关联到实例(称为关联的方
# 法),这样的示例你见过多个。然而,如果你通过类调用方法(如Bird.__init__),就没有实例
# 与其相关联。在这种情况下,你可随便设置参数self。这样的方法称为未关联的。这就对本节的
# 标题做出了解释。
# 通过将这个未关联方法的self参数设置为当前实例,将使用超类的构造函数来初始化
# SongBird对象。这意味着将设置其属性hungry。

# 9.2.3 使用函数 super
# 如果你使用的不是旧版Python,就应使用函数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('Hungry! Go To Eat!!!')
            self.hungry = False
        else:
            print('Finish! Thanks!')

class SoundBird(Bird):
    def __init__(self):
        super().__init__()  # 使用函数super继承超类Bird()中的eat属性
        self.sound = 'Squawk'
    def sing(self):
        print(self.sound)

print("*" * 70)
print("继承super方法")
print("*" * 70)
soundbird = SoundBird()
print(soundbird.sing())
print("*" * 70)
print(soundbird.eat())
print("*" * 70)
print(soundbird.eat())
print("*" * 70)

# 使用函数super有何优点
# 在我看来,相比于直接对超类调用未关联方法,使用函数super更直观,但这并非其唯一
# 的优点。实际上,函数super很聪明,因此即便有多个超类,也只需调用函数super一次(条件
# 是所有超类的构造函数也使用函数super)。另外,对于使用旧式类时处理起来很棘手的问题
# (如两个超类从同一个类派生而来),在使用新式类和函数super时将自动得到处理。你无需知
# 道函数super的内部工作原理,但必须知道的是,使用函数super比调用超类的未关联构造函
# 数(或其他方法)要好得多。
# 函数super返回的到底是什么呢?通常,你无需关心这个问题,只管假定它返回你所需的
# 超类即可。实际上,它返回的是一个super对象,这个对象将负责为你执行方法解析。当你访
# 问它的属性时,它将在所有的超类(以及超类的超类,等等)中查找,直到找到指定的属性或
# 引发AttributeError异常。

# 9.3 元素访问
# 虽然__init__无疑是你目前遇到的最重要的特殊方法,但还有不少其他的特殊方法,让你能
# 够完成很多很酷的任务。本节将介绍一组很有用的魔法方法,让你能够创建行为类似于序列或映射的对象。
# 基本的序列和映射协议非常简单,但要实现序列和映射的所有功能,需要实现很多魔法方法。
# 所幸有一些捷径可走,我马上就会介绍。

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


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

# 要了解更复杂的接口和使用的抽象基类(Sequence),请参阅有关模块collections的文档。
# 下面来试一试,看看能否创建一个无穷序列。

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__:
# >>> del s[4]
# Traceback (most recent call last):
#  File "<stdin>", line 1, in ?
# AttributeError: ArithmeticSequence instance has no attribute '__delitem__'

# 另外,这个类没有方法__len__,因为其长度是无穷的。
# 如果所使用索引的类型非法,将引发TypeError异常;如果索引的类型正确,但不在允许的
# 范围内(即为负数),将引发IndexError异常。

# >>> s["four"]
# Traceback (most recent call last):
#  File "<stdin>", line 1, in ?
#  File "arithseq.py", line 31, in __getitem__
#  check_index(key)
#  File "arithseq.py", line 10, in checkIndex
# if not isinstance(key, int): raise TypeError
# TypeError
# >>> s[-42]
# Traceback (most recent call last):
#  File "<stdin>", line 1, in ?
#  File "arithseq.py", line 31, in __getitem__
#  check_index(key)
#  File "arithseq.py", line 11, in checkIndex
#  if key < 0: raise IndexError
# IndexError
# 索引检查是由我为此编写的辅助函数check_index负责的。

# 9.3.2 从 list、dict 和 str 派生
# 基本的序列/映射协议指定的4个方法能够让你走很远,但序列还有很多其他有用的魔法方法
# 和普通方法,其中包括将在9.6节介绍的方法__iter__。要实现所有这些方法,不仅工作量大,而
# 且难度不小。如果只想定制某种操作的行为,就没有理由去重新实现其他所有方法。这就是程序员的懒惰(也是常识)。

# 那么该如何做呢?“咒语”就是继承。在能够继承的情况下为何去重新实现呢?在标准库中,
# 模块collections提供了抽象和具体的基类,但你也可以继承内置类型。因此,如果要实现一种
# 行为类似于内置列表的序列类型,可直接继承list。

# 来看一个简单的示例——一个带访问计数器的列表。
class CounterList(list):
    def __init__(self, *args):
        super().__init__(*args) # 使用super来调用超类(list)的属性方法
        self.counter = 0        # 初始化属性counter
    def __getitem__(self, index):
        self.counter += 1       # 更新属性counter
        return super(CounterList, self).__getitem__(index)

# CounterList类深深地依赖于其超类(list)的行为。CounterList没有重写的方法(如
# append、extend、index等)都可直接使用。在两个被重写的方法中,使用super来调用超类的
# 相应方法,并添加了必要的行为:初始化属性counter(在__init__中)和更新属性counter(在__getitem__中)。
# 注意 重写__getitem__并不能保证一定会捕捉用户的访问操作,因为还有其他访问列表内容的方式,如通过方法pop。

# 下面的示例演示了CounterList的可能用法:
# >>> 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

# 9.4 其他魔法方法
# 特殊(魔法)名称的用途很多,前面展示的只是冰山一角。魔法方法大多是为非常高级的用
# 途准备的,因此这里不详细介绍。然而,如果你感兴趣,可以模拟数字,让对象像函数一样被调
# 用,影响对象的比较方式,等等。要更详细地了解有哪些魔法方法,可参阅“Python Reference
# Manual”的Special method names一节。

# 9.5 特性
# 第7章提到了存取方法,它们是名称类似于getHeight和setHeight的方法,用于获取或设置属
# 性(这些属性可能是私有的,详情请参阅7.2.4节)。如果访问给定属性时必须采取特定的措施,
# 那么像这样封装状态变量(属性)很重要。例如,请看下面的Rectangle类:

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

# 下面的示例演示了如何使用这个类:
# >>> r = Rectangle()
# >>> r.width = 10
# >>> r.height = 5
# >>> r.get_size()
# (10, 5)
# >>> r.set_size((150, 100))
# >>> r.width
# 150

# get_size和set_size是假想属性size的存取方法,这个属性是一个由width和height组成的元
# 组。(可随便将这个属性替换为更有趣的属性,如矩形的面积或其对角线长度。)这些代码并非完
# 全错误,但存在缺陷。使用这个类时,程序员应无需关心它是如何实现的(封装)。如果有一天
# 你想修改实现,让size成为真正的属性,而width和height是动态计算出来的,就需要提供用于访
# 问width和height的存取方法,使用这个类的程序也必须重写。应让客户端代码(使用你所编写
# 代码的代码)能够以同样的方式对待所有的属性。

# 那么如何解决这个问题呢?给所有的属性都提供存取方法吗?这当然并非不可能,但如果有
# 大量简单的属性,这样做就不现实(而且有点傻),因为将需要编写大量这样的存取方法,除了
# 获取或设置属性外什么都不做。这将引入复制并粘贴(重复代码)的坏味,显然很糟糕(虽然在
# 有些语言中,这样的问题很常见)。所幸Python能够替你隐藏存取方法,让所有的属性看起来都
# 一样。通过存取方法定义的属性通常称为特性(property)。

# 在Python中,实际上有两种创建特定的机制,我将重点介绍较新的那种——函数property,
# 它只能用于新式类。随后,我将简单说明如何使用魔法方法来实现特性。

# 9.5.1 函数 property
# 函数property使用起来很简单。如果你编写了一个类,如前一节的Rectangle类,只需再添加一行代码。


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
    # 添加 函数 property
    size = property(get_size, set_size)

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

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

# case1
# class CaulateRectangle:
#     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 = CaulateRectangle()
# print("property 属性")
# print("*" * 80)
# r.height = 10
# r.width = 20
# print(r.size)
# print("*" * 80)


# 如你所见,属性size依然受制于get_size和set_size执行的计算,但看起来就像普通属性一样。
# 注意 如果特性的行为怪异,务必确保你使用的是新式类(通过直接或间接地继承object或直
# 接设置__metaclass__)。不然,特性的获取方法依然正常,但设置方法可能不正常(是否
# 如此取决于使用的Python版本)。这可能有点令人迷惑。

# 实际上,调用函数property时,还可不指定参数、指定一个参数、指定三个参数或指定四
# 个参数。如果没有指定任何参数,创建的特性将既不可读也不可写。如果只指定一个参数(获
# 取方法),创建的特性将是只读的。第三个参数是可选的,指定用于删除属性的方法(这个方
# 法不接受任何参数)。第四个参数也是可选的,指定一个文档字符串。这些参数分别名为fget、
# fset、fdel和doc。如果你要创建一个只可写且带文档字符串的特性,可使用它们作为关键字参
# 数来实现。
# 本节虽然很短(旨在说明函数property很简单),却非常重要。这里要说明的是,对于新式
# 类,应使用特性而不是存取方法。

# 函数property的工作原理
# 你可能很好奇,想知道特性是如何完成其魔法的,下面就来说一说。如果你对此不感兴
# 趣,可跳过这些内容。
# property其实并不是函数,而是一个类。它的实例包含一些魔法方法,而所有的魔法都
# 是由这些方法完成的。这些魔法方法为__get__、__set__和__delete__,它们一道定义了所谓
# 的描述符协议。只要对象实现了这些方法中的任何一个,它就是一个描述符。描述符的独特
# 之处在于其访问方式。例如,读取属性(具体来说,是在实例中访问类中定义的属性)时,如
# 果它关联的是一个实现了__get__的对象,将不会返回这个对象,而是调用方法__get__并将
# 其结果返回。实际上,这是隐藏在特性、关联的方法、静态方法和类方法(详细信息请参阅下
# 一小节)以及super后面的机制。
# 有关描述符的详细信息,请参阅Descriptor HowTo Guide(https://docs.python.org/3/howto/descriptor.html)。

# 9.5.2 静态方法和类方法
# 讨论旧的特性实现方式之前,先来说说另外两种实现方式类似于新式特性的功能。静态方法
# 和类方法是这样创建的:将它们分别包装在staticmethod和classmethod类的对象中。静态方法的
# 定义中没有参数self,可直接通过类来调用。类方法的定义中包含类似于self的参数,通常被命
# 名为cls。对于类方法,也可通过对象直接调用,但参数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)

# 像这样手工包装和替换方法有点繁琐。在Python 2.4中,引入了一种名为装饰器的新语法,
# 可用于像这样包装方法。(实际上,装饰器可用于包装任何可调用的对象,并且可用于方法和函
# 数。)可指定一个或多个装饰器,为此可在方法(或函数)前面使用运算符@列出这些装饰器(指
# 定了多个装饰器时,应用的顺序与列出的顺序相反)。

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'>

# 在Python中,静态方法和类方法以前一直都不太重要,主要是因为从某种程度上说,总是可
# 以使用函数或关联的方法替代它们,而且早期的Python版本并不支持它们。因此,虽然较新的代
# 码没有大量使用它们,但它们确实有用武之地(如工厂函数),因此你或许应该考虑使用它们。

# 注意 实际上,装饰器语法也可用于特性,详情请参阅有关函数property的文档。

# 9.5.3 __getattr__、__setattr__等方法
# 可以拦截对对象属性的所有访问企图,其用途之一是在旧式类中实现特性(在旧式类中,函
# 数property的行为可能不符合预期)。要在属性被访问时执行一段代码,必须使用一些魔法方法。
# 下面的四个魔法方法提供了你需要的所有功能(在旧式类中,只需使用后面三个)。
#  __getattribute__(self, name):在属性被访问时自动调用(只适用于新式类)。
#  __getattr__(self, name):在属性被访问而对象没有这样的属性时自动调用。
#  __setattr__(self, name, value):试图给属性赋值时自动调用。
#  __delattr__(self, name):试图删除属性时自动调用。
# 相比函数property,这些魔法方法使用起来要棘手些(从某种程度上说,效率也更低),但
# 它们很有用,因为你可在这些方法中编写处理多个特性的代码。然而,在可能的情况下,还是使
# 用函数property吧。

# 再来看前面的Rectangle示例,但这里使用的是魔法方法:
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,就执行与以前一样的操作;否则就使用魔法属性__dict__。
# __dict__属性是一个字典,其中包含所有的实例属性。之所以使用它而不是执行常规属性
# 赋值,是因为旨在避免再次调用__setattr__,进而导致无限循环。
#  仅当没有找到指定的属性时,才会调用方法__getattr__。这意味着如果指定的名称不是
# size,这个方法将引发AttributeError异常。这在要让类能够正确地支持hasattr和getattr
# 等内置函数时很重要。如果指定的名称为size,就使用前一个实现中的表达式。

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

# 9.6 迭代器
# 本书前面粗略地提及了迭代器(和可迭代对象),本节将更详细地介绍。对于魔法方法,这
# 里只介绍__iter__,它是迭代器协议的基础。

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

# 注意 在Python 3中,迭代器协议有细微的变化。在以前的迭代器协议中,要求迭代器对象包含方法next而不是__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()
# 然后就可在for循环中使用这个对象,如找出第一个大于1000的斐波那契数。
# >>> for f in fibs:
# ... if f > 1000:
# ... print(f)
# ... break
# ...
# 1597
# 这个循环之所以会停止,是因为其中包含break语句;否则,这个for循环将没完没了地执行。

# 提示 通过对可迭代对象调用内置函数iter,可获得一个迭代器。
# >>> it = iter([1, 2, 3])
# >>> next(it)
# 1
# >>> next(it)
# 2
# 还可使用它从函数或其他可调用对象创建可迭代对象,详情请参阅库参考手册。

# 9.6.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]

# 9.7 生成器
# 生成器是一个相对较新的Python概念。由于历史原因,它也被称为简单生成器(simple
# generator)。生成器和迭代器可能是近年来引入的最强大的功能,但生成器是一个相当复杂的概
# 念,你可能需要花些功夫才能明白其工作原理和用途。虽然生成器让你能够编写出非常优雅的代
# 码,但请放心,无论编写什么程序,都完全可以不使用生成器。
# 生成器是一种使用普通函数语法定义的迭代器。生成器的工作原理到底是什么呢?通过示例
# 来说明最合适。下面先来看看如何创建和使用生成器,然后再看看幕后的情况。

# 9.7.1 创建生成器
# 生成器创建起来与函数一样简单。你现在肯定厌烦了老套的斐波那契数列,所以下面换换口
# 味,创建一个将嵌套列表展开的函数。这个函数将一个类似于下面的列表作为参数:
# 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)
# ...
# 1
# 2
# 3
# 4
# 5
# 或
# >>> list(flatten(nested))
# [1, 2, 3, 4, 5]

# 简单生成器
# 在Python 2.4中,引入了一个类似于列表推导(参见第5章)的概念:生成器推导(也叫生
# 成器表达式)。其工作原理与列表推导相同,但不是创建一个列表(即不立即执行循环),而
# 是返回一个生成器,让你能够逐步执行计算。
# >>> g = ((i + 2) ** 2 for i in range(2, 27))
# >>> next(g)
# 16
# 如你所见,不同于列表推导,这里使用的是圆括号。在像这样的简单情形下,还不如使
# 用列表推导;但如果要包装可迭代对象(可能生成大量的值),使用列表推导将立即实例化一
# 个列表,从而丧失迭代的优势。
# 另一个好处是,直接在一对既有的圆括号内(如在函数调用中)使用生成器推导时,无需
# 再添加一对圆括号。换而言之,可编写下面这样非常漂亮的代码:
# sum(i ** 2 for i in range(10))

# 9.7.2 递归式生成器
# 前一节设计的生成器只能处理两层的嵌套列表,这是使用两个for循环来实现的。如果要处
# 理任意层嵌套的列表,该如何办呢?例如,你可能使用这样的列表来表示树结构(也可以使用特
# 定的树类,但策略是相同的)。对于每层嵌套,都需要一个for循环,但由于不知道有多少层嵌套,
# 你必须修改解决方案,使其更灵活。该求助于递归了。

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

# 如你所见,如果表达式nested + ''引发了TypeError异常,就忽略这种异常;如果没有引发
# TypeError异常,内部try语句中的else子句将引发TypeError异常,这样将在外部的excpet子句中
# 原封不动地生成类似于字符串的对象。明白了吗?
# 下面的示例表明,这个版本也可用于字符串:
# >>> list(flatten(['foo', ['bar', ['baz']]]))
# ['foo', 'bar', 'baz']
# 请注意,这里没有执行类型检查:我没有检查nested是否是字符串,而只是检查其行为是否
# 类似于字符串,即能否与字符串拼接。对于这种检查,一种更自然的替代方案是,使用isinstance
# 以及字符串和类似于字符串的对象的一些抽象超类,但遗憾的是没有这样的标准类。另外,即便
# 是对UserString来说,也无法检查其类型是否为str。

# 9.7.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>
# 对于生成器的函数返回的迭代器,可以像使用其他迭代器一样使用它。

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

# 9.7.5 模拟生成器
# 如果你使用的是较老的Python版本,就无法使用生成器。下面是一个简单的解决方案,让你
# 能够使用普通函数模拟生成器。
# 首先,在函数体开头插入如下一行代码:
# result = []
# 如果代码已使用名称result,应改用其他名称。(在任何情况下,使用更具描述性的名称都
# 是不错的主意。)接下来,将类似于yield some_expression的代码行替换为如下代码行:
# yield some_expression with this:
# result.append(some_expression)
# 最后,在函数末尾添加如下代码行:
# return result
# 尽管使用这种方法并不能模拟所有的生成器,但可模拟大部分生成器。例如,这无法模拟无
# 穷生成器,因为显然不能将这种生成器的值都存储到一个列表中。
# 下面使用普通函数重写了生成器flatten:
# def flatten(nested):
#  result = []
#  try:
#  # 不迭代类似于字符串的对象:
#  try: nested + ''
#  except TypeError: pass
#  else: raise TypeError
#  for sublist in nested:
#  for element in flatten(sublist):
#  result.append(element)
#  except TypeError:
#  result.append(nested)
#  return result

# 9.8 八皇后问题
# 学习各种魔法方法后,该付诸应用了。本节将演示如何使用生成器来解决一个经典的编程问题。

# 9.8.1 生成器的回溯
# 对于逐步得到结果的复杂递归算法,非常适合使用生成器来实现。要在不使用生成器的情况
# 下实现这些算法,通常必须通过额外的参数来传递部分结果,让递归调用能够接着往下计算。通
# 过使用生成器,所有的递归调用都只需生成其负责部分的结果。前面的递归版flatten就是这样
# 做的,你可使用这种策略来遍历图结构和树结构。
# 然而,在有些应用程序中,你不能马上得到答案。你必须尝试多次,且在每个递归层级中都
# 如此。打个现实生活中的比方吧,假设你要去参加一个很重要的会议。你不知道会议在哪里召开,
# 但前面有两扇门,而会议室就在其中一扇门的后面。你选择进入左边那扇门后,又看到两扇门。
# 你再次选择进入左边那扇门,但发现走错了。因此你往回走,并进入右边那扇门,但发现也走错
# 了。因此你继续往回走到起点,现在可以尝试进入右边那扇门。

# 如果你以前从未听说过图和树,应尽快学习,因为它们是编程和计算机科学中非常重要
# 的概念。要深入了解图和树,可参阅计算机科学、离散数学、数据结构或算法方面的图书。
# 下面的网页提供了有关图和树的简明定义:
#  http://mathworld.wolfram.com/Graph.html
#  http://mathworld.wolfram.com/Tree.html
#  www.nist.gov/dads/HTML/tree.html
#  www.nist.gov/dads/HTML/graph.html
# 通过在网上搜索或浏览维基百科(http://wikipedia.org),可找到大量有关这些主题的资料。

# 对于需要尝试所有组合直到找到答案的问题,这种回溯策略对其解决很有帮助。这种问题的
# 解决方案类似于下面这样:
# # 伪代码
# for each possibility at level 1:
#  for each possibility at level 2:
#  ...
#  for each possibility at level n:
#  is it viable?
# 要直接使用for循环来实现,必须知道有多少层。如果无法知道,可使用递归。

# 9.8.2 问题
# 这是一个深受大家喜爱的计算机科学谜题:你需要将8个皇后放在棋盘上,条件是任何一个
# 皇后都不能威胁其他皇后,即任何两个皇后都不能吃掉对方。怎样才能做到这一点呢?应将这些
# 皇后放在什么地方呢?
# 这是一个典型的回溯问题:在棋盘的第一行尝试为第一个皇后选择一个位置,再在第二行尝
# 试为第二个皇后选择一个位置,依次类推。在发现无法为一个皇后选择合适的位置后,回溯到前
# 一个皇后,并尝试为它选择另一个位置。最后,要么尝试完所有的可能性,要么找到了答案。
# 在前面描述的问题中,只有8个皇后,但这里假设可以有任意数量的皇后,从而更像现实世
# 界的回溯问题。如何解决这个问题呢?如果你想自己试一试,就不要再往下读了,因为马上就会提供解决方案。

# 9.8.3 状态表示
# 可简单地使用元组(或列表)来表示可能的解(或其一部分),其中每个元素表示相应行中
# 皇后所在的位置(即列)。因此,如果state[0] == 3,就说明第1行的皇后放在第4列(还记得吧,
# 我们从0开始计数)。在特定的递归层级(特定的行),你只知道上面各皇后的位置,因此状态元
# 组的长度小于8(即皇后总数)。

# 注意 完全可以使用列表(而不是元组)来表示状态,具体使用哪个完全取决于你的喜好。一
# 般而言,如果序列较小且是静态的,使用元组可能是不错的选择。

# 9.8.4 检测冲突
# 先来做些简单的抽象。要找出没有冲突(即任何一个皇后都吃不到其他皇后)的位置组合,
# 首先必须定义冲突是什么。为何不使用一个函数来定义呢?
# 函数conflict接受(用状态元组表示的)既有皇后的位置,并确定下一个皇后的位置是否会
# 导致冲突。
# def conflict(state, nextX):
#  nextY = len(state)
#  for i in range(nextY):
#  if abs(state[i] - nextX) in (0, nextY - i):
#  return True
#  return False
# 参数nextX表示下一个皇后的水平位置(x坐标,即列),而nextY为下一个皇后的垂直位置(y
# 坐标,即行)。这个函数对既有的每个皇后执行简单的检查:如果下一个皇后与当前皇后的x坐标
# 相同或在同一条对角线上,将发生冲突,因此返回True;如果没有发生冲突,就返回False。比
# 较难理解的是下面的表达式:
# abs(state[i] - nextX) in (0, nextY - i)
# 如果下一个皇后和当前皇后的水平距离为0(在同一列)或与它们的垂直距离相等(位于一
# 条对角线上),这个表达式就为真;否则为假。

# 9.8.5 基线条件
# 八皇后问题解决起来有点棘手,但通过使用生成器并不太难。然而,如果你不熟悉递归,就
# 很难自己想出这里的解决方案。另外,这个解决方案的效率不是特别高,因此皇后非常多时,其
# 速度可能有点慢。
# 下面先来看基线条件:最后一个皇后。对于这个皇后,你想如何处理呢?假设你想找出所有
# 可能的解——给定其他皇后的位置,可将这个皇后放在什么位置(可能什么位置都不行)?可以
# 这样编写代码。
def queens(num, state):
    if len(state) == num-1:
        for pos in range(num):
            if not conflict(state, pos):
                yield pos

# 这段代码的意思是,如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那
# 些不会引发冲突的位置。参数num为皇后总数,而参数state是一个元组,包含已放好的皇后的位
# 置。例如,假设总共有4个皇后,而前3个皇后的位置分别为1、3和0,如图9-1所示。(现在不用
# 关心白色的皇后。)

# 从该图可知,每个皇后都占据一行,而皇后的位置是从0开始编号的(Python中通常如此)。
# >>> list(queens(4, (1, 3, 0)))
# [2]
# 代码的效果很好。这里使用list旨在让生成器生成所有的值。在这个示例中,只有一个位置
# 符合条件。在图9-1中,在这个位置放置了一个白色皇后。(请注意,颜色没有什么特殊含义,不
# 是程序的一部分。)

# 9.8.6 递归条件
# 现在来看看这个解决方案的递归部分。处理好基线条件后,可在递归条件中假设来自更低层
# 级(编号更大的皇后)的结果都是正确的。因此,只需在函数queens的前述实现中给if语句添加
# 一个else子句。
# 你希望递归调用返回什么样的结果呢?你希望它返回当前行下面所有皇后的位置,对吧?假
# 设位置是以元组的方式返回的,因此需要修改基线条件,使其返回一个(长度为1的)元组,但
# 这将在后面处理。
# 因此,对于递归调用,向它提供的是由当前行上面的皇后位置组成的元组。对于当前皇后的
# 每个合法位置,递归调用返回的是由下面的皇后位置组成的元组。为了让这个过程不断进行下去,
# 只需将当前皇后的位置插入返回的结果开头,如下所示:
...
else:
    for pos in range(num):
        if not conflict(state, pos):
            for result in queens(num, state + (pos,)):
                yield (pos,) + result

# 这里的for pos和if not conflict部分与前面相同,因此可以稍微简化一下代码。另外,还可给参数指定默认值。
def queens(num=8, state=()):
    for pos in range(num):
        if not conflict(state, pos):
            if len(state) == num-1:
                yield (pos,)
        else:
            for result in queens(num, state + (pos,)):
                yield (pos,) + result
# 如果你觉得这些代码难以理解,用自己的话来描述其作用可能会有所帮助。另外,你可能还
# 记得(pos,)中的逗号必不可少(不能仅用圆括号将pos括起),这样得到的才是元组。
# 生成器queens提供了所有的解(即所有合法的皇后位置组合)。

# >>> list(queens(3))
# []
# >>> list(queens(4))
# [(1, 3, 0, 2), (2, 0, 3, 1)]
# >>> for solution in queens(8):
# ... print solution
# ...
# (0, 4, 7, 5, 2, 6, 1, 3)
# (0, 5, 7, 2, 6, 3, 1, 4)
# ...
# (7, 2, 0, 5, 1, 4, 6, 3)
# (7, 3, 0, 2, 5, 1, 6, 4)
# >>>
# 如果运行queens时将参数num设置为8,将快速显示大量的解。下面看看有多少个解。
# >>> len(list(queens(8)))
# 92

# 9.8.7 扫尾工作
# 结束本节之前,可以让输出更容易理解些。在任何情况下,清晰的输出都是好事,因为这让
# 查找bug等工作更容易。
# def prettyprint(solution):
#  def line(pos, length=len(solution)):
#  return '. ' * (pos) + 'X ' + '. ' * (length-pos-1)
#  for pos in solution:
#  print(line(pos))
# 请注意,我在prettyprint中创建了一个简单的辅助函数。之所以将它放在prettyprint中,
# 是因为我认为在其他地方都用不到它。下面随机地选择一个解,并将其打印出来,以确定它是正
# 确的。
# >>> import random
# >>> prettyprint(random.choice(list(queens(8))))
# . . . . . X . .
# . X . . . . . .
# . . . . . . X .
# X . . . . . . .
# . . . X . . . .
# . . . . . . . X
# . . . . X . . .

# 9.9 小结
# 本章介绍的内容很多,下面来总结一下。
#  新式类和旧式类:Python类的工作方式在不断变化。较新的Python 2版本有两种类,其中
# 旧式类正在快速退出舞台。新式类是Python 2.2引入的,提供了一些额外的功能,如支持
# 函数super和property,而旧式类不支持。要创建新式类,必须直接或间接地继承object
# 或设置__metaclass__。
#  魔法方法:Python中有很多特殊方法,其名称以两个下划线开头和结尾。这些方法的功能
# 各不相同,但大都由Python在特定情况下自动调用。例如__init__是在对象创建后调用的。
#  构造函数:很多面向对象语言中都有构造函数,对于你自己编写的每个类,都可能需要
# 为它实现一个构造函数。构造函数名为__init__,在对象创建后被自动调用。
#  重写:类可重写其超类中定义的方法(以及其他任何属性),为此只需实现这些方法即可。
# 要调用被重写的版本,可直接通过超类调用未关联版本(旧式类),也可使用函数super
# 来调用(新式类)。
#  序列和映射:要创建自定义的序列或映射,必须实现序列和映射协议指定的所有方法,
# 其中包括__getitem__和__setitem__等魔法方法。通过从list(或UserList)和dict(或
# UserDict)派生,可减少很多工作量。
#  迭代器:简单地说,迭代器是包含方法__next__的对象,可用于迭代一组值。没有更多的
# 值可供迭代时,方法__next__应引发StopIteration异常。可迭代对象包含方法__iter__,
# 它返回一个像序列一样可用于for循环中的迭代器。通常,迭代器也是可迭代的,即包含
# 返回迭代器本身的方法__iter__。
#  生成器:生成器的函数是包含关键字yield的函数,它在被调用时返回一个生成器,即一
# 种特殊的迭代器。要与活动的生成器交互,可使用方法send、throw和close。
#  八皇后问题:八皇后问题是个著名的计算机科学问题,使用生成器可轻松地解决它。这
# 个问题要求在棋盘上放置8个皇后,并确保任何两个皇后都不能相互攻击。

# 9.9.1 本章介绍的新函数
# 函 数                                  描 述
# iter(obj)                             从可迭代对象创建一个迭代器
# next(it)                              让迭代器前进一步并返回下一个元素
# property(fget, fset, fdel, doc)       返回一个特性;所有参数都是可选的
# super(class, obj)                     返回一个超类的关联实例

# 调用iter和super时,还可提供这里没有列出的其他参数,更详细的信息请参阅标准Python文档。






























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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_44119674

觉得有帮助,鼓励下吧

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

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

打赏作者

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

抵扣说明:

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

余额充值