Advertisement

Python基础复习---重点知识笔记(二)

阅读量:

文章目录

  • 七、再谈抽象

    • 7.1 对象魔法 ⭐🌙🔺

      • 7.1.1 多态
      • 7.1.2 封装
      • 7.1.3 继承
    • 7.2 类

      • 7.2.1 类的创建
      • 7.2.2 属性、函数、方法
      • 7.2.3 隐藏--不能从外部访问⭐
      • 7.2.4 类的命名空间
      • 7.2.5 指定超类⭐
      • 7.2.6 深入探讨继承
      • 7.2.7 多个超类
      • 7.2.8 抽象基类
  • 八、魔法方法、特性和迭代器

    • 8.1 构造函数⭐

      • 8.1.1 重写特殊的构造函数
    • 8.2 元素访问⭐🌙

    • 8.3 特性

    • 8.4 迭代器

    • 8.5 生成器

      • 8.5.1 递归式生成器
      • 8.5.2 通用生成器
    • 8.6 八皇后问题⭐

  • 总结


七、再谈抽象

 多态 :可对不同类型的对象执行相同的操作,而这些操作就像“被施了魔法”一样能够
正常运行。
 封装 :对外部隐藏有关对象工作原理的细节。
 继承 :可基于通用类创建出专用类。

7.1 对象魔法 ⭐🌙🔺

7.1.1 多态

“有多种形态” 。这大致意味着即便你不知道变量指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异。

复制代码
    >>> object.get_price()
    2.5
    
    
      
      
    
    • 案例展示

标准库模块random包含一个名为choice的函数,它从序列中随机选择一个元素

复制代码
    # 案例一 count()方法
    >>> from random import choice
    >>> x = choice(['Hello, world!', [1, 2, 'e', 'e', 4]])
    >>> x.count('e')
    2
    
    # 案例二 加法运算夫
    >>> 1 + 2
    3
    >>> 'Fish' + 'license'
    'Fishlicense'
    
    # 案例三 repr
    def length_message(x):
    	print("The length of", repr(x), "is", len(x))
    
    >>> length_message('Fnord')
    The length of 'Fnord' is 5
    >>> length_message([1, 2, 3])
    The length of [1, 2, 3] is 3
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

7.1.2 封装

多态 让你无需知道对象所属的类(对象的类型)就能调用其方法
封装 让你无需知道对象的构造就能使用它。

  • 问题引入

已知一个名为OpenObject的类

复制代码
    >>> o = OpenObject() # 对象就是这样创建的
    >>> o.set_name('Sir Lancelot')
    >>> o.get_name()
    'Sir Lancelot'
    
    
      
      
      
      
    

当创建多个对象时,出现问题,因为他们公用同一个变量

复制代码
    >>> o1 = OpenObject()
    >>> o2 = OpenObject()
    >>> o1.set_name('Robin Hood')
    >>> o2.get_name()
    'Robin Hood'
    
    
      
      
      
      
      
      
    

如上代码所见,设置一个对象的名称时,将自动设置另一个对象的名称。
解决办法将其作为一个属性即可 ,属性是归属于对象的变量,就像方法一样。

对象有自己的状态 。对象的状态由其属性(如名称)描述。对象的方法可能修改这些属性,因此对象将一系列函数(方法)组合起来,并赋予它们访问一些变量(属性)的权限,而属性可用于在两次函数调用之间存储值。

使用属性而非全局变量重新编写前面的类,并将其重命名为ClosedObject,就可像下面这样使用它:

复制代码
    >>> c = ClosedObject()
    >>> c.set_name('Sir Lancelot')
    >>> c.get_name()
    'Sir Lancelot'
    >>> r = ClosedObject()
    >>> r.set_name('Sir Robin')
    r.get_name()
    'Sir Robin'
    
    >>> c.get_name()
    'Sir Lancelot'
    
    
      
      
      
      
      
      
      
      
      
      
      
    

7.1.3 继承

见7.2.5章节、7.2.6章节

7.2 类

7.2.1 类的创建

类:类型,一种对象。每个对象都属于特定的类,并被称为该类的实例。
例如:鸟就是鸟类的实例。
鸟类是一个非常通用(抽象)的类,它有多个子类:你看到的那只鸟可能属于子类“云雀”。你可将“鸟类”视为由所有鸟组成的集合,而“云雀”是其一个子集。一个类的对象为另一个类的对象的子集时,前者就是后者的子类。因此“云雀”为“鸟类”的子类,而“鸟类”为“云雀”的超类。
类的所有实例都有该类的所有方法,因此子类的所有实例都有超类的所有方法

  • 定义一个类:Person
复制代码
    class Person:
    
    	def set_name(self, name):
    		self.name = name
    		
    	def get_name(self):
    		return self.name
    		
    	def greet(self):
    		print("Hello, world! I'm {}.".format(self.name))
    
    
      
      
      
      
      
      
      
      
      
      
    

class语句创建独立的命名空间,用于在其中定义函数

复制代码
    >>> foo = Person()
    >>> bar = Person()
    >>> foo.set_name('Luke Skywalker')
    >>> bar.set_name('Anakin Skywalker')
    >>> foo.greet()
    Hello, world! I'm Luke Skywalker.
    >>> bar.greet()
    Hello, world! I'm Anakin Skywalker.
    
    
      
      
      
      
      
      
      
      
      
    

对foo调用set_name和greet时,foo都会作为第一个参数自动 传递给它们。我将这个参数命名为self,这非常贴切。实际上,可以随便给这个参数命名,但鉴于它总是指向对象本身,因此习惯上将其命名为self。
self很有用,甚至必不可少。如果没有它,所有的方法都无法访问对象本身——要操作的属性所属的对象

复制代码
    # 外部访问属性
    >>> foo.name
    'Luke Skywalker'
    >>> bar.name = 'Yoda'
    >>> bar.greet()
    Hello, world! I'm Yoda.
    
    
      
      
      
      
      
      
    

7.2.2 属性、函数、方法

  • 实例展示
复制代码
    # 案例一
    >>> class Class:
    ... 	def method(self):
    ... 		print('I have a self!')
    ...
    >>> def function():
    ... 	print("I don't...")
    ...
    >>> instance = Class()
    >>> instance.method() 
    I have a self!
    
    >>> instance.method = function
    >>> instance.method() 
    I don't...
    
    # 案例二
    >>> class Bird:
    ... song = 'Squaawk!'
    ... def sing(self):
    ... 	print(self.song)
    ...
    >>> bird = Bird()
    >>> bird.sing()
    Squaawk!
    
    >>> birdsong = bird.sing
    >>> birdsong()
    Squaawk!
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

7.2.3 隐藏–不能从外部访问⭐

解释何为:不能从外部访问?

复制代码
    >>> c.name
    'Sir Lancelot'
    >>> c.name = 'Sir Gumby'
    >>> c.get_name()
    'Sir Gumby'
    
    
      
      
      
      
      
    

毕竟,如果能直接访问ClosedObject(对象c所属的类)的属性name,就不需要创建方法setName和getName了。
例如,ClosedObject可能在对象修改其名称时向管理员发送电子邮件。这种功能可能包含在方法set_name中 。但如果直接设置c.name ,结果将如何呢?什么都不会发生——根本不会发送电子邮件。为避免这类问题,可将属性定义为私有
私有属性不能从对象外部访问 ,而只能通过存取器方法(如get_name和set_name)来访问。

复制代码
    class Secretive:
    
    	def __inaccessible(self):
    		print("Bet you can't see me ...")
    		
    	def accessible(self):
    		print("The secret message is:")
    		# 现在从外部不能访问__inaccessible,但在类中(如accessible中)依然可以使用它。
    		self.__inaccessible()
    
    
      
      
      
      
      
      
      
      
      
    
复制代码
    >>> s = Secretive()
    >>> s.__inaccessible()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: Secretive instance has no attribute '__inaccessible'
    
    >>> s.accessible()
    The secret message is:
    Bet you can't see me ...
    
    
      
      
      
      
      
      
      
      
      
    

7.2.4 类的命名空间

在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间 )内执行的,而类的所有成员都可访问这个命名空间。类定义其实就是要执行的代码段。

复制代码
    class MemberCounter:
    members = 0
    def init(self):
    MemberCounter.members += 1
    
    >>> m1 = MemberCounter()
    >>> m1.init()
    >>> MemberCounter.members
    1
    >>> m2 = MemberCounter()
    >>> m2.init()
    >>> MemberCounter.members
    2
    
    >>> m1.members
    2
    >>> m2.members
    2
    
    >>> m1.members = 'Two'
    >>> m1.members
    'Two'
    >>> m2.members
    2
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

7.2.5 指定超类⭐

子类扩展了超类的定义。要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起。

复制代码
    # Filter 是一个过滤序列的通用类 实际上,它不会过滤掉任何东西
    class Filter:
    	def init(self):
    		self.blocked = []
    	def filter(self, sequence):
    		return [x for x in sequence if x not in self.blocked] # 返回的是列表
    
    class SPAMFilter(Filter): # SPAMFilter是Filter的子类
    	def init(self): # 重写超类Filter的方法init
    		self.blocked = ['SPAM']
    
    
      
      
      
      
      
      
      
      
      
      
    
复制代码
    >>> f = Filter()
    >>> f.init()
    >>> f.filter([1, 2, 3])
    [1, 2, 3]
    
    
      
      
      
      
    

Filter类的用途在于可用作其他类(将’SPAM’从序列中过滤掉的SPAMFilter类)的基类(超类)。

复制代码
    >>> s = SPAMFilter()
    >>> s.init()
    >>> s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM'])
    ['eggs', 'bacon']
    
    
      
      
      
      
      
    

请注意SPAMFilter类的定义中有两个要点。

  • 以提供新定义的方式重写了Filter类中方法init的定义。
  • 直接从Filter类继承了方法filter的定义,因此无需重新编写其定义。
    第二点说明了继承很有用的原因:可以创建大量不同的过滤器类,它们都从Filter类派生而来,并且都使用已编写好的方法filter。这就是懒惰的好处。

7.2.6 深入探讨继承

  • 内置方法 issubclass

要确定一个类是否是另一个类的子类,可使用内置方法issubclass

复制代码
    >>> issubclass(SPAMFilter, Filter)
    True
    >>> issubclass(Filter, SPAMFilter)
    False
    
    
      
      
      
      
    

如果你有一个类,并想知道它的基类 ,可访问其特殊属性__bases__

复制代码
    # 类SPAMFilter的基类是Filter 
    >>> SPAMFilter.__bases__
    (<class __main__.Filter at 0x171e40>,)# FIlter 
    
    >>> Filter.__bases__
    (<class 'object'>,)
    
    
      
      
      
      
      
      
    
  • 使用isinstance 判断是否是实例
复制代码
    >>> s = SPAMFilter()
    >>> isinstance(s, SPAMFilter)
    True
    >>> isinstance(s, Filter)
    True
    >>> isinstance(s, str)
    False
    
    
      
      
      
      
      
      
      
    

使用isinstance通常不是良好的做法,依赖多态在任何情况下都是更好的选择。一个重要的例外情况是使用抽象基类和模块abc时。

  • class

如果你要获悉对象属于哪个类,可使用属性

复制代码
    >>> s.__class__
    <class __main__.SPAMFilter at 0x1707c0>
    # 对象s属于类SPAMFilter
    
    
      
      
      
    

还可使用type(s)来获悉其所属的类

7.2.7 多个超类

  • 多重继承
复制代码
    class Calculator:
    	def calculate(self, expression):
    		self.value = eval(expression)
    
    class Talker:
    	def talk(self):
    		print('Hi, my value is', self.value)
    
    class TalkingCalculator(Calculator, Talker):
    	pass
    
    
      
      
      
      
      
      
      
      
      
      
    
复制代码
    >>> tc = TalkingCalculator()
    >>> tc.calculate('1 + 2 * 3')
    >>> tc.talk()
    Hi, my value is 7
    
    
      
      
      
      
    
复制代码
    eval() 函数用来执行一个字符串表达式,并返回表达式的值。
    
    >>>x = 7
    >>> eval( '3 * x' )
    21
    >>> eval('pow(2,2)')
    4
    >>> eval('2 + 2')
    4
    >>> n=81
    >>> eval("n + 4")
    85
    
    
      
      
      
      
      
      
      
      
      
      
      
      
    

注意:应避免使用多重继承,因为在有些情况下,它可能带来意外的“并发症”。
如果多个超类以不同的方式实现了同一个方法(即有多个同名方法),必须在class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法
在前面的示例中,如果Calculator类包含方法talk,那么这个方法将覆盖Talker类的方法talk(导致它不可访问

如下反转超类的排列顺序:导致Talker的方法talk是可以访问的。

复制代码
    class TalkingCalculator(Talker, Calculator): 
    	  pass
    
    
      
      
    

7.2.8 抽象基类

一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法

复制代码
    from abc import ABC, abstractmethod
    
    class Talker(ABC):
    	@abstractmethod
    	def talk(self):
    		pass
    
    
      
      
      
      
      
      
    

使用@abstractmethod来将方法标记为抽象的——在子类中必须实现的方法。
抽象类(即包含抽象方法的类)最重要的特征是不能实例化
若派生出的子类没有重写抽象方法,则该类也是抽象的,不能被实例化。

  • 实例展示
复制代码
    >>> Talker()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class Talker with abstract methods talk
    # 翻译:无法用抽象方法talk实例化抽象类Talker
    # instantiate :例示;用具体例子说明
    
    # 派生出子类 该类也是抽象的,无法实例化--->因为没有重写talk()方法,不能实例化
    class Knigget(Talker):
    	pass
    
    # 重写talk()方法 让其可以实例化
    class Knigget(Talker):
    	def talk(self):
    		print("Ni!")
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
  • 注册机制

已知有一个类Herring,假设该类是从他人的模块中导入的。这样无法通过派生的方法。
解决问题:可将Herring注册为Talker(而不从Herring和Talker派生出子类),这样所有Herring对象都将被视为Talker对象。

复制代码
    class Herring:
    	def talk(self):
    		print("Blub.")
    		
    >>> h = Herring()
    >>> isinstance(h, Talker)
    False
    
    >>> Talker.register(Herring)# 注册机制
    <class '__main__.Herring'>
    >>> isinstance(h, Talker)
    True
    >>> issubclass(Herring, Talker)
    True
    
    >>> class Clam:
    ... 	pass
    ...
    >>> Talker.register(Clam)
    <class '__main__.Clam'>
    
    >>> issubclass(Clam, Talker)
    True
    
    >>> c = Clam()
    >>> isinstance(c, Talker)
    True
    # 缺点:直接从抽象类派生提供的保障没有了
    >>> c.talk()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: 'Clam' object has no attribute 'talk'
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

八、魔法方法、特性和迭代器

8.1 构造函数⭐

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

复制代码
    class FooBar:
    	def __init__(self):
    		self.somevar = 42
    
    		
    >>> f = FooBar() # 创建实例时,自动调用__init__
    >>> f.somevar
    42
    
    
      
      
      
      
      
      
      
      
    

8.1.1 重写特殊的构造函数

重写构造函数时,必须调用超类(继承的类)的构造函数,否则可能无法正确地初始化对象。

  • 问题引入
复制代码
    # 超类
    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):
    		self.sound = 'Squawk!'
    	def sing(self):
    		print(self.sound)
    -----------------------------------------------------
    # 调用
    >>> b = Bird()
    >>> b.eat()
    Aaaah ...
    >>> b.eat()
    No, thanks!
    
    >>> 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)的构造函数,以确保基本的初始化得以执行。

  • 方案一:调用未关联的超类构造函数
复制代码
    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。

  • 方案二:使用函数super()

调用这个函数时,将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法
在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)
    
    # 调用
    
    >> sb = SongBird()
    >>> sb.sing()
    Squawk!
    >>> sb.eat()
    Aaaah ...
    >>> sb.eat()
    No, thanks!
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

即便有多个超类,也只需调用函数super一次(条件是所有超类的构造函数也使用函数super)

在这里插入图片描述

8.2 元素访问⭐🌙

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

在这里插入图片描述
  • 创建无序序列
复制代码
    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 # 存储修改后的值 
    
    # 调用
    >>> 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
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

8.3 特性

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

  • 案例展示
复制代码
    # 代码一
    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
    --------------------------------------
    # 代码二
    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
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

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

8.4 迭代器

  • “列表”为斐波那契数列,表示该数列的迭代器如下:
复制代码
    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__(并像刚才那样让它返回self),这样迭代器就可直接用于for循环中。
注意 :更正规的定义是,实现了方法__iter__的对象是可迭代的,而实现了方法__next__的对象是迭代器

复制代码
    # 首先,创建一个Fibs对象。
    >>> fibs = Fibs()
    # 然后就可在for循环中使用这个对象,如找出第一个大于1000的斐波那契数。
    >>> for f in fibs:
    ... 	if f > 1000:
    ... 		print(f)
    ... 		break
    ...
    1597
    # 这个循环之所以会停止,是因为其中包含break语句;否则,这个for循环将没完没了地执行。
    
    
      
      
      
      
      
      
      
      
      
      
    
  • 迭代器创建序列
复制代码
    >>> 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]
    
    
      
      
      
      
      
      
      
      
      
      
      
      
    

8.5 生成器

首先迭代所提供嵌套列表中的所有子列表 ,然后按顺序迭代每个子列表的元素。
包含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]
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
    

8.5.1 递归式生成器

  • 不知道多少层嵌套时,使用递归
复制代码
    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]
    
    
      
      
    

在函数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
    
    print(list(flatten(['foo', ['bar', ['baz']]])))
    
    # 调用
    
    >>> list(flatten(['foo', ['bar', ['baz']]]))
    ['foo', 'bar', 'baz']
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    

8.5.2 通用生成器

生成器是包含关键字yield的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器
yield意味着应生成一个值,而return意味着生成器应停止执行(即不再生成值;仅当在生成器调用return时,才能不提供任何参数)
生成器由两个单独的部分组成:生成器的函数生成器的迭代器 ,生成器的函数是由def语句定义的,其中包含yield。生成器的迭代器是这个函数返回的结果。这两个实体通常被视为一个,通称为生成器。

复制代码
    >>> def simple_generator():
    yield 1
    ...
    >>> simple_generator
    <function simple_generator at 153b44>
    
    >>> simple_generator()
    <generator object at 1510b0>
    
    
      
      
      
      
      
      
      
      
    

8.6 八皇后问题⭐

  • 问题引入

你需要将8个皇后放在棋盘上,条件是任何一个皇后都不能威胁其他皇后,即任何两个皇后都不能吃掉对方。[任意两个皇后都不能处于同一行、同一列或同一斜线上]

  • 思路解析

在棋盘的第一行尝试为第一个皇后选择一个位置,再在第二行尝试为第二个皇后选择一个位置,依次类推。在发现无法为一个皇后选择合适的位置后,回溯到前一个皇后,并尝试为它选择另一个位置。最后,要么尝试完所有的可能性,要么找到了答案。

  • 状态表示

可简单地使用元组(或列表)来表示可能的解(或其一部分),其中每个元素表示相应行中皇后所在的位置(即列)。如果state[0] == 3,就说明第1行的皇后放在第4列(还记得吧,我们从0开始计数)
完全可以使用列表(而不是元组)来表示状态,具体使用哪个完全取决于你的喜好。一般而言,如果序列较小且是静态的,使用元组可能是不错的选择
状态元组state的长度小于8(即皇后总数)

  • 检测冲突

要找出没有冲突(即任何一个皇后都吃不到其他皇后)的位置组合,首先必须定义冲突 :

  • 如果下一个皇后与当前皇后的x坐标相同或在同一条对角线上,将发生冲突,因此返回True;如果没有发生冲突,就返回False。
复制代码
    # nextX表示下一个皇后的水平位置(x坐标,即列)
    # nextY为下一个皇后的垂直位置(y坐标,即行)
    # 状态元组state的长度小于8(即皇后总数)
    组的长度小于8(即皇后总数)。
    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
    '''
    abs(state[i] - nextX) in (0, nextY - i) 解释:
    state[0] = 3:皇后的位置第一行第四列
    	如果差值等于0,两个皇后在同一列, 则代表冲突, 返回True;
    	如果列的差值等与行的差, 两个皇后在对角线上, 则代表冲突, 返回True;
    '''
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
  • 基线条件

最后一个皇后。对于这个皇后,你想如何处理呢?

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

这段代码的意思是,如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那些不会引发冲突的位置。

  • 参数num为皇后总数
  • 参数state是一个元组,包含已放好的皇后的位置。
    • 示例如下:
复制代码
    >>> list(queens(4, (1, 3, 0)))
    [2]
    
    
      
      
      
    

在这个示例中,只有一个位置符合条件
在这里插入图片描述

  • 递归条件

对于递归调用,向它提供的是由当前行上面的皇后位置组成的元组。对于当前皇后的每个合法位置,递归调用返回的是由下面的皇后位置组成的元组。为了让这个过程不断进行下去,只需将当前皇后的位置插入返回的结果开头,

复制代码
    def queens(num=8, state=()):
    """
    采用生成器的方式来产生每一个皇后的位置,并用递归来实现下一个皇后的位置。
    num: 皇后的数量
    state: 标记已经排好的每个皇后的位置
    	"""
    	# # 八皇后的数量N=0, 1, 2, 3, 4, 5, 6 , 7 你要在哪一列放置皇后
    	for pos in range(num):
    		#  如果不冲突,则递归构造棋盘
    		if not conflict(state, pos):
    			# # 如果棋盘状态state已经等于num-1,即到达倒数第二行,而这时最后一行皇后又没冲突,直接yield,打出其位置(pos, )
    			if len(state) == num-1:
    				yield (pos,)
    			else:
    				for result in queens(num, state + (pos,)):
    					yield (pos,) + result
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
复制代码
    # 对于 state + (pos,): state是状态元组
    >>> (1,3,0)+(2,)
    (1, 3, 0, 2)
    
    
      
      
      
    
  • 棋盘展示
复制代码
    def prettyprint(solution):
    """
    友好展示: 为了直观表现棋盘,用X表示每个皇后的位置
    """
    	def line(pos, length=len(solution)):
    		return '. ' * (pos) + 'X ' + '. ' * (length-pos-1)
    	for pos in solution:
    		print(line(pos))
    
    
      
      
      
      
      
      
      
      
    
    • 测试数据
复制代码
    >>> 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,
    
    
      
      
      
      
      
      
      
      
      
      
      
      
    
    • 结果展示
复制代码
    >>> import random
    >>> prettyprint(random.choice(list(queens(8))))
    . . . . . X . .
    . X . . . . . .
    . . . . . . X .
    X . . . . . . .
    . . . X . . . .
    . . . . . . . X
    . . . . X . . .
    . . X . . . . .
    
    
      
      
      
      
      
      
      
      
      
      
    

random.choice(seq):
非空序列 中随机选取一个数据并带回,该序列可以是list、tuple、str、set。
如果序列为空,则弹出IndexError错误。

复制代码
    >>> len(list(queens(8)))
    92
    # random.choice(seq)则会从92个数据中随机选择一个待会进行棋盘打印展示
    
    
      
      
      
    

总结

全部评论 (0)

还没有任何评论哟~