【Python 基础教程】 第9章 魔法方法、特性和迭代器

             *     ,MMM8&&&.            *
                  MMMM88&&&&&    .
                 MMMM88&&&&&&&
     *           MMM88&&&&&&&&
                 MMM88&&&&&&&&
                 'MMM88&&&&&&'
                   'MMM8&&&'      *    
          |\___/|     /\___/\
          )     (     )    ~( .              '
         =\     /=   =\~    /=
           )===(       ) ~ (
          /     \     /     \
          |     |     ) ~   (
         /       \   /     ~ \
         \       /   \~     ~/
  jy__/\_/\__  _/_/\_/\__~__/_/\_/\_/\_/\__ww
  |  |  |  |( (  |  |  | ))  |  |  |  |  |  |
  |  |  |  | ) ) |  |  |//|  |  |  |  |  |  |
  |  |  |  |(_(  |  |  (( |  |  |  |  |  |  |     
  |  |  |  |  |  |  |  |\)|  |  |  |  |  |  |
  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |


魔法方法… 火球术?召唤术?


9.1 Python3 中都是新类


9.2 构造函数

构造函数(constructor)——第一个魔法方法。在前面的方法中,我们用到了初始化方法

>>> f = Class()
>>> f.init()

构造函数只需要像下面这样做:

>>> f = Class()

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

class Class:
    def __init__(self):
        self.name = 'Wei Wu'

>>> c = Class()
>>> c.name
'Wei Wu'

为构造函数添加一个参数

class Class:
    def __init__(self,honey = 'Jing Yu'):
        self.name = honey

>>> c = Class()
>>> c.name
'Jing Yu'

>>> c = Class('Ling Yu')
>>> c.name
'Ling Yu'

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

7章介绍了继承。每个类都有一个或多个超类,并从他们那里继承行为,对类B的实例调用方法时,如果找不到该方法,将在其超类A中查找(访问属性同理),如下:

class A:
    def hello(self):
        print("Hello,小鲸鱼!")

class B(A):
    pass

# 类A定义了一个名叫 hello 的方法,并被B继承
>>> a = A()
>>> b = B()
>>> a.hello()
Hello,小鲸鱼!

>>> b.hello()
Hello,小鲸鱼!

在子类中添加功能,一种基本方式是添加方法。

# 重写B
class B(a):
    def hello(self):
        print("Hello,我是无尾")

>>> b = B()
>>> b.hello()
Hello,我是无尾

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

# 定义一个鸟类,所有鸟【雏鸟】都具备一项基本能力:饿了进食
class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaaa,吃奶奶')
            self.hungry = False
        else:
            print('叽叽喳喳')

>>> b = Bird()
>>> b.eat()
Aaaaa,吃奶奶

>>> b.eat()
叽叽喳喳

我们发现,小鸟进过食就不饥饿了。下面来看子类 SongBird,它新增了鸣叫功能(叽叽喳喳!)

扫描二维码关注公众号,回复: 1469922 查看本文章
class SongBird(Bird):
    def __init__(self):
        self.sound = 'Squawk!'
    def sing(self):
        pring(self.sound)

>>> sb = SOngBird()
>>> sb.sing() #你个小傻逼!叫叫叫叫个P!
Squawk!

SongBird 是 Bird 的子类,因此也继承了方法 eat,但是在尝试调用时,会报错:

>>> sb.eat()

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-90-05f67b3cf162> in <module>()
----> 1 sb.eat()

<ipython-input-83-9b686e13f983> in eat(self)
      3                 self.hungry = True
      4         def eat(self):
----> 5                 if self.hungry:
      6                         print('Aaaaa,吃奶奶')
      7                         self.hungry = False

AttributeError: 'SongBird' object has no attribute 'hungry'

错误提示,在 SongBird 类中未定义 ‘hungry‘,没有属性‘hungry‘。这些因为在 SongBird的构造函数中没有包含任何舒适化熟悉行 ‘hungry’‘ 的代码。下面讲解方法1——如何调用超类构造函数以及方法2——使用函数 super。


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

这个方法主要是用于解决历史遗留问题。新版本的Python 中应使用函数 super。

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()
Aaaaa,吃奶奶

>>>sb.eat()
叽叽喳喳

对实例调用方法时,方法的参数 self 将自动关联到实例(称为关联的方法)。然而,通过类调用方法(Bird.init),没有实例与之(Bird.init)关联。这样的方法称为未关联的。
通过这个未关联的方法的 self 参数设置为当前实例,将使用超类的构造函数来初始化SongBird对象。这意味着将设置其属性 hungry。


9.2.3 使用函数 super

调用函数时,将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类的方法。

# 函数 super 返回的是一个 super 对象,这个对象负责为你执行方法解析。当你访问它的属性时,
# 他将在所有的超类(以及超类的超类,等等)中查找,知道找到指定的属性或引发 AttributeError 异常。
class Bird:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print('Aaaaa,吃奶奶')
            self.hungry = False
        else:
            print('叽叽喳喳')

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

9.3 元素访问

一组很有用的魔法方法,让你能够创建行为类似于序列或映射的对象。
基本的序列和映射协议非常简单,但要实现序列和映射的所有功能,需要实现很多魔法方法。

在Python中,协议通常指的是规范行为的准则。协议指定应实现哪些方法以及这些方法应该做什么。在Python中,多态仅仅基于对象的行为(而不是基于祖先,比如属于哪个类或者是超类),因此这个概念很重要:

其他的语言可能要求对象属于特定的类或实现了特定的接口,而python通常只要求对象遵循特定的协议。

因此,要成为序列,只需遵循序列协议即可


9.3.1 基本的序列和映射协议

序列和映射基本上是元素(item)的集合,要实现它们的基本行为(协议),不可变对象需要 2 个方法,而可变对象需要实现 4 个。

  • len(self):这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说为 key-value 对数。如果 len 返回零(且没有实现覆盖这种行为的 nonzero),对象在布尔上下文中将被视为 False(就像空的列表、元组、字符串和字典一样)。
  • getitem(self,ley):这个方法应返回与指定 索引(键)相关联的值。对序列来说,索引应该是 0~ n-1 的整数(也可是负数,稍后说),其中 n 为序列长度。对映射来说,可以是任何类型。
  • setitem(self,key,value):这个方法应以与 索引(键) 相关联的方式存储值,以便以后能够使用 getitem 来获取。当然,仅当对象可变时才需要实现这个方法。

    对于这些方法,还有一些额外的要求。

  • 对于序列,如果索引为负整数,应从末尾往前数。换而言之,x[-n] == x[len(x)-n]。

  • 如果索引的类型不合适(如对序列使用字符串索引),可能引发 TypeError 异常。
  • 对于序列,如果索引的类型是正确的,但不在允许的范围,应引发 IndexError 异常。

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

'''
实现功能如下:
    输入:序列的起始值、步长和元素索引
    输出:指定索引对应的值
    要求:输入非负、int整型
    ⚠️注意:这里所谓的序列,并不是存储在某个列表、某个地方、已存在的、占用了内存的序列,
           而是在特定规则(协议)下存在的序列;当输入索引,可以按照规则得到索引对应的元素值。
'''

def CheckIndex(index):
    '检查索引类型,负数和非整形都为异常'
    if not isinstance(index,int):
        raise TypeError # 类型不正确
    if index < 0:
        raise IndexError # 索引不存在

class ArithmeticSequence:
    '''
    构建序列协议,包括:定义序列规则、保存对序列值的局部更改
    '''
    def __init__(self, start= 0, step= 1):
        '初始化属性'
        self.start = start # 内部序列起始值(非通过规则生成的实际序列)
        self.step = step # 步长
        self.save_dic = {} # 用于存储用户对于序列中某一元素的更改结果

    def __getitem__(self, index):
        '定义序列规则,并将元素输出给用户'
        CheckIndex(index)

        # 返回用户的更改结果, 因为更改的结果与规则不服,所以为了不被“天道”发现,只能悄悄的藏在 save_dic 这个字典里
        try: 
            return self.save_dic[index]

        # 当神秘的小屋 save_dic 中没有藏匿 index 所对应的 value 时,由我们定义的序列规则生成 index 对应的序列元素
        except KeyError:
            return self.start + index * self.step

    def __setitem__(self, index, value):
        CheckIndex(index)

        # 发现一个被篡改的元素,赶快将这个 index 对应的元素藏进小黑屋不被天道发现
        # 当需要查找这个 index 的值时,从 save_dic 中直接调用,从而蒙蔽天道!!! cool!
        self.save_dic[index] = value
# 设置 start、step, 查询 index=4 对应的序列值
>>> a = ArithmeticSequence(1,2)
>>> a[4]
9

# 更改 index=4 的序列值为以下字符串,记录并保存在字典中
>>> a[4] = 'Wei Wu marke it!'
>>> a[4]
'Wei Wu mark it!'

# 查看序列前 10 个元素
>>> for i in range(10):
... print(a[i])
1
3
5
7
Wei Wu Marking!
11
13
15
17
19

这个类中,没有定义 dellen , 因此不能删除元素,且序列长度是无穷的


9.3.2 从 list、dict 和 str 派生

如果想定制某种操作的行为,可以继承内置类型。比如,实现一种行为类似与内置列表的序列类型,可直接继承。

class CounterList(list):

    def __init__(self,*args):
        super().__init__(*args)
        self.counter = 0

    def __getitem__(self, index):
        self.counter += 1
        return super().__getitem__(index)


>>> c = CounterList([1,2,3,4,5])
>>> c
[1,2,3,4,5]

>>> c.counter
0

>>> c[0] + c[1]
3

>>> c.counter
2

CounterList 类 深深的依赖于其超类 list 的行为。没有重写的方法(如 append、extend、index)等都可直接使用。


9.4 其它魔法方法


9.5 特性

class Rectangle:
    def __init__(self):
        self.widht = 0
        self.height = 0
    def set_size(self,size):
        self.width, self.height = size
    def get_size(slef):
        return self.width,self.height


----------
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.get_size()
(10,5)

>>> r.set_size((100,10))
>>> r.width
100

上面代码中, 属性 size 是由 width 和 height 两个属性组成的。如果想让 size 成为真正的属性。如下


9.5.1 函数 property

# 这里的 size 看起来就像普通属性一样了
class Rectangle:
    def __init__(self):
        self.widht = 0
        self.height = 0
    def set_size(self,size):
        self.width, self.height = size
    def get_size(slef):
        return self.width,self.height
    size = propertyj(get_size, set_size)


----------
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.size
(10,5)

>>> r.size = 100,10
>>> r.width
100

9.5.2 staticmethod and classmethod

class Num:
    #普通方法:能用Num调用而不能用实例化对象调用    
    def one():   
        print ('1')

    #实例方法:能用实例化对象调用而不能用Num调用
    def two(self):
        print ('2')

    #静态方法:能用Num和实例化对象调用
    @staticmethod  
    def three():   
        print ('3')

    #类方法:第一个参数cls长什么样不重要,都是指Num类本身,调用时将Num类作为对象隐式地传入方法    
    @classmethod  
    def go(cls):  
        cls.three()
# classmethod 例子

class Data_test():
    day = 0
    month = 0
    year = 0

    def __init__(self,year=0,month=0,day=0):
        self.year = year
        self.month = month
        self.day = day

    @classmethod
    def get_data(cls, string_date):
        year,month,day = map(int, string_date.split('-'))
        # 将 year month day 作为参数传入 Data_test 类中的,等同于 Data_test(year,month,day)
        return cls(year,month,day)

    def out_data(self):
        print(self.year,'.',self.month,'.',self.day)

另外引用知乎的一篇文章。
Python 中的 classmethod 和 staticmethod 有什么具体用途? – 李宝银


9.5.3 getattrsetattr 等方法

class Rectangle:
    def __init__(self):
        self.width = 0
        self.hight = 0

    # 发生更改时调用 __setattr__   
    def __setattr__(self,name,value):
        if name == 'size':
            self.width, self.height = value
        else:
            self.__dict__[name] = value

    # 获取元素时,先调用 __dict__ ,若没有对应的 name 的值,再调用 __getattr__
    def __getattr__(self, name):
        if name == 'size':
            return self.width,self.height
        else:
            raise AttributeError()


----------
>>> r = Rectangle()
>>> r.size = 100,10
>>> r.size
(100,10)

>>> r.name = 10,1
>>> r.name
(10,1)

9.6 迭代器

对于魔法方法,这里只介绍 iter,它是迭代器协议的基础


9.6.1 迭代器协议

可作用于next()函数的对象都是Iterator。具体的实现是,任何对象只要定义了iternext方法,那就是迭代器对象;迭代器表示一个惰性计算的序列,需要iter返回迭代器自身,next返回迭代器中的下一个值,迭代到结尾时引发 StopIteration 异常;也就是说迭代器在遍历集合时,并不是将所有的元素事先都准备好,而是迭代到某个元素时才去计算该元素,利用这一特性我们可以去遍历一些巨大的集合,之前总结的函数式编程中,map,reduce,filter函数返回的就是一个新的迭代器。

还有一点需要明确的,迭代器都是可迭代对象,可迭代对象可以通过iter()返回一个新的迭代器。

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 # 返回迭代器本身,保留所有属性


----------
# for 循环中,调用 __iter__ 获得迭代器对象,再调用 __next__ 获取元素,
# 迭代器内部状态保存在实例属性 a 和 b 中。
for f in Fibs():
    if f > 1000:
        print(f)
        break
1597
# 通过对可迭代对象调用内置函数 iter,也可获得一个迭代器
>>> it = iter([1,2,3])
>>> next(it)
1

>>> next(it)
2

9.6.2 从迭代器创建序列

使用构造函数 list 显式地将迭代器转换为列表

# 例子仍引用上面的迭代器例子
class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        # 不同之处是,这里对 a 的值进行限制
        if self.a > 100:
            raise StopIteration
        return self.a

    def __iter__(self):
        return self


----------
>>> f = Fibs()
>>> list(f)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

9.7 生成器


9.7.1 创建生成器

# 用生成器,比用迭代器实现斐波那契数列更简洁
def Fibs():
    prev, curr = 0,1
    while True:
        yield curr
        prev, curr = curr, prev + curr

for f in fib():
    if f < 20:
        print(i)
1
1
2
3
5
8
13


#也可以这样写,在函数中控制迭代次数
def Fibs(n):
    prev, curr = 0,1
    while n > 0:
        n -= 1
        yield curr
        prev, curr = curr, prev + curr
# 再举个例子
nested = [[1,2],[3,4],5]

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

>>> list(flatten(nested))
[1,2,3,4,5]
# 生成器推导
>>> g= (i ** 2 for i in range(10))
>>> for i in g:
...     print(i)

0
1
4
9
16
25
36
49
64
81

>>> next(g)
0

>>> next(g)
1

>>> next(g)
4

# 在sum函数内使用生成器推导
>>> sum(i**2 for i in range(10))
285

9.7.2 递归式生成器

对于多层嵌套问题

# 调用函数时,有两种可能性:基线条件和递归条件。在基线条件下,如果函数处理单个元素,将引发异常,因为不能迭代一个数。
def flatten(nested):
    try:
        for sunlist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError
        yield nested

>>> llist(flatten([[1,[2,11,23,15,45,[454,345,643,345]]],[3,4],[5]]))
[1, 2, 11, 23, 15, 45, 454, 345, 643, 345, 3, 4, 5]


# 如果处理单个元素,且没有处理异常:将报不可迭代的异常

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

>>> list(a([1]))

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-781526e214bb> in <module>()
----> 1 list(a([1]))

<ipython-input-51-2824f5858b7c> in a(nested)
      1 def a (nested):
      2     for sublist in nested:
----> 3         for element in a(sublist):
      4             yield element

<ipython-input-51-2824f5858b7c> in a(nested)
      1 def a (nested):
----> 2     for sublist in nested:
      3         for element in a(sublist):
      4             yield element

TypeError: 'int' object is not iterable

如果 nested 是字符串或是类似字符串的对象,它就属于序列,因此不会引发 TypeError异常,可你并不想对其进行迭代。比如如下:

>>> list(flatten('Wu wei jun'))
['W', 'u', ' ', 'w', 'e', 'i', ' ', 'j', 'u', 'n']

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

def flatten(nested):
    '''
    通过检查nested是否符合字符串行为来判断 nested 是不是字符串
    '''
    try:
        try:
            nested + '' # 如果是字符串,那么拼接行为不会报错
        except TypeError:
            pass # 如果是字符串,将引发异常TypeError,但是这里采取了跳过这个异常,执行下面的for循环
        else:
            raise TypeError # 是字符串,引发异常

        for sublist in nested:
            for element in flatten(sublist):
                yield element # 这里不会有实际输出
    except TypeError:
        yield nested

>>> list(flatten(['Wu wei',[1,2,[3,'nihao']]]))
['Wu wei', 1, 2, 3, 'nihao']

9.7.3 通用生成器

生成器是包含关键字 yield 的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。每次请求值时,都将执行生成器代码,知道遇到 yield 或 return。yield意味着应生成一个值,而 return 意味着生成器应停止执行。

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

def simple_generator():
    yield 1

>>> simple_generator
<function __main__.simple_generator>

>>> simple_generator()
<generator object simple_generator at 0x103ac4e08>

对于生成器的函数返回的迭代器,可以向使用其他迭代器一样使用它。


9.7.4

def repeater(value):
    while True:
        new = (yield value)
        if new is not None:
            value = new

>>> r = repeater(42)
>>> next(r)
42

>>> r.send('Hello word!')
'Hello word!'

9.8 八皇后问题

掩饰如何使用生成器来解决一个经典的编程问题


9.8.1 生成器的回溯

对于逐步得到结果的复杂递归算法,非常适合使用生成器来实现。【掌握图和树】


9.8.2 问题

“你需要将 8 个皇后放在棋盘上,条件是任何一个皇后都不能威胁其他皇后,即任何两个皇后都不能吃掉对方。怎样才能做到这一点呢?应将这些皇后放在什么地方呢?“

规则:皇后能吃掉同一行,同一列,同一对角线的任意棋子。棋盘大小为 8✖️8 。


9.8.3 状态表示

可用元组或列表表示可能的解,每个元素表示相应行中皇后所在的位置(即列)。如 state[0] = 3 表示第 1 行的皇后在第 4 列的位置。

9.8.4 检测冲突

冲突即“某一皇后可以吃掉另一皇后“,我们用一个函数conflict 接受既有皇后的位置,并确定下一个皇后的位置是否会导致冲突。

# 检测冲突
def conflict(state, nextX):
    '''如果发生冲突,返回 True,跳出 for 循环; 如果没发生冲突,在for循环后返回 False'''
    nextY = len(state)
    for i in range(nextY):
        #if abs(state[i] - nextX) == 0 or  abs(state[i] - nextX) == nextY - i 
        if abs(state[i] - nextX) in (0, nextY - i): 
            return True
    return False


----------
# True 即为冲突
>>> conflict([2],1)
True
# 基线条件,即最后一个皇后如何处理
def queens(num, state) :
    if len(state) == num -1: 
        for pos in range(num):
            if not conflict(state,pos):
                yield pos


----------
>>> list(queens(4,(1,3,0)))
[2]
# 递归条件,基于“基线条件建立“。找到符合要求的最后一个棋子的位置,将递归路径上的每一次正确的位置(pos)选择逆向相加组合,即可得到最终的位置元组

def queens(num = 8 , state = ()):
    if len(state) == num -1:
        for pos in range(num):
            if not conflict(state,pos):
                yield (pos,)

    # 基线条件是结束递归的条件,结束后返回的值要不为‘空’,要不为‘最后一个棋子的位置’
    else:
        for pos in range(num):
            if not conflict(state, pos):
                # 只有当返回最后一个棋子位置时,result才有值,启动for循环
                for result in queens(num, state + (pos,)):
                    yield (pos,) + result
# 上述递归过程解析
# queens(4,()):
#     for pos in range(4): 
#         pos = 0:
#             if not conflict((),0):# True
#                 for result in queens(4,() + (0,)):
#                     queens(4,(0)):
#                         for pos in range(4):
#                             pos = 0:
#                                 if not conflict((0),0): # False
#                             pos = 1:
#                                 if not conflict((0),1): # False
#                             pos = 2:
#                                 if not conflict((0),2): # True
#                                     for result in queens(4,(0,) + (2,)):
#                                         queens(4,(0,2)):
#                                             for pos in range(4):
#                                                 pos = 0:
#                                                     if not conflict((0,2),0): # False
#                                                 pos = 1:
#                                                     if not conflict((0,2),1): # False
#                                                 pos = 2:
#                                                     if not conflict((0,2),2): # False
#                                                 pos = 3:
#                                                     if not conflict((0,2),3): # False
#                             pos = 3:
#                                 if not conflict((0),3): # True
#                                     for result in queens(4,(0,) + (3,):
#                                         queens(4,(0,3)):
#                                             for pos in range(4):
#                                                 pos = 0:
#                                                     if not conflict((0,3),0): # False
#                                                 pos = 1:
#                                                     if not conflict((0,3),1):
#                                                         for result in queens(4,(0,3) + (1,)):
#                                                             queens(4,(0,3,1)):
#                                                                 if len((0,3,1)) == 4-1: # True
#                                                                     for pos in number:
#                                                                         ... ...
#                                                                         yield None
        # 若最后一个棋子不符合要求,则退回到初始棋子
#         pos = 1:
#             if not conflict((),1):# True
#                |for result in queens(4,() + (1,)):
#                |    queens(4,(1)):
#                |        for pos in range(4):
#                |            pos = 3:
#                |               if not conflict((1),3): # True
#                |                   |for result in queens(4,(1,) + (3,)):
#                |                   |    queens(4,(1,3)):
#                |                   |        for pos in range(4):
#                |                   |            pos = 0:
#                |                   |                if not conflict((1,3),0): # True
#                |                   |                    |for result in queens(4,(1,3)+ (0,)):
#                |                   |                    |    queens(4,(1,3,0)):
#                |                   |                    |        # 知道最后一个棋子符合要求,进入 for 循环,按相反顺序将棋子位置存储    
#                |                   |                    |        if len((1,3,0)) == 4-1: # True
#                |                   |                    |           for pos in number:
#                |                   |                    |                pos = 2:
#                |                   |                    |                    yild (0,) +(2,)
#                |                   |                    |    #return (0,2)
#                |                   |    result = (0,2):
#                |                   |        yield (3,) + (0,2)
#                |                   |    #return (3,0,2)
#                |    result = (3,0,2)
#                |        yield (1,) + (3,0,2)
#                |    #return (1,3,0,2)
#             
#         pos = 2:
#             ... ...
# 化简,“合并同类项”

def queens(num = 8 , state = ()):
    for pos in range(num):
        if not conflict(state, pos):
            if len(state) == num -1:
                yield (pos,)

            # 基线条件是结束递归的条件,结束后返回的值要不为‘空’,要不为‘最后一个棋子的位置’
            else:
                # 只有当返回最后一个棋子位置时,result才有值,启动for循环
                for result in queens(num, state + (pos,)):
                     yield (pos,) + result


----------
>>> list(queens(4))
[(1, 3, 0, 2), (2, 0, 3, 1)]
# 可视化
def prettyprint(solution):
    '''先解决每行输出什么,再解决如何输出多行'''
    def line(pos, length = len(solution)):
        return '.' * pos + 'X' + '.' * (length -pos -1)
    for pos in solution:
        print(line(pos))


----------
>>> import random
>>> prettyprint(random.choice(list(queens(8))))

......X.
.X......
.....X..
..X.....
X.......
...X....
.......X
....X...

猜你喜欢

转载自blog.csdn.net/weixin_37392582/article/details/80367129
今日推荐