在Python中,有些名称很特别,开头和结尾都是两个下划线。你在本书前面已经见过一些, 如__future__。
在这样的名称中,很大一部分都是魔法(特殊)方法的名称。如果你的对象实现了这些方法,它们将在特定情况下(具体是哪种情况取决于方法的名称)被Python调用,而几乎不需要直接调用
本章讨论几个重要的魔法方法,其中最重要的是__init__以及一些处理元素访问的方法(它 们让你能够创建序列或映射)。本章还将讨论两个相关的主题:特性(property)和迭代器(iterator)。

9.1 构造函数

我们要介绍的第一个魔法方法是构造函数。你可能从未听说过构造函数(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

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

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

class A:def hello(self):print("Hello, I'm A.")
class B(A): pass
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

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

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

class Bird:                          # 父类def __init__(self):self.hungry = True def eat(self):if self.hungry: print('Aaaah ...')self.hungry = Falseelse:print('No, thanks!')
bird = Bird()
bird.eat()
bird.eat()
class SongBird(Bird):               # 子类def __init__(self):self.sound = 'Squawk!' def sing(self):print(self.sound)
sb = SongBird()
sb.sing()
sb.eat()
# 报错 'SongBird' object has no attribute 'hungry'

因为在SongBird中重写了构造函数,但新的构造函数没有包含任何初始化属性hungry的代码。
要消除这种错误,SongBird的构造函数必须调用其超类(Bird)的构造函数,以确保基本的初始化得以执行。
为此,有两种方法:调用未关联的超类构造函数,以及使用函数super。

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

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

对实例调用方法时,方法的参数self将自动关联到实例(称为关联的方 法),这样的示例你见过多个。
然而,如果你通过类调用方法(如Bird._init_),就没有实例 与其相关联。在这种情况下,你可随便设置参数self。这样的方法称为未关联的。

9.1.3 使用函数 super

新式类中应该使用super。
调用这个函数时,将当前类和当前实例作为参数。对其返回的对象调用方法时,调用的将是超类(而不是当前类)的方法。因此,在SongBird的构造函数中,可不使用Bird, 而是使用super(SongBird, self)。另外,可像通常那样(也就是像调用关联的方法那样)调用方法__init__。

class SongBird(Bird):                # 子类def __init__(self):super().__init__(self)self.sound = 'Squawk!' def sing(self):print(self.sound)

使用super的优点

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

9.2 元素访问

9.2.1 基本的序列和映射协议

序列和映射基本上是元素(item)的集合。

  • _len_(self) 这个方法应返回集合包含的项数,对序列来说为元素个数,对映射来说 为键值对数。
  • _getitem_(self, key):这个方法应返回与指定键相关联的值
  • _setitem_(self, key, value):这个方法应以与键相关联的方式存储值,以便以后能够 使用__getitem__来获取。
  • _delitem_(self, key):这个方法在对对象的组成部分使用__del__语句时被调用,应 删除与key相关联的值。
    示例代码
class ArithmeticSequence:def __init__(self, start=0, step=1):self.start = start self.step = stepself.changed = {}def __getitem__(self,key):try: return self.changed[key] except KeyError:return self.start + key * self.stepdef __setitem__(self, key, value):self.changed[key] = value
s = ArithmeticSequence(1, 2)
s[4]     # 9
s[10000] # 20001

上面这个代码实现的是一个无穷序列。通过魔方方法来实现索引不存在时的处理

9.2.2 从list、dict和str派生

序列中还有很多的魔法方法可以通过继承得到。重写其中的魔法方法来进行修改,这里可以从list、dict和str中继承得到
来看一个简单的示例——一个带访问计数器的列表。

class CounterList(list):def __init__(self, *args):super().__init__(*args)self.counter = 0def __getitem__(self, index):self.counter += 1return super(CounterList, self).__getitem__(index)
cl = CounterList(range(10))       # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
cl[0] + cl[2]                    # 2
cl.counter          # 访问计数器

9.3 特性

第7章中提到了存取方法,它们是名称类似于getHeight和setHeight的方法,用于获取或设置属性。
下面是示例

class Rectangle:def __init__(self):self.width = 0self.height = 0def 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
print(r.get_size())   # (10, 5)
r.set_size((150, 100))
r.width               # 150

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

9.3.1 函数property

函数property使用起来很简单。只需要再添加一行代码。

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

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

9.3.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)
# 引入了装饰器之后的版本
class MyClass:@staticmethod def smeth():print('This is a static method')@classmethoddef cmeth(cls):print('This is a class method of', cls)

定义这些方法后,就可像下面这样使用它们(无需实例化类):

MyClass.smeth()
MyClass.cmeth()

9.3.3 _getattr_、__setattr__等方法

  • _getattribute_(self, name):在属性被访问时自动调用(只适用于新式类)。
  • _getattr_(self, name):在属性被访问而对象没有这样的属性时自动调用。
  • _setattr_(self, name, value):试图给属性赋值时自动调用。
  • _delattr_(self, name):试图删除属性时自动调用。

9.4 迭代器

9.4.1 迭代器协议

迭代(iterate)意味着重复多次,就像循环那样。本书前面只使用for循环迭代过序列和字典, 但实际上也可迭代其他对象:实现了方法__iter__的对象。
方法__iter__返回一个迭代器,它是包含方法__next__的对象,当你调用方法__next__时,迭代器应返回其下一个值。
你还可使用内置的便利函数next,在这种情况下,next(it)与 it.next()等效。
更正规的定义是,实现了方法__iter__的对象是可迭代的,而实现了方法__next__的对象 是迭代器。

class Fibs:def __init__(self):self.a = 0self.b = 1def __next__(self):self.a, self.b = self.b, self.a + self.breturn self.a def __iter__(self):return self
fibs = Fibs()
for f in fibs:if f > 1000: print(f)break

9.4.2 从迭代器创建序列

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

class TestIterator:value = 0def __next__(self):self.value += 1if self.value > 10:raise StopIterationreturn self.valuedef __iter__(self):return self
ti = TestIterator()
list(ti)
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

9.5 生成器

生成器是一个相当复杂的概 念,你可能需要花些功夫才能明白其工作原理和用途。虽然生成器让你能够编写出非常优雅的代 码,但请放心,无论编写什么程序,都完全可以不使用生成器。

9.5.1 创建生成器

下面用一个示例说明,顺序提供列表的列表中的元素

def flatten(nested):for sublist in nested:for element in sublist: yield element
nested = [[1, 2], [3, 4], [5]]
for num in flatten(nested):print(num)

包含yield语句的函数都被称为生成器。
生成器不是使用return返回一个 值,而是可以生成多个值,每次一个。每次使用yield生成一个值后,函数都将冻结,即在此停止执行,等待被重新唤醒。被重新唤醒后,函数将从停止的地方开始继续执行。

9.5.2 递归式生成器

调用flatten时,有两种可能性(处理递归时都如此):基线条件和递归条件。
在基线条件下, 要求这个函数展开单个元素(如一个数)。在这种情况下,for循环将引发TypeError异常(因为 你试图迭代一个数),而这个生成器只生成一个元素。

def flatten(nested): try:for sublist in nested:for element in flatten(sublist): yield element except TypeError:yield nested
list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8]))

9.5.3 通用生成器

生成器是包含关键字 yield的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。
每次请求值时,都将执行生成器的代码,直到遇到yield或return。yield意味着应生成一个值,而return意味着生成器应停止执行(即不再生成值;仅当在生成器调用return时,才能不提供任何参数)。
生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。生成器的函数是由def语句定义的,其中包含yield。

def simple_generator(): yield 1

9.5.4 生成器的方法

在生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值。这个通信渠道包含 如下两个端点。

  • 外部世界 外部世界可访问生成器的方法send,这个方法类似于next,但接受一个参数(要 发送的“消息”,可以是任何对象)。
  • 生成器 在挂起的生成器内部,yield可能用作表达式而不是语句。当生成器重新运行时,yield将返回一个值–通过send从外部世界发送的值
def repeater(value):while True:new = (yield value)if new is not None: value = new
r = repeater(42)
next(r)
r.send("Hello, world!")

9.6 八皇后问题

9.6.1 生成器的回溯

对于逐步得到结果的复杂递归算法,非常适合使用生成器来实现。
要在不使用生成器的情况 下实现这些算法,通常必须通过额外的参数来传递部分结果,让递归调用能够接着往下计算。通过使用生成器,所有的递归调用都只需生成其负责部分的结果。
对于需要尝试所有组合直到找到答案的问题,这种回溯策略对其解决很有帮助。

9.6.2 问题

国际象棋中的皇后比中国象棋里的大车还厉害,皇后能横向,纵向和斜向移动,在这三条线上的其他棋子都可以被吃掉。所谓八皇后问题就是:将八位皇后放在一张8x8的棋盘上,使得每位皇后都无法吃掉别的皇后,(即任意两个皇后都不在同一条横线,竖线和斜线上),问一共有多少种摆法。此问题是在1848年由棋手马克思·贝瑟尔提出的,后面陆续有包括高斯等大数学家们给出自己的思考和解法,所以此问题不只是有年头了,简直比82年的拉菲还有年头,我们今天不妨尝尝这老酒。
我们先举例来理解一下这个问题的场景到底是什么样子的,下面的绿色格子是一个皇后在棋盘上的“封锁范围”,其他的皇后不能放置在这些绿格子中:


这是一个深受大家喜爱的计算机科学谜题:你需要将8个皇后放在棋盘上,条件是任何一个 皇后都不能威胁其他皇后,即任何两个皇后都不能吃掉对方。怎样才能做到这一点呢?应将这些 皇后放在什么地方呢?
这是一个典型的回溯问题:在棋盘的第一行尝试为第一个皇后选择一个位置,再在第二行尝 试为第二个皇后选择一个位置,依次类推。在发现无法为一个皇后选择合适的位置后,回溯到前 一个皇后,并尝试为它选择另一个位置。最后,要么尝试完所有的可能性,要么找到了答案。

9.6.3 状态表示

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

9.6.4 检测冲突

先来做些简单的抽象。要找出没有冲突(即任何一个皇后都吃不到其他皇后)的位置组合,首先必须定义冲突是什么。为何不使用一个函数来定义呢?
函数conflict接受(用状态元组表示的)既有皇后的位置,并确定下一个皇后的位置是否会 导致冲突。

def conflict(state, nextX):nextY = len(state)for i in range(nextY):if abs(state[i] - nextX) in (0,nextY - i):return Truereturn False

参数nextX表示下一个皇后的水平位置(x坐标,即列),而nextY为下一个皇后的垂直位置(y 坐标,即行)。这个函数对既有的每个皇后执行简单的检查:如果下一个皇后与当前皇后的x坐标 相同或在同一条对角线上,将发生冲突,因此返回True;如果没有发生冲突,就返回False。
如果下一个皇后和当前皇后的水平距离为0(在同一列)或与它们的垂直距离相等(位于一 条对角线上),这个表达式就为真;否则为假。

9.6.5 基线条件

下面先来看基线条件:最后一个皇后。对于这个皇后,你想如何处理呢?假设你想找出所有 可能的解——给定其他皇后的位置,可将这个皇后放在什么位置(可能什么位置都不行)?可以 这样编写代码。

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

这段代码的意思是,如果只剩下最后一个皇后没有放好,就遍历所有可能的位置,并返回那 些不会引发冲突的位置。参数num为皇后总数,而参数state是一个元组,包含已放好的皇后的位置。

9.6.6 递归条件

处理好基线条件后,可在递归条件中假设来自更低层级(编号更大的皇后)的结果都是正确的。因此,只需在函数queens的前述实现中给if语句添加 一个else子句。
对于递归调用,向它提供的是由当前行上面的皇后位置组成的元组。对于当前皇后的 每个合法位置,递归调用返回的是由下面的皇后位置组成的元组。为了让这个过程不断进行下去, 只需将当前皇后的位置插入返回的结果开头。

def queens(num=8, state=()):for pos in range(num):if not conflict(state, pos):if len(state) == num-1:yield (pos,)       # (pos,)中的逗号必不可少(不能仅用圆括号将pos括起),这样得到的才是元组else:for result in queens(num, state + (pos,)):yield (pos,) + result
list(queens(4))
# [(1, 3, 0, 2), (2, 0, 3, 1)]
len(list(queens(8)))
# 92                八皇后问题有92个解法

Python基础教程-第9章-魔法方法、特性和迭代器相关推荐

  1. Python基础教程第七章学习笔记——更加抽象

    7 更加抽象-创建自己的对象 前面讲了: Python主要的内建对象类型(数字.字符串.列表.元组和字典) 内建函数和标准库的用法 自定义函数的方式 本章主讲: 创建自己的对象(尤其是类型或者被称为类 ...

  2. python基础读后感_《python基础教程 》第一章 读书笔记

    python是一个简单强大的直译语言,它同样提供交互式编译环境,学起来还算有趣,在学习的过程中,同样体会了动态语言的直接与强大. 第一章 基础知识 一 运行python 在ubuntu终端输入 pyt ...

  3. 【书山有路】Python基础教程 第5章

    这章的主题是"条件.循环和其他语句".主要介绍if for之类的语句的使用,能大大丰富可以做的事情. 赋值魔法 序列解包 多个赋值操作可以同时进行: >>> x, ...

  4. Python基础教程-第6章-函数

    6.1 自定义函数 函数执行特定的操作并返回一个值1,你可以调用它.一般而言,要判断某个对象是否可调用,可使用内置函数callable. import math x = 1 y = math.sqrt ...

  5. Python基础教程-第4章-字典

    可通过名称来访问其各个值的数据结构.这种数据结构称为映射(mapping).字典是Python中唯一的内置映射类型,其中的值不按顺序排列,而是存储在键下. 4.1 字典的用途 字典(日常生活中的字典和 ...

  6. 杨桃的Python基础教程——第4章:Python基础语法

    本人CSDN博客专栏:https://blog.csdn.net/yty_7 Github地址:https://github.com/yot777/Python-Primary-Learning 4. ...

  7. 杨桃的Python基础教程——第1章:Python简介

    本人CSDN博客专栏:https://blog.csdn.net/yty_7 Github地址:https://github.com/yot777/Python-Primary-Learning Py ...

  8. Python基础教程 | 第三章 字符串

    1. 基本字符串操作 标准序列操作:索引.分片.乘法. 成员资格.长度.最大最小值 >>> website = 'www.baidu.com' >>> websit ...

  9. Python基础教程第1章:基础知识---学习记录

    1.python官方学习相关资料 (1) 下载安装python,见python官网:https://www.python.org/downloads/ (2) python官方文档 1)chm格式,安 ...

最新文章

  1. 如何规划创建一个家庭实验室
  2. 翻译:创建 Windows8 应用 Part I: Hello, world!
  3. 面试前需要准备的五个步骤
  4. return另外一个用法
  5. azw3转换为pdf_干货:如何Java 将 Word 文档转换为 PDF
  6. 利用MATLAB对数据进行切片并绘制图表
  7. gnome桌面环境 kde桌面环境的区别
  8. linux 多线程超时中断,c#中的线程超时
  9. java递归实现汉字组词穷举_Javascript迭代、递推、穷举、递归常用算法实例讲解...
  10. cad剖切线的快捷键_CAD快捷键记不住怎么办?顶级绘图员教你,从此不求人
  11. 传智播客黑马Java学习笔记_day07
  12. 看ftp服务器文件日期,ftp查看服务器当前日期
  13. 前端js遍历map获取key与value
  14. Linux下useradd命令与adduser命令的区别(adduser更适合初级使用者,useradd比较适合有些高阶经验的使用者)
  15. IPV4服务器如何支持ipv6访问
  16. 安徽大学计算机学院高亮,计算机学院关于智能计算的大规模优化学术报告圆满结束...
  17. 宣传折页设计三折页宣传册(案例分享)(版权归redtrans所有,请勿私用)
  18. LT9611 MIPI转HDMI芯片,方案成熟,提供技术支持
  19. 25则“验尸报告”— 创业失败者启示录
  20. 汇编 - 环境设置(Environment Setup)

热门文章

  1. html直接加载二维码图片
  2. ULEI Token(友利代币)
  3. PostgreSQL系列生成函数generate_series
  4. mysql创建date类型的字段_mysql创建date数据类型
  5. 智能锁语音提示芯片?看这里!
  6. 讯飞云语音-常见问题汇总
  7. vo类,model类,dto类的作用及划分
  8. unity开发VR的项目01——环境配置(unity2020.3)
  9. 微信支付python版2.0_刷卡支付-翟东平-专题视频课程
  10. CF烟雾头NVIDIA控制面板调节