《Python基础教程》读书笔记09-魔法方法、属性和迭代器
构造方法
一个对象被创建后,会立即调用构造方法。
class FooBar:
def __init__(self): # 也可以有参数
self.somevar = 42
>>> f = FooBar()
>>> f.somevar
42
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(SongBird, self).__init__() # Python3 可以 super().__init__(),super 函数不带任何参数调用
# 在老版本中没有 super 函数,可以 Bird.__init__(self),新式类中就不要这么做了,因为如果继承了多个类
self.sound = 'Squawk!'
def sing(self):
print (self.sound)
>>> sb = SongBird()
>>> sb.sing()
Squawk!
>>> sb.eat()
Aaaah...
基本的序列和映射规则
如果对象是不可变,只有两个方法,可变 4 个方法
__len__(self)
:返回集合中的元素数,如果__len__
返回0
(而且没重写该行为的__nonzero__
),对象会被当作一个布尔变量中的假值。__getitem__(self, key)
:返回所给键对应的值。__setitem__(self, key, value)
:__delitem__(self, key)
:在对象使用 del 语句时被调用,同时必修删除和键相关的键。
对于这些方法的附加要求:
- 对于一个序列,如果键是负数,那么要从末尾开始计数,即
x[-n]
和x[len(x)-n]
是一样的 - 如果键是不合适的类型(如:序列使用字符串作为键),会引发一个 TypeError 异常。
- 如果索引超出了范围,引发 IndexError 异常。
子类化列表,字典和字符串
标准库有3个关于序列和映射规则(UserList、UserString 和 UserDict)可以立即使用的实现
也可以子类化内建类型(list、str 和 dict)
属性
通过访问器定义的特性被称为属性
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self, size):
self.width, self.height = size
def getSize(self):
return self.width, self.height
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.getSize()
(10, 5)
>>> r.setSize((150, 100))
>>> r.width
150
上面的例子中,如果 size 需要称为真正的特性,并不修改 getSize 和 setSize 方法
property 函数
只需要添加一句话
size = property(getSize, setSize)
size 特性仍然取决于 getSize 和 setSize 中的计算,但他们看起来就像普通的属性一样。
property
函数有四个参数(fget, fset, fdel, doc)
- 没有参数,产生的属性不可读不可写
- 只有一个参数调用(一个取值的方法),产生的属性只读
- 只有第二个参数(用关键字参数),属性只写
- 第三个参数是一个用于删除的特性的方法(这个方法不要参数)
- 第四个参数是一个文档字符串
实际上,property
函数不是一个真正的函数,它是其实例拥有很多特殊方法的类。__get__
, __set__
和 __delete__
,这3个方法合在一起,就定义了描述符的规则。实现了其中任何一个方法的对象就叫描述符。描述符的特殊之处在于它们如何被访问的,如,程序读取一个特性时(尤其是在实例中访问该特性,但该特性在类中定义时),如果该特性被绑定到实现了 __get__
方法的对象上,那么就会调用 __get__
方法(结果值也会被返回)。
静态方法和类成员方法
静态方法 没有 self 参数
类成员方法 需要有 cls 参数,可以由类的对象调用,但 cls 参数是自动被绑定到类的
class MyClass:
@staticmethod
def smeth():
print ("静态方法")
# smeth = staticmethod(smeth) # 也可以这样
@classmethod
def cmeth(cls):
print ("类成员方法", cls)
# cmeth = classmethod(cmeth) # 也可以这样
>>> MyClass.smeth()
静态方法
>>> MyClass.cmeth()
类成员方法 <class '__main__.MyClass'>
>>> x = MyClass()
>>> x.smeth() # 对象也可以调用
静态方法
>>> x.cmeth()
类成员方法 <class '__main__.MyClass'>
拦截对象的所有特性访问
__getattribute__(self, name)
:当特性 name 被访问时自动被调用__getattr__(self, name)
:当特性 name 被访问且对象没有相应的特性时被调用__setattr__(self, name, value)
:当试图给特性 name 赋值时会被自动调用__delattr__(self, name)
:当试图删除特性 name 时被自动调用
>>> 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 # __dict__方法包含一个字典,是所有实例的属性
def __getattr__(self, name):
if name == 'size':
return self.width, self.height
else:
raise AttributeError
>>> r = Rectangle()
>>> r.size
(0, 0)
>>> r.width = 100
>>> r.height = 200
>>> r.size
(100, 200)
>>> r.size = (10,20)
>>> r.width
10
__setattr__
方法在所涉及的特性不是 size 时也会被调用。__getattr__
方法只在普通的特性没有被找到的时候调用,可以使用 hasattr,getattr 函数找到 size 属性。__getattribute__
方法会拦截所有特性的访问,也拦截__dict__
的访问。访问__getattribute__
中与self
相关的特性时,使用超类的__getattribute__
方法(super 函数)是唯一安全的途径。
迭代器
__iter__
方法是迭代器规则的基础,返回一个迭代器(iterator),所谓的迭代器就是具有 next
方法(不需要参数)的对象。在调用 next
方法时,迭代器会返回它的下一个值。
Python3.0,迭代器应该实现 __next__
方法,而不是 next。而新的内建函数 next
可以访问这个方法,即:next(it)
等同于之前的版本中的 it.next()
。
一个实现了 __iter__
方法的对象是可迭代的,一个实现了 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
>>> fibs = Fibs()
>>> for f in fibs:
if f > 1000:
print (f)
break
1597
内建函数 iter
可以从可迭代的对象中获得迭代器
>>> it = iter([1, 2, 3])
>>> next(it) #Pyhton2.7 it.next()
1
从迭代器得到序列
使用 list
构造方法将迭代器转化为列表
>>> class TextIterator:
value = 0
def __next__(self):
self.value += 1
if self.value > 10: raise StopIteration
return self.value
def __iter__(self):
return self
>>> t = TextIterator()
>>> list(t)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
生成器 generator
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
>>> L = [x * x for x in range(10)] # list
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10)) # generator
>>> g
<generator object <genexpr> at 0x1022ef630>
# 可以通过next()函数获得generator的下一个返回值:
>>> next(g)
0
...
# 直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的错误。
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
按顺序打印列表中的数字 nested = [[1, 2], [3, 4], [5]]
>>> def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
return 123 # for 循环,无法获得该值
>>> 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]
用 for 循环调用 generator 时,发现拿不到 generator 的 return 语句的返回值。如果想要拿到返回值,必须捕获 StopIteration
错误,返回值包含在 StopIteration
的 value
中:
>>> a = flatten(nested)
>>> next(a)
1
>>> next(a)
2
>>> a.__next__()
3
>>> a.__next__()
4
>>> a.__next__()
5
>>> try:
x = next(a)
except StopIteration as e:
print('Generator return value:', e.value)
Generator return value: 111
任何包含 yield
语句的函数称为生成器。每次产生一个值,函数就会被冻结:即函数停在那点等待被重新唤醒。
生成器推导式
>>> g = ((i+2)**2 for i in range(2,27))
>>> next(g) # Python2.7 g.next()
16
和列表推导式的不同,使用普通括号 ()
,这个例子还是列表推导式好,但如果生成大量的值,还是生成器推导式好。
在函数调用中,不用另外加一对括号 sum(i**2 for i in range(0,10))
递归生成器
如果要处理任意层的嵌套。
>>> def flatten(nested):
try:
# 不要迭代类似字符串的对象
try: nested + '' # 这是检查一个对象是不是类似于字符串对象的最简单最快速的方法
except TypeError: pass
else: raise TypeError # 检查字符串 end
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]))
[1, 2, 3, 4, 5, 6, 7, 8]
生成器方法
send
方法,生成器和外部进行交流的渠道。需要一个参数(需要发送的消息——任意对象)
在内部挂起生成器(yield
方法执行一次),yield
作为表达式而不是语句使用。
即:当生成器重新运行的时候,yield
方法返回一个值,也就是外部通过 send
方法发送的值,如果 next
方法被使用,那么 yield
方法返回 None
。
>>> def re(value):
while True:
new = (yield value) # 安全起见,加上括号
if new is not None:
print ('yield--Send')
value = new
>>> r = re(42)
>>> r.next()
42
>>> r.next() # 因为while True,可以重复调用r.next(),yield方法返回None
42
>>> r.send('hello, world!')
yield--Send
'hello, world!'
>>> nested = [[1, 2], [3]]
>>> def flatten(nested):
for sublist in nested:
for element in sublist:
new = (yield element)
if new is not None:
print ('new = ' + new)
# element = new # 这里不知道怎么可以让迭代器返回 new
>>> x = flatten(nested)
>>> x.next()
1
>>> x.send('test')
new = test
2
>>> x.send('test2')
new = test2
3
>>> x.send('test3')
new = test3
Traceback (most recent call last):
File "<pyshell#113>", line 1, in <module>
x.send('test5')
StopIteration
生成器的两个方法
- throw 方法,使用异常类型调用,还有可选值以及回溯对象,用于在生成器中引发一个异常(在 yield 表达式中)
- close 方法,调用时不用参数,用于停止生成器
模拟生成器
>>> def flatten(nested):
result = []
try:
for sublist in nested:
for element in flatten(sublist):
result.append(element)
except TypeError:
result.append(nested)
return result
八皇后问题
水平垂直对角线,只能有一个皇后,n 个皇后则棋盘 n*n
# 判断下一个的皇后位置会不会有冲突
def conflict(state, nextX): # nextX:X坐标;如果state[0] = 3,代表第一行的皇后在第四列
nextY = len(state) # nextY:Y坐标
for i in range(nextY):
if abs(state[i] - nextX) in (0, nextY - i):
return True
return False
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
>>> list(queens(4))
[(1, 3, 0, 2), (2, 0, 3, 1)]
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Python基础教程》读书笔记09-魔法方法、属性和迭代器
文章字数:2.8k
本文作者:Bin
发布时间:2017-01-01, 22:39:34
最后更新:2019-10-12, 16:11:24
原始链接:http://coolview.github.io/2017/01/01/Python%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B/%E3%80%8APython%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B009-%E9%AD%94%E6%B3%95%E6%96%B9%E6%B3%95%E3%80%81%E5%B1%9E%E6%80%A7%E5%92%8C%E8%BF%AD%E4%BB%A3%E5%99%A8/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。