* ,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,它新增了鸣叫功能(叽叽喳喳!)
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
这个类中,没有定义 del 和 len , 因此不能删除元素,且序列长度是无穷的
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 getattr、setattr 等方法
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。具体的实现是,任何对象只要定义了iter和next方法,那就是迭代器对象;迭代器表示一个惰性计算的序列,需要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...