cook book:7:函数+8:类与对象
1:可接受任意数量参数的函数 *args
# 为了能让一个函数接受任意数量的位置参数,可以使用一个*参数 def avg(first, *args): return (first + sum(args)) / (1 + len(args)) print(avg(1, 2)) # 1.5 print(avg(1, 2, 3, 4)) # 2.5 # *args接收后面的所有的位置参数封装成一个元组到args这个变量里 # args是由所有其他位置参数组成的元组。然后我们在代码中把它当成了一个序列来进行后续的计算
# 接受任意数量的关键字参数的函数,**kwargs import html def make_element(name, value, **attrs): keyvals = [' %s="%s"' % item for item in attrs.items()] # [' size="large"', ' quantity="6"'] attr_str = ''.join(keyvals) # size="large" quantity="6" str类型 element = '<{name}{attrs}>{value}</{name}>'.format( name=name, attrs=attr_str, value=html.escape(value)) return element # 创建 '<item size="large" quantity="6">Albatross</item>' print(make_element('item', 'Albatross', size='large', quantity=6)) # 创建 '<p><spam></p>' print(make_element('p', '<spam>')) # **attrs接收所有的关键字参数封装成一个字典到attrs里面 # attrs是一个包含所有被传入进来的关键字参数的字典
# 同时接受任意数量的位置参数和关键字参数,同时使用*和** def anyargs(*args, **kwargs): print(args) # args是一个元组 print(kwargs) # kwargs是一个字典 使用这个函数时,所有位置参数会被放到args元组中,所有关键字参数会被放到字典kwargs中。
# 一个*参数只能出现在函数定义中最后一个位置参数后面,而 **参数只能出现在最后一个参数。在*参数后面仍然可以定义其他参数 def a(x, *args, y): pass
print(a(1, 2, 3, 4, 5, y=10)) # 只能这么调用 def b(x, *args, y, **kwargs): pass print(a(1, 2, 3, 4, 5, y=10, c=20, b=30)) # 只能这么调用 强制关键字参数
2:只接受关键字参数的函数 希望函数的某些参数强制使用关键字参数传递
# 将强制关键字参数放到某个*参数或者单个*后面就能达到这种效果 def recv(maxsize, *, block): pass # recv(1024) # TypeError 还需要传一个参数 # recv(1024, 2048, block=True) # TypeError 报错,*不是*args,*不接受参数只是强制后面得参数和关键字参数 recv(1024, block=True) # Ok # 在接受任意多个位置参数的函数中指定关键字参数 def minimum(*values, clip=None): m = min(values) if clip is not None: m = clip if clip > m else m return m print(minimum(1, 5, 2, -5, 10)) # -5 print(minimum(1, 5, 2, -5, 10, clip=0)) # 0 # *vaues接收多个位置参数,clip=None接收一个关键字参数
# 使用强制关键字参数会比使用位置参数表意更加清晰,程序也更加具有可读性 msg = recv(1024, False) # 如果调用者对recv函数并不是很熟悉,那他肯定不明白那个False参数到底来干嘛用的。 但是,如果代码变成下面这样子的话就清楚多了 msg = recv(1024, block=False)
# 使用强制关键字参数也会比使用**kwargs参数更好,因为在使用函数help的时候输出也会更容易理解
help(recv)
3:给函数参数增加元信息 写好了一个函数,然后想为这个函数的参数增加一些额外的信息,让其他使用者就能清楚的知道这个函数应该怎么使用
# 函数参数注解提示程序员应该怎样正确使用这个函数 def add(x: int, y: int) -> int: return x + y x是int,y是int ->返回是int # python解释器不会对这些注解添加任何的语义。它们不会被类型检查,运行时跟没有加注解之前的效果也没有任何差距。
然而,对于那些阅读源码的人来讲就很有帮助。第三方工具和框架可能会对这些注解添加语义。同时它们也会出现在文档中 输出: Help on function add in module __main__: add(x: int, y: int) -> int None
可以使用任意类型的对象给函数添加注解(例如数字,字符串,对象实例等等),不过通常来讲使用类或者字符串会比较好点
# 函数注解只存储在函数的 __annotations__ 属性中 def add(x: int, y: int) -> int: return x + y print(add.__annotations__) 输出: {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>} # 注解的使用方法可能有很多种,但是它们的主要用途还是文档。 因为python并没有类型声明,
通常来讲仅仅通过阅读源码很难知道应该传递什么样的参数给这个函数。
这时候使用注解就能给程序员更多的提示,让他们可以正确的使用函数
4: 返回多个值的函数 构造一个可以返回多个值的函数 return一个元组就行了
# return一个元组就行了 def func(): return 1, 2, 3 a = func() print(a) # (1, 2, 3) a, b, c = func() print(a, b, c) # 1 2 3 # 尽管myfun()看上去返回了多个值,实际上是先创建了一个元组然后返回的。 # 这个语法看上去比较奇怪,实际上我们使用的是逗号来生成一个元组,而不是用括号 a = (1, 2) b = 1, 2 print(a == b, b) # True (1, 2) # 当我们调用返回一个元组的函数的时候 ,通常我们会将结果赋值给多个变量,
其实这就是元组解包。返回结果也可以赋值给单个变量, 这时候这个变量值就是函数返回的那个元组本身了 def myfun(): return 1, 2, 3 x = myfun() print(x) # (1, 2, 3) a, b, c = x print(a, b, c) # 1 2 3
5:定义有默认参数的函数 定义一个函数或者方法,它的一个或多个参数是可选的并且有一个默认值
# 定义一个有可选参数的函数是非常简单的,直接在函数定义中给参数指定一个默认值,并放到参数列表最后就行了 def spam(a, b=42): print(a, b) spam(1) # 1, 42 spam(1, 2) # 1, 2 # 如果默认参数是一个可修改的容器比如一个列表、集合或者字典,可以使用None作为默认值 # 使用列表作为默认值 def spam(a, b=None): if b is None: b = [] # 不想提供一个默认值,而是想仅仅测试下某个默认参数是不是有传递进来, _no_value = object() def spam(a, b=_no_value): if b is _no_value: print('No b value supplied') spam(1) 输出:No b value supplied spam(1, 2) # b = 2 spam(1, None) # b = None # 传递一个None值和不传值两种情况是有差别的
# 默认参数的值仅仅在函数定义的时候赋值一次后面不会复制了 x = 42 def spam(a, b=x): print(a, b) spam(1) # 1 42 x = 23 spam(1) # 1 42 # 改变x的值的时候对默认参数值并没有影响,这是因为在函数定义的时候就已经确定了它的默认值了
# 默认参数的值应该是不可变的对象,比如None、True、False、数字或字符串,不要写个列表[] def spam(a, b=[]): pass # 默认值b=[]这样写了,当默认值在其他地方被修改后你将会遇到各种麻烦。这些修改会影响到下次调用这个函数时的默认值 def spam(a, b=[]): print(b) return b x = spam(1) print(x) # 输出:[]现在函数返回的是[] x.append(99) x.append('hello') print(x) # 输出:[99, 'hello'] # 为了避免这种情况的发生,最好是将默认值设为None,
# 测试None值时使用 is 操作符是很重要的 def spam(a, b=None): if not b: # 不改用“b为无” b = [] print(b) # 尽管None值确实是被当成False, 但是还有其他的对象(比如长度为0的字符串、列表、元组、字典等)都会被当做False。 # 因此,上面的代码会误将一些其他输入也当成是没有输入。 spam(1) x = [] spam(1, x) # 无声错误。默认情况下会覆盖x值 spam(1, 0) # 无声错误。0被忽略 spam(1, '') # 无声的错误,忽略
# 一个函数需要测试某个可选参数是否被使用者传递进来。 这时候需要小心的是你不能用某个默认值比如None、
0或者False值来测试用户提供的值(因为这些值都是合法的值,是可能被用户传递进来的)。 因此,你需要其他的解决方案了
# 创建一个独一无二的私有对象实例,就像上面的_no_value变量那样。
在函数里面,你可以通过检查被传递参数值跟这个实例是否一样来判断。
这里的思路是用户不可能去传递这个_no_value实例作为输入。 因此,这里通过检查这个值就能确定某个参数是否被传递进来了
# object 是python中所有类的基类。 你可以创建 object 类的实例,
但是这些实例没什么实际用处,因为它并没有任何有用的方法, 也没有任何实例数据(因为它没有任何的实例字典,你甚至都不能设置任何属性值)。
你唯一能做的就是测试同一性。这个刚好符合要求,因为我在函数中就只是需要一个同一性的测试而已
6:定义匿名或内联函数 lambda表达式定义简单函数
add = lambda x, y: x + y add(2, 3) # 5 add('hello', 'world') # 'helloworld' # 这个lambda函数类似 def add(x, y): return x + y add(2, 3) # 5
# lambda表达式典型的使用场景是排序或数据reduce names = ['David Beazley', 'Brian Jones', 'Raymond Hettinger', 'Ned Batchelder'] print(sorted(names)) # ['Brian Jones', 'David Beazley', 'Ned Batchelder', 'Raymond Hettinger'] print(sorted(names, key=lambda name: name.split()[-1].lower())) # ['Ned Batchelder', 'David Beazley', 'Raymond Hettinger', 'Brian Jones'] a = [5, 4, 2, 3, 1] a.sort() print(a) # [1, 2, 3, 4, 5] # sorted(xxx)不会改变原数据,会返回一个排序后的数据对象 # xxx.sort()会改变原始数据,
7:匿名函数捕获变量值 lambda定义了一个匿名函数,并想在定义时捕获到某些变量的值
x = 10 a = lambda y: x + y x = 20 b = lambda y: x + y print(a(10)) # 30 print(b(10)) # 30 # 都是返回30, # lambda表达式中的x是一个自由变量, 在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。
因此,在调用这个lambda表达式的时候,x的值是执行时的值,执行两个lambda匿名函数的数据x=20 x = 10 a = lambda y: x + y x = 20 b = lambda y: x + y print(a(10)) # 30 print(b(10)) # 30 x = 15 print(a(10)) # 25 x = 3 print(a(10)) # 3 # 匿名函数在定义时就捕获到值,将那个参数值定义成默认参数即可 x = 10 a = lambda y, x=x: x + y x = 20 b = lambda y, x=x: x + y print(a(10)) # 20 print(b(10)) # 30
类似函数定义下面情况
x = 10
def func1(y):
print(x+y)
x = 20
def func2(y):
print(x+y)
func1(10) # 30
func2(10) # 30
# x只有再调用的时候才会赋值,而不会在定义的时候赋值,所有是30,30
x = 10
def func1(y, x=x):
print(x+y)
x = 20
def func2(y, x=x):
print(x+y)
func1(10) # 20
func2(10) # 30
# x在定义的时候就被赋值了,而不是等到func1()被调用的时候赋值,所有书20,30
# 通过在一个循环或列表推导中创建一个lambda表达式列表,并期望函数能在定义时就记住每次的迭代值 funcs = [lambda x: x+n for n in range(5)] for f in funcs: print(f(0)) 输出: 4 4 4 4 4 # 实际效果是运行是n的值为迭代的最后一个值 列表生成式只是定义了5个lambda函数,n的值没有赋值到x+n的n里面去 定义五个lambda函数函数后现在n=4了,这个列表里面存储了5个lambda函数,for循环遍历取出每个lambda传递参数0,所以结果是0+4=4 # 如果想结果改变可以写个n的关键字参数 funcs = [lambda x, n=n: x+n for n in range(5)] for f in funcs: print(f(0)) 输出: 0 1 2 3 4 # 这样写每个lambda函数就会先把每个n的值定义好了,每个lambda存储的n的值都不同
8:减少可调用对象的参数个数 被其他python代码使用的callable对象,可能是一个回调函数或者是一个处理器, 但是它的参数太多了,导致调用时出错 functools.partial()
# 减少某个函数的参数个数,你可以使用 functools.partial() 。
partial() 函数允许你给一个或多个参数设置固定的值,减少接下来被调用时的参数个数 from functools import partial def spam(a, b, c, d): print(a, b, c, d) spam(1, 2, 3, 4) # 1 2 3 4 s1 = partial(spam, 1) # a=1 s1(2, 3, 4) # 1 2 3 4 s2 = partial(spam, d=42) # d=42 s2(1, 2, 3) # 1 2 3 42 s3 = partial(spam, 1, 2, d=42) # a = 1, b = 2, d = 42 s3(3) # 1 2 3 42 # partial() 固定某些参数并返回一个新的callable对象。这个新的callable接受未赋值的参数,
然后跟之前已经赋值过的参数合并起来,最后将所有参数传递给原始函数
# 有一个点的列表来表示(x,y)坐标元组。 使用下面的函数来计算两点之间的距离 import math from functools import partial points = [(1, 2), (3, 4), (5, 6), (7, 8)] def distance(p1, p2): x1, y1 = p1 x2, y2 = p2 return math.hypot(x2 - x1, y2 - y1) # 想以某个点为基点,根据点和基点之间的距离来排序所有的这些点。 # 列表的 sort() 方法接受一个关键字参数来自定义排序逻辑, # 但是它只能接受一个单个参数的函数(distance()很明显是不符合条件的)。 # 现在我们可以通过使用 partial() 来解决这个问题 pt = (4, 3) # 基点 points.sort(key=partial(distance, pt)) # 根据points列表里面的点和pt这个基点的距离来进行排序 print(points) # [(3, 4), (1, 2), (5, 6), (7, 8)] points.sort() # 默认会根据points这个列表里面每个元素的第一位来进行排序 print(points) # [(1, 2), (3, 4), (5, 6), (7, 8)] points.sort(key=lambda x: x[-1]) # 传匿名函数参数让根据这个point点的后面的值来排序 print(points) # [(1, 2), (3, 4), (5, 6), (7, 8)]
# partial() 通常被用来微调其他库函数所使用的回调函数的参数 # 使用 multiprocessing 来异步计算一个结果值, 然后这个值被传递给一个接受一个result值和一个可选logging参数的回调函数: def output_result(result, log=None): if log is not None: log.debug('Got: %r', result) def add(x, y): return x + y if __name__ == '__main__': import logging from multiprocessing import Pool # 创建进程的模块,Pool进程池 from functools import partial logging.basicConfig(level=logging.DEBUG) # 设置log等级 log = logging.getLogger('test') # 创建一个log对象 p = Pool() p.apply_async(add, (3, 4), callback=partial(output_result, log=log)) # callback启动回调函数,每个子进程运行函数有了结果后子进程再回调partial这个函数继续运行 # partial指定output_result这个函数的的log参数为log对象,后面类似调用output_result(result, log=log),只需要传递一个参数 # result是每个进行执行完成后的返回值传递给output_result函数来回调 p.close() # 关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成 p.join() # 等待所有工作进程退出。此方法只能在close()或teminate()之后调用给apply_async()
提供回调函数时,通过使用partial()
传递额外的logging
参数。
而multiprocessing
对这些一无所知——它仅仅只是使用单个值来调用回调函数
# 编写网络服务器,socketserver 模块让它变得很容易 # echo服务器 from socketserver import StreamRequestHandler, TCPServer class EchoHandler(StreamRequestHandler): def handle(self): for line in self.rfile: # 按行读取,一点要读取到\r\n才会停止本次循环执行下面的wfile操作 # 相当于把文件描述符分成了两份,一个rfile读,一个write写,类似socket的recv和send self.wfile.write(b'GOT:' + line) # EchoHandler类里面的handle定义其他用户连接进来后的处理函数, # 连进来后就for line in self.rfile,类似从文件句柄中按行读取,读取到了一个\r\n就write发送 serv = TCPServer(('', 15000), EchoHandler) serv.serve_forever() # 给EchoHandler增加一个可以接受其他配置选项的 __init__ 方法 from socketserver import StreamRequestHandler, TCPServer class EchoHandler(StreamRequestHandler): # ack是仅添加关键字的参数, *args, **kwargs可以接收提供的任何正常参数(已传递) def __init__(self, *args, ack, **kwargs): self.ack = ack super().__init__(*args, **kwargs) # 返回上一层 def handle(self): for line in self.rfile: self.wfile.write(self.ack + line) # 这么修改后,不需要显式地在TCPServer类中添加前缀了。 但是再次运行程序后会报类似下面的错误 serv = TCPServer(('', 15000), EchoHandler) serv.serve_forever() # 这样运行会报错,少了一个参数TypeError: __init__() missing 1 required keyword-only argument: 'ack' serv = TCPServer(('', 15000), partial(EchoHandler, ack=b'RECEIVED:')) serv.serve_forever() # 这样写运行不会报错,使用partial指定了ack的参数了,使用partial()
就能很轻松的解决,给它传递ack
参数的值来初始化即可
#__init__()
方法中的ack参数声明方式其实就是声明ack为一个强制关键字参数
# 很多时候 partial() 能实现的效果,lambda表达式也能实现 points.sort(key=lambda p: distance(pt, p)) p.apply_async(add, (3, 4), callback=lambda result: output_result(result,log)) serv = TCPServer(('', 15000), lambda *args, **kwargs: EchoHandler(*args, ack=b'RECEIVED:', **kwargs)) # 使用 partial() 可以更加直观的表达你的意图(给某些参数预先赋值)
9:将单方法的类转换为函数 一个除 __init__()
方法外只定义了一个方法的类。为了简化代码,你想将它转换成一个函数
# 可以使用闭包来将单个方法的类转换成函数 # 许使用者根据某个模板方案来获取到URL链接地址的类 from urllib.request import urlopen class UrlTemplate: def __init__(self, template): self.template = template def open(self, **kwargs): return urlopen(self.template.format_map(kwargs)) # format_map类似format,只能格式化字典 # 雅虎下载股票数据 yahoo = UrlTemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}') for line in yahoo.open(names='IBM,AAPL,FB', fields='sl1c1v'): print(line.decode('utf-8')) str_a = 'http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}' print(str_a.format_map({"names": 'IBM,AAPL,FB', "fields": 'sl1c1v'})) # 输出拼接后的结果:http://finance.yahoo.com/d/quotes.csv?s=IBM,AAPL,FB&f=sl1c1v print('zzz{name}zzz'.format(name="aaa")) # 输出:zzzaaazzz # for循环遍历一个文件句柄或者socket,一定会遍历到\r\n才会停止认为是一行,就是一次for循环然后运行for循环里面内容继续下次for循环 # 上面的访问url的类可以被一个更简单的函数来代替 from urllib.request import urlopen def urltemplate(template): def opener(**kwargs): return urlopen(template.format_map(kwargs)) return opener yahoo = urltemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}') for line in yahoo(names='IBM,AAPL,FB', fields='sl1c1v'): print(line.decode('utf-8')) # 类似装饰器的前身,urltemplate()函数的运行返回opener这个函数的内存地址
现在yahoo变量就指向opener这个函数的内存地址,yahoo()函数运行相当于opener()这个函数运行
类似闭包的使用
# 部分情况下,你拥有一个单方法类的原因是需要存储某些额外的状态来给方法使用。
定义UrlTemplate类的唯一目的就是先在某个地方存储模板值,以便将来可以在open()方法中使用
# 使用一个内部函数或者闭包的方案通常会更优雅一些。一个闭包就是一个函数,
只不过在函数内部带上了一个额外的变量环境。闭包关键特点就是它会记住自己被定义时的环境。
因此,在我们的解决方案中,opener() 函数记住了 template 参数的值,并在接下来的调用中使用它
# 任何时候只要你碰到需要给某个函数增加额外的状态信息的问题,都可以考虑使用闭包。
相比将你的函数转换成一个类而言,闭包通常是一种更加简洁和优雅的方案
10:带额外状态信息的回调函数 回调函数的使用:事件处理器、等待后台任务完成后的回调,需要让回调函数拥有额外的状态值,以便在它的内部使用到
# 定义一个需要调用回调函数的函数 def apply_async(func, args, *, callback): # 计算结果 result = func(*args) # 使用结果调用回调函数callback callback(result) def print_result(result): print('Got:', result) def add(x, y): return x + y apply_async(add, (2, 3), callback=print_result) # Got: 5 apply_async(add, ('hello', 'world'), callback=print_result) # Got: helloworld # print_result() 函数仅仅只接受一个参数 result 。不能再传入其他信息。 而当你想让回调函数访问其他变量或者特定环境的变量值的时候就会遇到麻烦 # 让回调函数访问外部信息,一种方法是使用一个绑定方法来代替一个简单函数 # 类保存一个内部序列号,每次接收到一个 result 的时候序列号加1 def apply_async(func, args, *, callback): result = func(*args) callback(result) def add(x, y): return x + y class ResultHandler: def __init__(self): self.sequence = 0 def handler(self, result): self.sequence += 1 print('[{}] Got: {}'.format(self.sequence, result)) # 先创建一个类的实例,然后用它的 handler() 绑定方法来做为回调函数 r = ResultHandler() apply_async(add, (2, 3), callback=r.handler) # 输出[1] Got: 5 # 类的替代,使用一个闭包捕获状态值 def apply_async(func, args, *, callback): result = func(*args) callback(result) def add(x, y): return x + y def make_handler(): sequence = 0 def handle(result): nonlocal sequence sequence += 1 print('[{}] Got: {}'.format(sequence, result)) return handle handle = make_handler() apply_async(add, (2, 3), callback=handle) # 输出:[1] Got: 5
# 使用协程(实际上是生成器) def apply_async(func, args, *, callback): result = func(*args) callback(result) def add(x, y): return x + y def make_handler(): sequence = 0 while True: result = yield sequence += 1 print('[{}] Got: {}'.format(sequence, result)) # 对于协程,你需要使用它的 send() 方法作为回调函数 # 这里是使用一个yield生成器(生成器实现方式类似协程),传递生成器的send方法为回调,每次回调调用send(xxx)一个值给生成器就会print打印一次 # 下次再send(xxx)然后继续打印 handler = make_handler() print(next(handler)) # 没有这个会报错,第一次先走yield右边的部分,如果直接send没有可以接收的,这一步是生成器的初始化 apply_async(add, (2, 3), callback=handler.send) # [1] Got: 5
# 仅仅只需要给回调函数传递额外的值的话,还有一种使用 partial() 的方式也很有用。 在没有使用 partial() 的时候使用lambda表达式 apply_async(add, (2, 3), callback=lambda r: handler(r, seq))
11:内联回调函数 编写使用回调函数的代码的时候,担心很多小函数的扩张可能会弄乱程序控制流。 你希望找到某个方法来让代码看上去更像是一个普通的执行序列
# 通过使用生成器和协程可以使得回调函数内联在某个函数中 # 一个执行某种计算任务然后调用一个回调函数的函数 from queue import Queue from functools import wraps def apply_async(func, args, *, callback): result = func(*args) callback(result) class Async: def __init__(self, func, args): self.func = func self.args = args def inlined_async(func): @wraps(func) # @wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性 def wrapper(*args): f = func(*args) # func这里指代被装饰的func,func()返回的是一个迭代器,所以f是一个迭代器 result_queue = Queue() # 创建一个队列 result_queue.put(None) # 队列里加None元素 while True: result = result_queue.get() # 从队列里拿元素出来,先拿出一个None try: a = f.send(result) # f是个迭代器,往迭代器里send东西顺便拿出一个东西 # 这里第一个result是None,生成器第一次必须send None或者next(f)过滤掉第一次才不会报错 # a = 拿出一个生成器返回的元素Async(add, (2, 3)) # Async是一个类,add传给func函数名称,(2, 3)传给args是参数,Async()得到一个实例化对象赋值给a # 把实例化对象a取func属性和args属性传递给apply_async函数, # 回调函数是往队列里put增加元素,apply_async往队列里添加add函数运行后的结果 apply_async(a.func, a.args, callback=result_queue.put) except StopIteration: break return wrapper # Async和inlined_async这两个代码片段允许使用 yield 语句内联回调步骤,所有的调用步骤都在inlined_async这个函数里,
# 创建一个队列,从test生成器里取元素,然后给回调函数调用返回的结果传到生成器,然后再继续从生成器里取元素
def add(x, y): return x + y @inlined_async def test(): r = yield Async(add, (2, 3)) print(r) r = yield Async(add, ('hello', 'world')) print(r) for n in range(10): r = yield Async(add, (n, n)) print(r) print('Goodbye') test() 输出: 5 helloworld 0 2 4 6 8 10 12 14 16 18 Goodbye # 除了那个特别的装饰器和 yield 语句外,其他地方并没有出现任何的回调函数(其实是在后台定义的)
# 在需要使用到回调的代码中,关键点在于当前计算工作会挂起并在将来的某个时候重启(比如异步执行)。
当计算重启时,回调函数被调用来继续处理结果。apply_async() 函数演示了执行回调的实际逻辑,
实际情况中它可能会更加复杂(包括线程、进程、事件处理器等等)。 # 计算的暂停与重启思路跟生成器函数的执行模型不谋而合。
具体来讲,yield 操作会使一个生成器函数产生一个值并暂停。
接下来调用生成器的 __next__() 或 send() 方法又会让它从暂停处继续执行 # 核心就在 inline_async()装饰器函数中了。 关键点就是,装饰器会逐步遍历生成器函数的所有 yield 语句,每一次一个。
为了这样做,刚开始的时候创建了一个 result 队列并向里面放入一个 None 值。
然后开始一个循环操作,从队列中取出结果值并发送给生成器,它会持续到下一个 yield 语句,
在这里一个 Async 的实例被接受到。然后循环开始检查函数和参数,并开始进行异步计算 apply_async() 。
然而,这个计算有个最诡异部分是它并没有使用一个普通的回调函数,而是用队列的 put() 方法来回调 # 主循环立即返回顶部并在队列上执行 get() 操作。
如果数据存在,它一定是 put() 回调存放的结果。如果没有数据,那么先暂停操作并等待结果的到来。
这个具体怎样实现是由 apply_async() 函数来决定的。 如果你不相信会有这么神奇的事情,
可以使用 multiprocessing 库来试一下, 在单独的进程中执行异步计算操作 if __name__ == '__main__': import multiprocessing pool = multiprocessing.Pool() apply_async = pool.apply_async # Run the test function test() # 实际上你会发现这个真的就是这样的,但是要解释清楚具体的控制流得需要点时间了。 # 将复杂的控制流隐藏到生成器函数背后的例子在标准库和第三方包中都能看到。
比如,在 contextlib 中的 @contextmanager 装饰器使用了一个令人费解的技巧,
通过一个 yield 语句将进入和离开上下文管理器粘合在一起。 另外非常流行的 Twisted 包中也包含了非常类似的内联回调
回调:主函数把自己的代码段的引用以函数数参数的形式转给了一个它调用的函数,让那个函数在适当的时候根据这个引用调用自己的代码段
内联回调的基本知识:https://blog.csdn.net/ld326/article/details/78760772
12:访问闭包中定义的变量 扩展函数中的某个闭包,允许它能访问和修改函数的内部变量
# 闭包的内部变量对于外界来讲是完全隐藏的,可以通过编写访问函数并将其作为函数属性绑定到闭包上来实现这个目的 def sample(): n = 0 # 闭包函数 def func(): print('n=', n) # n的存取器方法,get是获取,set是设置n这个内部变量 def get_n(): return n def set_n(value): nonlocal n n = value # 附加为函数属性 func.get_n = get_n func.set_n = set_n return func f = sample() f() # n= 0 f.set_n(10) f() # n= 10 f.set_n(999) print(f.get_n()) # 999 f() # n= 999
# nonlocal 声明可以让我们编写函数来修改内部变量的值。 # 函数属性允许我们用一种很简单的方式将访问方法绑定到闭包函数上,这个跟实例方法很像(尽管并没有定义任何类) # 让闭包模拟类的实例,复制上面的内部函数到一个字典实例中并返回它 import sys class ClosureInstance: def __init__(self, locals=None): if locals is None: locals = sys._getframe(1).f_locals # sys._getframe(1).f_locals获取调用栈的全部信息 # sys._getframe(1)查看栈深度对象,f_locals查看这个深度为1的栈的全部信息 # 在Stack()函数里面调用的这个函数,那么获取Stack这个函数的信息如下: # {'push': <function Stack.<locals>.push at 0x00000202F42EC5E0>, # 'pop': <function Stack.<locals>.pop at 0x00000202F42EC550>, # '__len__': <function Stack.<locals>.__len__ at 0x00000202F42EC0D0>, # 'items': []} # 使用可调用项更新实例字典 self.__dict__.update((key, value) for key, value in locals.items() if callable(value)) # 把Stack函数可调用的方法全部更新到这个类里面来 # self.__dict__ = {'push': <function Stack.<locals>.push at 0x000001635B28C5E0>, # 'pop': <function Stack.<locals>.pop at 0x000001635B28C550>, # '__len__': <function Stack.<locals>.__len__ at 0x000001635B28C0D0>} # 重定向特殊方法 # 必须要重构__len__外面调用len(s)才不会报错,因为__dict__里面存储的是一个函数的__len__方法 # 需要ClosureInstance这个类自己编写一个__len__方法才能len(s)调用 # 不在类里重构__len__外部只能 s.__len__()调用,需要在类里把__len__写成一个实例方法, # def __len__(): 如果这样写就是普通的方法不行,不能使用len(s)调用这个双下方法 def __len__(self): return self.__dict__['__len__']() def Stack(): items = [] def push(item): items.append(item) def pop(): return items.pop() def __len__(): return len(items) return ClosureInstance() s = Stack() # print(s) # <__main__.ClosureInstance object at 0x00000218A9AEDFA0> s.push(10) s.push(20) s.push('Hello') print(len(s)) # 3 print(s.pop()) # 'Hello' print(s.pop()) # 20 print(s.pop()) # 10
# 这个代码运行起来会比一个普通的类定义要快很多 import sys class ClosureInstance: def __init__(self, locals=None): if locals is None: locals = sys._getframe(1).f_locals self.__dict__.update((key, value) for key, value in locals.items() if callable(value)) def __len__(self): return self.__dict__['__len__']() def Stack(): items = [] def push(item): items.append(item) def pop(): return items.pop() def __len__(): return len(items) return ClosureInstance() class Stack2: def __init__(self): self.items = [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def __len__(self): return len(self.items) if __name__ == '__main__': from timeit import timeit s = Stack() # 闭合的试验 print(timeit('s.push(1);s.pop()', 'from __main__ import s')) # 0.48261580000000004 # 类的测试 s1 = Stack2() print(timeit('s1.push(1);s1.pop()', 'from __main__ import s1')) # 0.5306188999999999 # 闭包的方案运行起来要快大概8%,大部分原因是因为对实例变量的简化访问, 闭包更快是因为不会涉及到额外的self变量
13:改变对象的字符串显示 改变对象实例的打印或显示输出,让它们更具可读性,__str__()
和 __repr__()
# 改变一个实例的字符串表示,可重新定义它的 __str__() 和 __repr__() 方法 class Pair: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) def __str__(self): return '({0.x!s}, {0.y!s})'.format(self) # __repr__() 方法返回一个实例的代码表示形式,通常用来重新构造这个实例。 # 内置的 repr() 函数返回这个字符串,跟我们使用交互式解释器显示的值是一样的。 # __str__() 方法将实例转换为一个字符串,使用 str() 或 print() 函数会输出这个字符串 >>> p = Pair(3, 4) >>> p Pair(3, 4) # __repr__() output >>> print(p) (3, 4) # __str__() output >>> # 格式化的时候怎样使用不同的字符串表现形式 # !r 格式化代码指明输出使用 __repr__() 来代替默认的 __str__() class Pair: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) def __str__(self): return '({0.x!s}, {0.y!s})'.format(self) p = Pair(3, 4) print('p is {0!r}'.format(p)) # p is Pair(3, 4) !r调用__repr__ print('p is {0}'.format(p)) # p is (3, 4) 普通的format调用__str__
# 自定义 __repr__() 和 __str__() 通常是很好的习惯,它能简化调试和实例输出 # __repr__() 生成的文本字符串标准做法是需要让 eval(repr(x)) == x 为真。
如果不能这样子做,应该创建一个有用的文本表示,并使用 < 和 > 括起来 f = open('file.dat') print(f) # <_io.TextIOWrapper name='file.dat' mode='r' encoding='cp936'> class Pair: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) def __str__(self): return '({0.x!s}, {0.y!s})'.format(self) p = Pair(3, 4) print(repr(p)) # Pair(3, 4) print(p) # (3, 4) print(eval(repr(p))) # (3, 4)
# eval() 函数用来执行一个字符串表达式,并返回表达式的值
res = eval('Pair(3, 4)') # res返回的是一个实例化的对象,虽然print打印出来是(3, 4),
但是其实是print调用这个对象的__str__方法,eval返回的是一个class实例化对象
# 如果 __str__() 没有被定义,那么就会使用 __repr__() 来代替输出 # 格式化代码 {0.x} 对应的是第1个参数的x属性。 因此,在下面的函数中,0实际上指的就是 self 本身 def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) 也可以使用 % 操作符 def __repr__(self): return 'Pair(%r, %r)' % (self.x, self.y)
14:自定义字符串的格式化 format()
函数和字符串方法使得一个对象能支持自定义的格式化 __format__()
方法
# 类上面定义 __format__() 方法来自定义字符串的格式化 _formats = { 'ymd': '{d.year}-{d.month}-{d.day}', 'mdy': '{d.month}/{d.day}/{d.year}', 'dmy': '{d.day}/{d.month}/{d.year}' } class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day def __format__(self, code): if code == '': # 如果没有code code = 'ymd' fmt = _formats[code] return fmt.format(d=self) # 现在 Date 类的实例可以支持格式化操作了 d = Date(2012, 12, 21) print(format(d)) # 2012-12-21 就是对d实例化对象调用__format__方法 print(format(d, 'mdy')) # 12/21/2012 print(d.__format__('mdy')) # 12/21/2012 print('{:mdy}'.format(d)) # 12/21/2012 print('The date is {:ymd}'.format(d)) # The date is 2012-12-21 print('The date is {:mdy}'.format(d)) # The date is 12/21/2012 # format(d, 'mdy') == d.__format__('mdy') == '{:mdy}'.format(d)
三个都是调用双下format方法:对象.__format__(参数) 这样来执行的
# __format__() 方法给Python的字符串格式化功能提供了一个钩子。
强调的是格式化代码的解析工作完全由类自己决定。因此,格式化代码可以是任何值 from datetime import date d = date(2021, 8, 20) # 2021-08-20 print(format(d)) # 2021-08-20 print(format(d, '%A, %B %d, %Y')) # Friday, August 20, 2021 print('The end is {:%d %b %Y}. Goodbye'.format(d)) # The end is 20 Aug 2021. Goodbye
15:让对象支持上下文管理协议 兼容 with
语句实现 实现 __enter__()
和 __exit__()
方法
# 对象兼容 with 语句,你需要实现 __enter__() 和 __exit__() 方法 from socket import socket, AF_INET, SOCK_STREAM from functools import partial class LazyConnection: def __init__(self, address, family=AF_INET, type=SOCK_STREAM): self.address = address self.family = family self.type = type self.sock = None def __enter__(self): if self.sock is not None: raise RuntimeError('Already connected') self.sock = socket(self.family, self.type) self.sock.connect(self.address) return self.sock def __exit__(self, exc_ty, exc_val, tb): self.sock.close() self.sock = None # 类初始化的时候并不会做任何事情(比如它并没有建立一个连接)。 连接的建立和关闭是使用 with 语句自动完成的 conn = LazyConnection(('www.python.org', 80)) # 类的实例化 # 进入with语句默认就调用conn对象的__enter__返回self.sock,结束后自动调用__exit__方法 with conn as s: s.send(b'GET /index.html HTTP/1.0\r\n') s.send(b'Host: www.python.org\r\n') s.send(b'\r\n') # 发送一个http的get请求 resp = b''.join(iter(partial(s.recv, 8192), b'')) # 然后循环接收接收到b''结束 print(resp)
# 上下文管理器的主要原理是你的代码会放到 with 语句块中执行。 当出现 with 语句的时候,
对象的 __enter__() 方法被触发, 它返回的值(如果有的话)会被赋值给 as 声明的变量。
然后,with 语句块里面的代码开始执行。 最后,__exit__() 方法被触发进行清理工作 # 不管 with 代码块中发生什么,上面的控制流都会执行完,就算代码块中发生了异常也是一样的。
事实上,__exit__() 方法的三个参数包含了异常类型、异常值和追溯信息(如果有的话)。
__exit__() 方法能自己决定怎样利用这个异常信息,或者忽略它并返回一个None值。
如果 __exit__() 返回 True ,那么异常会被清空,就好像什么都没发生一样, with 语句后面的程序继续在正常执行 # 还有一个细节问题就是 LazyConnection 类是否允许多个 with 语句来嵌套使用连接。
很显然,上面的定义中一次只能允许一个socket连接,
如果正在使用一个socket的时候又重复使用 with 语句, 就会产生一个异常了。不过你可以像下面这样修改下上面的实现来解决这个问题 from socket import socket, AF_INET, SOCK_STREAM class LazyConnection: def __init__(self, address, family=AF_INET, type=SOCK_STREAM): self.address = address self.family = family self.type = type self.connections = []
# self.connections存放所有的socket套接字,with调用enter每次是在列表里存一个套接字,然后返回套接字,
# 最后调用__exit__从列表最后拿出套接字关闭
# 利用了栈的类似特性,后进先出,往列表的最后append,然后最后pop类似栈的使用 def __enter__(self): sock = socket(self.family, self.type) sock.connect(self.address) self.connections.append(sock) return sock def __exit__(self, exc_ty, exc_val, tb): self.connections.pop().close() from functools import partial conn = LazyConnection(('www.python.org', 80)) with conn as s1: pass with conn as s2: pass # s1和s2是独立的插座 # 在第二个版本中,LazyConnection 类可以被看做是某个连接工厂。在内部,一个列表被用来构造一个栈。
每次 __enter__() 方法执行的时候,它复制创建一个新的连接并将其加入到栈里面。
__exit__() 方法简单的从栈中弹出最后一个连接并关闭它。 这里稍微有点难理解,不过它能允许嵌套使用 with 语句创建多个连接 # 在需要管理一些资源比如文件、网络连接和锁的编程环境中,使用上下文管理器是很普遍的。
这些资源的一个主要特征是它们必须被手动的关闭或释放来确保程序的正确运行。
例如,如果你请求了一个锁,那么你必须确保之后释放了它,否则就可能产生死锁。
通过实现 __enter__() 和 __exit__() 方法并使用 with 语句可以很容易的避免这些问题, 因为 __exit__() 方法可以让你无需担心这些了
16:创建大量对象时节省内存方法 程序要创建大量(可能上百万)的对象,导致占用很大的内存
# 对于主要是用来当成简单的数据结构的类而言,可以通过给类添加 __slots__ 属性来极大的减少实例所占的内存 class Date: __slots__ = ['year', 'month', 'day'] def __init__(self, year, month, day): self.year = year self.month = month self.day = day # 定义 __slots__ 后,Python就会为实例使用一种更加紧凑的内部表示。
实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,
这跟元组或列表很类似。 在 __slots__ 中列出的属性名在内部被映射到这个数组的指定小标上。
使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在 __slots__ 中定义的那些属性名
# 使用slots后节省的内存会跟存储属性的数量和类型有关。 不过,一般来讲,使用到的内存总量和将数据存储在一个元组中差不多。
假设你不使用slots直接存储一个Date实例, 在64位的Python上面要占用428字节,
而如果使用了slots,内存占用下降到156字节。 如果程序中需要同时创建大量的日期实例,那么这个就能极大的减小内存使用量了
# 尽管slots看上去是一个很有用的特性,很多时候你还是得减少对它的使用冲动。
Python的很多特性都依赖于普通的基于字典的实现。 另外,定义了slots后的类不再支持一些普通类特性了,
比如多继承。 大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义slots (比如在程序中需要创建某个类的几百万个实例对象)
# 关于 __slots__ 的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。
尽管使用slots可以达到这样的目的,但是这个并不是它的初衷。 __slots__ 更多的是用来作为一个内存优化工具
17:在类中封装属性名 封装类的实例上面的“私有”数据,但是Python语言并没有访问控制
# Python不依赖语言特性去封装数据,而是通过遵循一定的属性和方法命名规约来达到这个效果 # 第一个约定是任何以单下划线_开头的名字都应该是内部实现 class A: def __init__(self): self._internal = 0 # 内在属性 self.public = 1 # 公共属性 def public_method(self): # 公共方法 pass def _internal_method(self): # 内在方法 pass # Python并不会真的阻止别人访问内部名称。这么做肯定是不好的,可能会导致脆弱的代码。
同时还要注意到,使用下划线开头的约定同样适用于模块名和模块级别函数。
例如,如果你看到某个模块名以单下划线开头(比如_socket),那它就是内部实现。
类似的,模块级别函数比如 sys._getframe() 在使用的时候就得加倍小心了
# 两个下划线(__)开头的命名 class B: def __init__(self): self.__private = 0 def __private_method(self): pass def public_method(self): pass self.__private_method() # 使用双下划线开始会导致访问名称变成其他形式。 # 比如,在类B中,私有属性会被分别重命名为 _B__private 和 _B__private_method 。
这时候你可能会问这样重命名的目的是什么,答案就是继承——这种属性通过继承是无法被覆盖的 class B: def __init__(self): self.__private = 0 def __private_method(self): pass def public_method(self): pass self.__private_method() class C(B): def __init__(self): super().__init__() self.__private = 1 # 不覆盖B._private # 不重写B.__private_method()方法 def __private_method(self): pass # C类里,私有名称 __private 和 __private_method 被重命名为 _C__private 和 _C__private_method ,
这个跟父类B中的名称是完全不同的
# 单下划线和双下划线两种不同的编码约定来命名私有属性 # 大多数而言,你应该让你的非公共名称以单下划线开头。
但是,如果你清楚你的代码会涉及到子类, 并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线方案 # 有时候你定义的一个变量和某个保留关键字冲突,这时候可以使用单下划线作为后缀 lambda_ = 2.0 # 拖尾uu以避免与lambda关键字冲突
# 单下划线是指代内部属性或者方法可以被继承,(只是这样一个指代,外面其实还是可以调用)
# 双下划线方法不可以继承,外部也不可以直接调用,需要特殊方法才能调用前面加_类名__方法名()这样才能调用
18:创建可管理的属性 给某个实例attribute增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证 property
# 自定义某个属性的一种方法是将它定义为一个property # 定义一个property,增加对一个属性简单的类型检查 class Person: def __init__(self, first_name): self._first_name = first_name @property # 获取函数 def first_name(self): # @property让frist_name函数变成一个属性,可以通过 对象.frist_name 来访问 return self._first_name @first_name.setter # 设置函数 def first_name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._first_name = value @first_name.deleter # 删除函数(可选) def first_name(self): raise AttributeError("Can't delete attribute") # 三个相关联的方法,这三个方法的名字都必须一样。 第一个方法是一个 getter 函数,
它使得 first_name 成为一个属性。 其他两个方法给 first_name 属性添加了 setter 和 deleter 函数。
需要强调的是只有在 first_name 属性被创建后, 后面的两个装饰器 @first_name.setter 和 @first_name.deleter 才能被定义 # property的一个关键特征是它看上去跟普通的attribute没什么两样, 但是访问它的时候会自动触发 getter 、setter 和 deleter 方法 a = Person('Guido') print(a.first_name) # Guido # a.first_name = 42 # TypeError: Expected a string 报错,必须是字符串 a.first_name = '42' # 可以成功设置成字符串 del a.first_name # AttributeError: Can't delete attribute 删除报错,del删除调用deleter装饰的函数
# 实现一个property的时候,底层数据(如果有的话)仍然需要存储在某个地方。 因此,在get和set方法中,你会看到对 _first_name 属性的操作,这也是实际数据保存的地方。 另外,你可能还会问为什么 __init__() 方法中设置了 self.first_name 而不是 self._first_name 。 在这个例子中,我们创建一个property的目的就是在设置attribute的时候进行检查。 因此,你可能想在初始化的时候也进行这种类型检查。通过设置 self.first_name , 自动调用 setter 方法, 这个方法里面会进行参数的检查,否则就是直接访问 self._first_name 了 # 在已存在的get和set方法基础上定义property class Person: def __init__(self, first_name): self.set_first_name(first_name) # 调用这个是为了类Person('liergou')的初始化的时候传进来的数据进行类型校验,必须要字符串类型才可以 def get_first_name(self): return self._first_name def set_first_name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._first_name = value def del_first_name(self): raise AttributeError("Can't delete attribute") # 从现有的get/set方法生成属性 name = property(get_first_name, set_first_name, del_first_name) p = Person('liergou') print(p.name) # liergou p.name = '20' print(p.name) # 20 del p.name # 报错 AttributeError: Can't delete attribute
# 一个property属性其实就是一系列相关绑定方法的集合。 # 查看拥有property的类, 发现property本身的fget、fset和fdel属性就是类里面的普通方法 class Person: def __init__(self, first_name): self._first_name = first_name @property def first_name(self): return self._first_name @first_name.setter def first_name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._first_name = value @first_name.deleter def first_name(self): raise AttributeError("Can't delete attribute") p = Person('liergou') print(Person.first_name.fget) # <function Person.first_name at 0x00000213B0FEC700> print(Person.first_name.fset) # <function Person.first_name at 0x00000210C313D5E0> print(Person.first_name.fdel) # <function Person.first_name at 0x0000020160DFD550> print(p.first_name) # 不会直接取调用fget或者fset,会在访问property的时候自动被触发 # 确实需要对attribute执行其他额外的操作的时候才应该使用到property。 # 有时候一些从其他编程语言(比如Java)过来的程序员总认为所有访问都应该通过getter和setter class Person: def __init__(self, first_name): self.first_name = first_name
# 因为定义了@frist_name_setter,所有这里 对象.属性 = 值会调用@frist_name_setter装饰器装饰的函数
# 所以self.frist_name = frist_name等同把frist_name初始化的值传给value然后self._frist_name = frist
# 所以self._frist_name = 'liergou' @property def first_name(self): return self._first_name @first_name.setter def first_name(self, value): self._first_name = value # 不要写这种没有做任何其他额外操作的property,它会让你的代码变得很臃肿,并且还会迷惑阅读者。
其次,它还会让你的程序运行起来变慢很多。 最后,这样的设计并没有带来任何的好处。
以后想给普通attribute访问添加额外的处理逻辑的时候, 你可以将它变成一个property而无需改变原来的代码。
因为访问attribute的代码还是保持原样
p = Person('liergou')
# Properties还是一种定义动态计算attribute的方法。 这种类型的attributes并不会被实际的存储,而是在需要的时候计算出来 import math class Circle: def __init__(self, radius): self.radius = radius @property def area(self): return math.pi * self.radius ** 2 @property def diameter(self): return self.radius * 2 @property def perimeter(self): return 2 * math.pi * self.radius # 通过使用properties,将所有的访问接口形式统一起来, 对半径、直径、周长和面积的访问都是通过属性访问,
就跟访问简单的attribute是一样的。 如果不这样做的话,那么就要在代码中混合使用简单属性访问和方法调用 c = Circle(4.0) print(c.radius) # 4.0 半径 print(c.diameter) # 8.0 直径 print(c.area) # 50.26548245743669 面积 print(c.perimeter) # 25.132741228718345 周长 # properties可以实现优雅的编程接口, # 也可以直接使用getter和setter函数 class Person: def __init__(self, first_name): self.set_first_name(first_name) def get_first_name(self): return self._first_name def set_first_name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._first_name = value def del_first_name(self): raise AttributeError("Can't delete attribute") name = property(get_first_name, set_first_name, del_first_name) p = Person('liergou') print(p.get_first_name()) # liergou p.set_first_name('shabi') print(p.get_first_name()) # shabi # 这种情况的出现通常是因为Python代码被集成到一个大型基础平台架构或程序中。
例如,有可能是一个Python类准备加入到一个基于远程过程调用的大型分布式系统中。
这种情况下,直接使用get/set方法(普通方法调用)而不是property或许会更容易兼容
class Person: def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name @property def first_name(self): return self._first_name @first_name.setter def first_name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._first_name = value # Repeated property code, but for a different name (bad!) @property def last_name(self): return self._last_name @last_name.setter def last_name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._last_name = value # 不要这样写大量重复代码的property定义
19:调用父类方法 super(类名, 类的实例(selg)).__init__(参数)
# 子类中调用父类的某个已经被覆盖的方法 # 调用父类(超类)的一个方法,使用 super() 函数 class A: def __init__(self, name): self.name = name self.age = 18 def input_name(self, in_data): print(f'my name is {self.name}') print(in_data) class B(A): def __init__(self, name): # super() 调用父类的__init__构造方法,默认不会继承父类的双下方法 super().__init__(name) # 调用父类的构造方法写法1 # super(B, self).__init__(name) # 写法2,写法1类似写法2的缩写 # A.__init__(self, name) # 写法3 self.y = 1 def input_name(self): pass # super().input_name(10) # 调用父类A的构造方法1 # A.input_name(self, 10) # 调用父类A的构造方法2 # super(B, self).input_name(10) # 调用父类的构造方法3 # super(B)就是找B的父类(超类)B的超累就是A b = B('ywt') # b.input_name() super(B, b).input_name(10) # 在类外调用B的父类A的input_name方法1 A.input_name(b, 10) # 在类外调用B的父类A的input_name方法2 # super().input_name(10) # 这样运行不行,报错RuntimeError: super(): no arguments super()要当初一个函数来使用
# super() 的另外一个常见用法覆盖Python特殊方法的代码
class Proxy:
def __init__(self, obj):
self._obj = obj
# 将属性查找委托给内部obj
def __getattr__(self, name):
return getattr(self._obj, name) # 在obj里查找name属性
# 委托属性分配
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # 调用原始的 __setattr__,往当前对象self里面添加属性和值
else:
setattr(self._obj, name, value) # 这个setattr是往_obj这个对象里加属性和值
class A:
b = 'bbb'
c = 'ccc'
d = 'ddd'
pass
a = A()
p = Proxy(a)
print(getattr(p, 'b')) # bbb
setattr(p, '_666', '666')
# {'_obj': <__main__.A object at 0x000002A052EBFFA0>, '_666': '666'},直接往p这个对象里加属性和值
print(p.__dict__)
print(p._666) # 666
setattr(p, 'a777', '777') # 往self._obj里面加属性和值,self._objs是传参进来的对象a,
print(a.a777) # 777
# __setattr__() 的实现包含一个名字检查。 如果某个属性名以下划线(_)开头,
就通过 super() 调用原始的 __setattr__() , 否则的话就委派给内部的代理对象 self._obj 去处理。
这看上去有点意思,因为就算没有显式的指明某个类的父类, super() 仍然可以有效的工作
# py中直接调用父类的一个方法 class Base: def __init__(self): print('Base.__init__') class A(Base): def __init__(self): Base.__init__(self) print('A.__init__') # 大部分代码而言这么做没什么问题,但是在更复杂的涉及到多继承的代码中就有可能导致很奇怪的问题发生 # 多继承 class Base: def __init__(self): print('Base.__init__') class A(Base): def __init__(self): Base.__init__(self) print('A.__init__') class B(Base): def __init__(self): Base.__init__(self) print('B.__init__') class C(A, B): def __init__(self): A.__init__(self) B.__init__(self) print('C.__init__') # A继承Base,B继承Base,C继承A和B, # C类实例化的时候Base.__init__() 被调用两次 c = C() 输出: Base.__init__ A.__init__ Base.__init__ B.__init__ C.__init__ # 假设你在代码中换成使用 super() ,结果就很完美了,Base.__init__只调用一次 class Base: def __init__(self): print('Base.__init__') class A(Base): def __init__(self): super().__init__() print('A.__init__') class B(Base): def __init__(self): super().__init__() print('B.__init__') class C(A, B): def __init__(self): super().__init__() print('C.__init__') c = C() 输出: Base.__init__ B.__init__ A.__init__ C.__init__ # 运行super版本的每个 __init__() 方法只会被调用一次了 # py的继承关系表 # py里定义的每一个类,Python会计算出一个所谓的方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表 class Base: def __init__(self): print('Base.__init__') class A(Base): def __init__(self): super().__init__() print('A.__init__') class B(Base): def __init__(self): super().__init__() print('B.__init__') class C(A, B): def __init__(self): super().__init__() print('C.__init__') print(C.__mro__) # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>) # 为了实现继承,Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止 # MRO列表的构造是通过一个C3线性化算法来实现的。它实际上就是合并所有父类的MRO列表并遵循如下三条准则: 子类会先于父类被检查 多个父类会根据它们在列表中的顺序被检查 如果对下一个类存在两个合法的选择,选择第一个父类 # 知道的就是MRO列表中的类顺序会让你定义的任意类层级关系变得有意义 # 使用 super() 函数时,Python会在MRO列表上继续搜索下一个类。
只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个MRO列表,
每个方法也只会被调用一次。 这也是为什么在第二个例子中你不会调用两次 Base.__init__() 的原因
# 多继承详解 class Base: def __init__(self): print('Base.__init__') def __func__(self): print('Base') class A(Base): def __init__(self): super().__init__() print('A.__init__') def __func__(self): super().__func__() print('A') class B(Base): def __init__(self): super().__init__() print('B.__init__') def __func__(self): super().__func__() print('B') class C(Base): def __init__(self): super().__init__() print('C.__init__') def __func__(self): super(C, self).__func__() print('C') class D(A, B, C): def __init__(self): super().__init__() print('D.__init__') def __func__(self): super(D, self).__func__() print('D') print(D.__mro__) # (<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>,
<class '__main__.C'>, <class '__main__.Base'>, <class 'object'>) d = D() d.__func__() 输出: Base.__init__ C.__init__ B.__init__ A.__init__ D.__init__ Base C B A D
原理:
d.__func__()调用__func__函数,调用super(D, d).__func__()找到第一个爹A,调用A类的__func__()函数,
A类里super(A,d).__func__(),调用第一个爹A后面的下一个爹B类,然后继续B里面super(B,d).__func__()找B前面的爹C类
super(A,d).__func__() 可以堪称找d这个实例化对象的类的mro继承表里面A这个类对象的后面一个对象的__func__函数来调用
# super() 不一定去查找某个类在MRO中下一个直接父类, 你甚至可以在一个没有直接父类的类中使用它 class A: def spam(self): print('A.spam') super().spam() # a = A() # a.spam() # 报错:AttributeError: 'super' object has no attribute 'spam' class B: def spam(self): print('B.spam') class C(A, B): pass print(C.__mro__) # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>) c = C() c.spam() # C没有spam方法,在第一个爹A里找spam方法,spam(c), super(A, c).spam()找到A类后面爹的spam方法 # A类前面是B,B.spam(),所以既调用了A类的spam也调用了B类的spam,打印A.spam和B.spam 输出: A.spam B.spam # 在类A中使用 super().spam() 实际上调用的是跟类A毫无关系的类B中的 spam() 方法
20:子类中扩展property 子类中,扩展定义在父类中的property的功能
# 在子类中,扩展定义在父类中的property的功能 class Person: def __init__(self, name): self.name = name # 调用setter方法,name传参给name(self, name),self._name = name @property def name(self): return self._name @name.setter def name(self, value): if not isinstance(value, str): raise TypeError('Expected a string') self._name = value @name.deleter def name(self): raise AttributeError("Can't delete attribute") # 继承自Person并扩展了 name 属性的功能 class SubPerson(Person): @property def name(self): print('Getting name') return super().name # 调用父类的@property装饰的函数 @name.setter def name(self, value): print('Setting name to', value) super(SubPerson, SubPerson).name.__set__(self, value) # 调用父类的name属性的set方法,父类里被@name.setter @name.deleter def name(self): print('Deleting name') super(SubPerson, SubPerson).name.__delete__(self) s = SubPerson('liergou') print(s.name) s.name = 'Larry' print(s.name) s.name = 42 # TypeError: Expected a string报错 # 仅仅只想扩展property的某一个方法,那么可以像下面这样写 class SubPerson(Person): @Person.name.getter def name(self): print('Getting name') return super().name # 只修改setter方法 class SubPerson(Person): @Person.name.setter def name(self, value): print('Setting name to', value) super(SubPerson, SubPerson).name.__set__(self, value)
# property是 getter、setter 和 deleter 方法的集合,而不是单个方法。
当扩展一个property的时候,先确定你是否要重新定义所有的方法还是只修改其中某一个 # 例1所有的property方法都被重新定义。 在每一个方法中,使用了 super() 来调用父类的实现。
在 setter 函数中使用 super(SubPerson, SubPerson).name.__set__(self, value) 的语句是没有错的。
为了委托给之前定义的setter方法,需要将控制权传递给之前定义的name属性的 __set__() 方法。
不过,获取这个方法的唯一途径是使用类变量而不是实例变量来访问它。 这也是为什么我们要使用 super(SubPerson, SubPerson) 的原因 # 重定义其中一个方法,那只使用 @property 本身是不够的 class SubPerson(Person): @property # Doesn't work def name(self): print('Getting name') return super().name s = SubPerson('Guido') # 报错:AttributeError: can't set attribute # 应该这样重定义其中一个方法 class SubPerson(Person): @Person.name.getter def name(self): print('Getting name') return super().name # 这么写后,property之前已经定义过的方法会被复制过来,而getter函数被替换。然后它就能按照期望的工作了 s = SubPerson('Guido') s.name s.name = 'Larry'
在这个特别的解决方案中,我们没办法使用更加通用的方式去替换硬编码的Person
类名。
如果你不知道到底是哪个基类定义了property, 那你只能通过重新定义所有property并使用super()
来将控制权传递给前面的实现
# 描述器扩展property
# 描述器
class String:
def __init__(self, name):
self.name = name
def __get__(self, instance, cls):
print('__get__xxx')
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('__set__')
if not isinstance(value, str):
raise TypeError('Expected a string')
instance.__dict__[self.name] = value # self.name = 'name'
class Person:
name = String('name') # name是描述器
print(111)
def __init__(self, name):
self.name = name # 调用描述器String的__set__方法,self是实例
# 实例.__dict__['name'] = 'liergou'
class SubPerson(Person):
@property
def name(self):
print('Getting name')
return super().name
@name.setter
def name(self, value):
print('Setting name to', value)
super(SubPerson, SubPerson).name.__set__(self, value)
# 调用父类Person的name属性的__set__方法
@name.deleter
def name(self):
print('Deleting name')
super(SubPerson, SubPerson).name.__delete__(self)
s = SubPerson('Guido')
# SubPerson实例化创建描述器,name = String('name')
# 调用父类的__init__方法,父类的__init__里self.name = name调用描述器String的__set__方法,self是实例
# 在s实例类里写入'name'= 'liergou'
# print(s.__dict__) # {'name': 'Guido'}
# print(s.name)
# 子类化 setter 和 deleter 方法其实是很简单的
21:创建新的类或实例属性 创建一个新的拥有一些额外功能的实例属性类型,比如类型检查
# 创建一个全新的实例属性,可以通过一个描述器类的形式来定义它的功能 # 整数类型检查属性的描述符属性class Integer: def__init__(self, name): self.name = name def__get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.name] def__set__(self, instance, value):
# self是Integer的实例,instance为实例,如果是类访问,那么instance为None,owner是调用者的类 ifnot isinstance(value, int): raise TypeError('Expected an int') instance.__dict__[self.name] = value def__delete__(self, instance): del instance.__dict__[self.name]
# 一个描述器就是一个实现了三个核心的属性访问操作(get, set, delete)的类,
分别为 __get__() 、__set__() 和 __delete__() 这三个特殊的方法。
这些方法接受一个实例作为输入,之后相应的操作实例底层的字典 # 使用一个描述器,需将这个描述器的实例作为类属性放到一个类的定义中class Point: x = Integer('x') y = Integer('y') def__init__(self, x, y): self.x = x
# self.x可以调动Integer类的实例Integer('x'),
# self.x = x,触发描述器Integer的set方法,
# Integer('x').__set__(Integer('x'), Point(2, 3), 2)
# x的值2传参给value,instance实例调用的时候表示Point实例Point(2, 3)
# print(Point.x).__dict__[Integer('x').name] = 2
# print(Point.x).__dict__['x'] = 2 self.y = y # 调用描述器Integer的set方法 # 所有对描述器属性(比如x或y)的访问会被 __get__() 、__set__() 和 __delete__() 方法捕获到 p = Point(2, 3) p.x # 调用 Point.x.__get__(p,Point) p.y = 5 # 调用 Point.y.__set__(p, 5) p.x = 2.3 # 调用 Point.x.__set__(p, 2.3) # 作为输入,描述器的每一个方法会接受一个操作实例。 为了实现请求操作,
会相应的操作实例底层的字典(__dict__属性)。 描述器的 self.name 属性存储了在实例字典中被实际使用到的key
# 描述器可实现大部分Python类特性中的底层魔法, @classmethod 、@staticmethod 、@property ,甚至是 __slots__ 特性 # 通过定义一个描述器,你可以在底层捕获核心的实例操作(get, set, delete),
并且可完全自定义它们的行为。 这是一个强大的工具,有了它你可以实现很多高级功能,
并且它也是很多高级库和框架中的重要工具之一 # 描述器的它只能在类级别被定义,而不能为每个实例单独定义。 class Point: def __init__(self, x, y): self.x = Integer('x') # 错误,必须是类属性,这样无法运行,不会校验必须数字才可以传参 self.y = Integer('y') self.x = x self.y = y # __get__() 方法实现起来比看上去要复杂得多 class Integer: def __get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.name] # __get__() 看上去有点复杂的原因归结于实例变量和类变量的不同。 如果一个描述器被当做一个类变量来访问,
那么 instance 参数被设置成 None 。 这种情况下,标准做法就是简单的返回这个描述器本身即可(尽管你还可以添加其他的自定义操作) # 整数类型检查属性的描述符属性 class Integer: def __init__(self, name): self.name = name def __get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value, int): raise TypeError('Expected an int') instance.__dict__[self.name] = value def __delete__(self, instance): del instance.__dict__[self.name] class Point: x = Integer('x') y = Integer('y') def __init__(self, x, y): self.x = x self.y = y p = Point(2,3) print(p.x) # 2 print(Point.x) # <__main__.Integer object at 0x000002669E46EFD0>
# 描述器通常是那些使用到装饰器或元类的大型框架中的一个组件。同时它们的使用也被隐藏在后面 # 类型检查属性的描述符 class Typed: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value, self.expected_type): raise TypeError('Expected ' + str(self.expected_type)) instance.__dict__[self.name] = value def __delete__(self, instance): del instance.__dict__[self.name] # 将其应用于选定属性的类装饰器 def typeassert(**kwargs): def decorate(cls): for name, expected_type in kwargs.items(): # Attach a Typed descriptor to the class setattr(cls, name, Typed(name, expected_type)) return cls return decorate @typeassert(name=str, shares=int, price=float) class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price
# 使用typeassert这个装饰器传递关键字参数,然后装饰Stock这个类
装饰器的本质是给Stock这个类setattr往cls类里增加name = Typed(name, expected_type)这个类属性
本质上是往类里添加一个类属性的描述性,然后通过描述器来指定初始化的时候的传参
22:使用延迟计算属性
# 将一个只读属性定义成一个property,并且只在访问的时候才会计算结果。 一旦被访问后结果值被缓存起来,不用每次都去计算 # 定义一个延迟属性的一种高效方法是通过使用一个描述器类 import math class lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, cls): print('xxxx') if instance is None: return self else: value = self.func(instance) setattr(instance, self.func.__name__, value) return value class Circle: def __init__(self, radius): self.radius = radius @lazyproperty # area = lazyproperty(area) 装饰器的作用相当于定义了个类属性area = 描述器 def area(self): print('Computing area') return math.pi * self.radius ** 2 @lazyproperty def perimeter(self): print('Computing perimeter') return 2 * math.pi * self.radius print(Circle.__dict__) c = Circle(4.0) print(c.radius) print(c.area) # area是一个描述器,本质上调用area描述器的__get__方法
# 调用__get__方法的setattr(instance, self.func.__name__, value)
# instance是c这个实例,self.func.__name__=lazyproperty(area)的func是area = area, value = area()函数计算的结果
# 所以c这个实例里面存储area = area()运算的结果 print(c.area) print(c.perimeter) print(c.perimeter) 输出: {'__module__': '__main__', '__init__': <function Circle.__init__ at 0x0000028386F5D0D0>,
'area': <__main__.lazyproperty object at 0x0000028386F6DBB0>,
'perimeter': <__main__.lazyproperty object at 0x0000028386F6DB50>,
'__dict__': <attribute '__dict__' of 'Circle' objects>,
'__weakref__': <attribute '__weakref__' of 'Circle' objects>, '__doc__': None} 4.0 xxxx Computing area 50.26548245743669 50.26548245743669 xxxx Computing perimeter 25.132741228718345 25.132741228718345Computing area
和Computing perimeter
仅仅出现一次
# 构造一个延迟计算属性的主要目的是为了提升性能。 #可以避免计算这些属性值,除非你真的需要它们。 这里演示的方案就是用来实现这样的效果的,
只不过它是通过以非常高效的方式使用描述器的一个精妙特性来达到这种效果的 # 当一个描述器被放入一个类的定义时,
每次访问属性时它的 __get__() 、__set__() 和 __delete__() 方法就会被触发。 # 如果一个描述器仅仅只定义了一个 __get__() 方法的话,
它比通常的具有更弱的绑定。 特别地,只有当被访问属性不在实例底层的字典中时 __get__() 方法才会被触发 # lazyproperty 类利用这一点,使用 __get__() 方法在实例中存储计算出来的值,
这个实例使用相同的名字作为它的property。 这样一来,结果值被存储在实例字典中并且以后就不需要再去计算这个property了 c = Circle(4.0) print(vars(c)) # {'radius': 4.0} 获取实例变量 # 计算面积,然后观察变量 print(c.area) Computing area 50.26548245743669 print(vars(c)) {'radius': 4.0, 'area': 50.26548245743669} # 访问不再调用属性 print(c.area) 50.26548245743669 # 删除变量并再次查看属性触发器 del c.area print(vars(c)) # {'radius': 4.0} print(c.area) Computing area 50.26548245743669 # 方案有一个小缺陷就是计算出的值被创建后是可以被修改的 c.area c.area = 25 # 可以使用一种稍微没那么高效的实现来避免修改
import math
def lazyproperty(func):
name = '_lazy_' + func.__name__
@property
def lazy(self):
if hasattr(self, name):
return getattr(self, name)
else:
value = func(self)
setattr(self, name, value)
return value
return lazy
class Circle:
def __init__(self, radius):
self.radius = radius
@lazyproperty
def area(self):
print('Computing area')
return math.pi * self.radius ** 2
@lazyproperty
def perimeter(self):
print('Computing perimeter')
return 2 * math.pi * self.radius
# 使用这个版本,修改操作已经不被允许
# 前面使用的是class lazyproperty:类来装饰area和perimeter函数,
# 这个例子使用def lazyproperty(func):这个装饰器函数来装饰area和perimeter函数
# 这种方案有一个缺点就是所有get操作都必须被定向到属性的getter
函数上去。
这个跟之前简单的在实例字典中查找值的方案相比效率要低一点。
import math def lazyproperty(func): name = '_lazy_' + func.__name__ @property def lazy(self): if hasattr(self, name): return getattr(self, name) else: value = func(self) setattr(self, name, value) return value @lazy.setter def lazy(self, value): if not isinstance(value, int): raise TypeError('Expected a int') setattr(self, name, value) @lazy.deleter def lazy(self): raise AttributeError("Can't delete attribute") return lazy class Circle: def __init__(self, radius): self.radius = radius @lazyproperty # area = lazyproperty(area) aera = lazy函数 # 所以c.area等同c.lazy # lazy函数被property装饰,c.lazy等同调用@property装饰的也就是get的函数 # name = '_lazy_' + func.__name__ = '_lazy_area' # c.area = c.lazy,c.lazy等同调用@property装饰的也就是get的函数lazyproperty(area)实例里没有'_lazy_area' # 走else, value = area(c), setattr(c, '_lazy_area', value)然后return返回value # 在c对象里设置属性值后返回value def area(self): print('Computing area') return math.pi * self.radius ** 2 @lazyproperty def perimeter(self): print('Computing perimeter') return 2 * math.pi * self.radius print(Circle.__dict__) c = Circle(4.0) print(c.area) print(c.area) # Computing perimeter # 25.132741228718345 # 25.132741228718345 c.area = 25 print(c.area) # 25 print(c.__dict__) # {'radius': 4.0, '_lazy_area': 25}
c.area = c.lazy,lazy被property装饰,c.lazy调用lazy函数里面的get方法,调用lazy(self)
在装饰器lazyproperty写一个完整的property也是ok的,拥有get,set,delete的操作
23:简化数据结构的初始化
# 写很多仅仅用作数据结构的类,不想写太多烦人的 __init__() 函数 # 在一个基类中写一个公用的 __init__() 函数 import math class Structure1: # 类变量,该变量指定所需的字段 _fields = [] def __init__(self, *args): if len(args) != len(self._fields): raise TypeError('Expected {} arguments'.format(len(self._fields))) # 设置参数 for name, value in zip(self._fields, args): setattr(self, name, value) # 示例类定义 class Stock(Structure1): _fields = ['name', 'shares', 'price'] class Point(Structure1): _fields = ['x', 'y'] class Circle(Structure1): _fields = ['radius'] def area(self): return math.pi * self.radius ** 2 if __name__ == '__main__': s = Stock('ACME', 50, 91.1) p = Point(2, 3) c = Circle(4.5) s2 = Stock('ACME', 50) # TypeError: Expected 3 arguments报错,需要三个参数
# 支持关键字参数,可以将关键字参数设置为实例属性 class Structure2: _fields = [] def __init__(self, *args, **kwargs): if len(args) > len(self._fields): raise TypeError('Expected {} arguments'.format(len(self._fields))) # 设置所有位置参数 for name, value in zip(self._fields, args): setattr(self, name, value) # 设置剩余的关键字参数 for name in self._fields[len(args):]: setattr(self, name, kwargs.pop(name)) # 取出kwargs对应的键为name的值并从字典里删除 # 检查是否有任何剩余的未知参数,如果还有其他kwargs字典参数没有被pop删除那么就报错 if kwargs: raise TypeError('Invalid argument(s): {}'.format(','.join(kwargs))) if __name__ == '__main__': class Stock(Structure2): _fields = ['name', 'shares', 'price'] s1 = Stock('ACME', 50, 91.1) s2 = Stock('ACME', 50, price=91.1) s3 = Stock('ACME', shares=50, price=91.1) # s4 = Stock('ACME', shares=50, price=91.1, aa=1)
# 将不在 _fields 中的名称加入到属性中去 class Structure3: # 类变量,该变量指定所需的字段 _fields = [] def __init__(self, *args, **kwargs): if len(args) != len(self._fields): raise TypeError('Expected {} arguments'.format(len(self._fields))) for name, value in zip(self._fields, args): setattr(self, name, value) extra_args = kwargs.keys() - self._fields # kwargs.keys()是一个集合, for name in extra_args: setattr(self, name, kwargs.pop(name)) if kwargs: raise TypeError('Duplicate values for {}'.format(','.join(kwargs))) if __name__ == '__main__': class Stock(Structure3): _fields = ['name', 'shares', 'price'] s1 = Stock('ACME', 50, 91.1) s2 = Stock('ACME', 50, 91.1, date='8/2/2012')
# 当需要使用大量很小的数据结构类的时候, 相比手工一个个定义 __init__() 方法而已,这种定义基类继承的方式很实用 # setattr() 函数类设置属性值,也可以更新字典一样设置属性 class Structure: # 类变量,该变量指定所需的字段 _fields= [] def __init__(self, *args): if len(args) != len(self._fields): raise TypeError('Expected {} arguments'.format(len(self._fields))) # 设置参数(备用) self.__dict__.update(zip(self._fields,args)) # 这也可以正常工作 # 但是当一个子类定义了 __slots__ 或者通过property(或描述器)来包装某个属性,
那么直接访问实例字典就不起作用了。我们上面使用 setattr() 会显得更通用些,因为也适用于子类情况 # 这种方法唯一不好的地方就是对某些IDE而言,在显示帮助函数时可能不太友好 class Structure: # 类变量,该变量指定所需的字段 _fields = [] def __init__(self, *args): if len(args) != len(self._fields): raise TypeError('Expected {} arguments'.format(len(self._fields))) # 设置参数(备用) self.__dict__.update(zip(self._fields, args)) class Stock(Structure): _fields = ['name', 'shares', 'price'] print(help(Stock))
24:定义接口或者抽象基类 定义一个接口或抽象类,并且通过执行类型检查来确保子类实现了某些特定的方法 abc
模块
# abc 模块定义抽象基类 from abc import ABCMeta, abstractmethod class IStream(metaclass=ABCMeta): @abstractmethod def read(self, maxbytes=-1): pass @abstractmethod def write(self, data): pass # 抽象类的一个特点是它不能直接被实例化 a = IStream() # TypeError: Can't instantiate abstract class IStream with abstract methods read, write # 抽象类的目的就是让别的类继承它并实现特定的抽象方法: class SocketStream(IStream): def read(self, maxbytes=-1): pass def write(self, data): pass # 抽象基类的一个主要用途是在代码中检查某些类是否为特定类型,实现了特定接口 def serialize(obj, stream): if not isinstance(stream, IStream): raise TypeError('Expected an IStream') pass
so = SocketStream()
serialize(SocketStream, so)
isinstance:检查一个对象是否是一个类的实例化
print(isinstance(so, SocketStream)) # True
print(isinstance(so, IStream)) # True
@abstractmethod+metaclass=ABCMeta定义了一个基类,
被@abstractmethod装饰的函数继承他的类也要实现这个函数,不然报错
# 通过注册方式来让某个类实现抽象基类 import io from abc import ABCMeta, abstractmethod class IStream(metaclass=ABCMeta): @abstractmethod def read(self, maxbytes=-1): pass @abstractmethod def write(self, data): pass # 将内置I/O类注册为支持我们的接口 IStream.register(io.IOBase) # 打开一个普通文件并键入check f = open('foo.txt', "r") print(f.read()) print(f.write('111111111111111')) # print(isinstance(f, IStream)) # True
# @abstractmethod 还能注解静态方法、类方法和 properties 。 只需保证这个注解紧靠在函数定义前即可 class A(metaclass=ABCMeta): @property @abstractmethod def name(self): pass @name.setter @abstractmethod def name(self, value): pass @classmethod @abstractmethod def method1(cls): pass @staticmethod @abstractmethod def method2(): pass
# 标准库中有很多用到抽象基类的地方。collections 模块定义了很多跟容器和迭代器(序列、映射、集合等)有关的抽象基类。
numbers 库定义了跟数字对象(整数、浮点数、有理数等)有关的基类。io 库定义了很多跟I/O操作相关的基类 # 可以使用预定义的抽象类来执行更通用的类型检查 import collections # 检查x是否是序列 print(isinstance('abc', collections.Sequence)) # 检查x是否可迭代 print(isinstance('abc', collections.Iterable)) # 检查x是否有尺寸 print(isinstance('abc', collections.Sized)) # 检查x是否是映射 print(isinstance({"aaa": "aaa"}, collections.Mapping)) # ABCs库本质上可以让我们很方便的做类型检查
25:实现数据模型的类型约束 定义某些在属性赋值上面有限制的数据结构
# 对某些实例属性赋值时进行检查。要自定义属性赋值函数,这种情况下最好使用描述器 # 基类。使用描述符设置值 class Descriptor: def __init__(self, name=None, **opts): self.name = name for key, value in opts.items(): setattr(self, key, value) def __set__(self, instance, value): instance.__dict__[self.name] = value # 用于强制类型的描述器 class Typed(Descriptor): expected_type = type(None) def __set__(self, instance, value): if not isinstance(value, self.expected_type): raise TypeError('expected ' + str(self.expected_type)) super().__set__(instance, value) # 用于强制值的描述器 class Unsigned(Descriptor): def __set__(self, instance, value): if value < 0: raise ValueError('Expected >= 0') super().__set__(instance, value) class MaxSized(Descriptor): def __init__(self, name=None, **opts): if 'size' not in opts: raise TypeError('missing size option') super().__init__(name, **opts) def __set__(self, instance, value): if len(value) >= self.size: raise ValueError('size must be < ' + str(self.size)) super().__set__(instance, value) # 这些类就是创建的数据模型或类型系统的基础构建模块 class Integer(Typed): expected_type = int class UnsignedInteger(Integer, Unsigned): pass class Float(Typed): expected_type = float class UnsignedFloat(Float, Unsigned): pass class String(Typed): expected_type = str class SizedString(String, MaxSized): pass # 使用这些自定义数据类型,定义一个类 class Stock: # 指定约束条件 name = SizedString('name', size=8)
# SizedString继承顺序:(<class '__main__.SizedString'>, <class '__main__.String'>,
# <class '__main__.Typed'>,<class '__main__.MaxSized'>,
# <class '__main__.Descriptor'>, <class 'object'>)
# SizedString('name', size=8)实例化调用MaxSized类得__init__(根据深度优先算法),
# MaxSized再调用Descriptor这个基类的__init__, name = 'name', opt = {'size': 8}
# 调用Descriptor这个基类的__init__在self(实例:这里指代name变量)设置两个属性{'name': 'name', 'size': 8}
# SizedString本质上是描述器
shares = UnsignedInteger('shares') # 类似上面分析流程 price = UnsignedFloat('price') # 类似上面分析流程 def__init__(self, name, shares, price): self.name = name
# self.name是一个构造器,调用name=SizedString('name', size=8)这个构造器的__set__方法 # SizedString继承顺序:(<class '__main__.SizedString'>, <class '__main__.String'>, # <class '__main__.Typed'>,<class '__main__.MaxSized'>, # <class '__main__.Descriptor'>, <class 'object'>) # self.name = name会调用self.name这个构造器的__set__方法,self.name是SizedString类的实例 # SizedString没有set方法,String没有set方法,Typed有set方法先调用判断传进来的name的value是不是预期的数据类型str # 如果不是str raise报错,如果是str那么走super().__set__(instance, value)跳转继承mro的下一个继承位置运行 # 下一个继承位置是MaxSized有set方法调用判断值是不是在8位范围内,然后再super().__set__(instance, value)运行下一个继承位置的set # 下一个继承位置是Descriptor调用Descriptor的set方法这个set方法直接往对象里加值 self.shares = shares # 和上类似 self.price = price # 和上类似 s = Stock('liergou', 75, 4.0) print(s.name) # liergouprint(s.shares) # 75 s.shares = 10 # s.shares = -10 # 报错,ValueError: Expected >= 0 # s.price = 'a lot' # 报错,TypeError: expected <class 'float'> # s.name = 'ABRACADABRA' # 报错,ValueError: size must be < 8
# 继承分析 class D: def __init__(self): print('D') class A(D): pass class B(): def __init__(self): print('B') class C(A, B): pass C() # D print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.D'>, <class '__main__.B'>, <class 'object'>) class D: def __init__(self): print('D') class A(D): pass class B(D): def __init__(self): print('B') class C(A, B): pass C() # B print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class 'object'>)
c继承A,B,A继承D,如果B不继承D,那么根据深度优先算法,mro继承顺序D在B之前,C默认继承init会先找A,A没有找D,D没有才会找B
c继承A,B,A继承D,如果B继承D,那么mro得继承顺序B在D之前,c继承父类得构造方法__init__会先找A,然后找B,再找D
# 简化上面的代码,使用类装饰器 def check_attributes(**kwargs): def decorate(cls): for key, value in kwargs.items(): if isinstance(value, Descriptor):
# 检测value值是不是Descriptor这个基类的实例或者这个基类下字类的实例
# 如果value是实例,key = name, value = SizedString(size=8), value是一个对象实例 value.name = key # 往value这个实例里添加属性值,增加name="name" setattr(cls, key, value) # 然后往cls这个类里添加属性name=SizedString(size=8, name="name") else:
# 如果检测到value不是实例,那么走下面的逻辑,key=shares, value=UnsignedInteger类名称
# cls类里增加 shares=UnsignedInteger('shares') 这对键和值 setattr(cls, key, value(key)) return cls return decorate @check_attributes(name=SizedString(size=8), shares=UnsignedInteger, price=UnsignedFloat) # Stock = check_attributes(name=SizedString(size=8), # shares=UnsignedInteger, # price=UnsignedFloat)(Stock) class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price
# 使用这个装饰器装饰类本质上还是在这个Stock类里写了三个类属性
# 使用元类 # 应用检查的元类 class checkedmeta(type): def __new__(cls, clsname, bases, methods): # 将属性名称符加到描述符
# clsname 保存类的名字
# bases 保存类的父类object
# methods 会以字典的方式保存所有的类属性 for key, value in methods.items(): # 遍历类属性 if isinstance(value, Descriptor): # 如果value是Descriptor的实例或者字类的实例 value.name = key # value添加属性
# return type.__new__(cls, clsname, bases, methods)
# 调用type类的__new__方法返回一个类 return type.__new__(cls, clsname, bases, methods) class Stock2(metaclass=checkedmeta): name = SizedString(size=8) shares = UnsignedInteger() price = UnsignedFloat() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price
# 简单的类装饰器一,如下,在调用类的时候增加一些前面的代码 def wraper(cls): print(1111) return cls @wraper # A = wraper(A) class A: pass # 类的装饰器二:类似带参数的装饰器 def wraper(**kwarges): def inner(cls): for i, j in kwarges.items(): setattr(cls, i, j) return cls return inner @wraper(name="ywt", age=18) # A = wraper(name="ywt", age=18)(A) # wraper(name="ywt", age=18)返回inner # inner(A)返回添加属性完后A这个类,类似带参数的装饰器 class A: pass print(A.__dict__) # 上面的wraper装饰器本质上就是在A这个类里写类属性 # {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'A' objects>, # '__weakref__': <attribute '__weakref__' of 'A' objects>, # '__doc__': None, 'name': 'ywt', 'age': 18}
# 函数版本带参数的装饰器 #这个bar函数就是带参数的装饰器 def wraper(age): def bar(func): def inner(name): func(name) print("这是新加的功能") print("年龄是:",age) return inner return bar @wraper(18) #第一层:foo=wraper(18)=bar ,foo=bar 第二层:foo=bar函数了,再执行bar函数得到foo=inner #执行foo()相当于执行inner(), def foo(name): print("测试订单:",name) foo("张三")
# Descriptor 基类中你会看到有个 __set__() 方法,却没有相应的 __get__() 方法。 # 如果一个描述仅仅是从底层实例字典中获取某个属性值的话,那么没必要去定义 __get__() 方法 # 所有描述器类都是基于混入类来实现的。比如 Unsigned 和 MaxSized 要跟其他继承自 Typed 类混入。 利用多继承来实现相应的功能 # 调用 super() 函数时,调用哪个具体类。 你需要跟其他类结合后才能正确的使用,也就是必须合作才能产生效果 # 使用类装饰器和元类通常可以简化代码。上面两个例子中只需要输入一次属性名即可了,如下面,直接输入x = Integer() 就可以了
class Point: x = Integer('x') y = Integer('y') # 使用原类的可以少传 class Point(metaclass=checkedmeta): x = Integer() y = Integer()
# 类装饰器方案最灵活和最高明的 # 装饰器还能作为混入类的替代技术来实现同样的效果 # 应用类型检查的装饰器 def Typed(expected_type, cls=None): if cls is None: return lambda cls: Typed(expected_type, cls) super_set = cls.__set__ def __set__(self, instance, value): if not isinstance(value, expected_type): raise TypeError('expected ' + str(expected_type)) super_set(self, instance, value) cls.__set__ = __set__ return cls # 无符号值的装饰器 def Unsigned(cls): super_set = cls.__set__ def __set__(self, instance, value): if value < 0: raise ValueError('Expected >= 0') super_set(self, instance, value) cls.__set__ = __set__ return cls # 允许大小值的装饰器 def MaxSized(cls): super_init = cls.__init__ def __init__(self, name=None, **opts): if 'size' not in opts: raise TypeError('missing size option') super_init(self, name, **opts) cls.__init__ = __init__ super_set = cls.__set__ def __set__(self, instance, value): if len(value) >= self.size: raise ValueError('size must be < ' + str(self.size)) super_set(self, instance, value) cls.__set__ = __set__ return cls # 专门描述符 @Typed(int) class Integer(Descriptor): pass @Unsigned class UnsignedInteger(Integer): pass @Typed(float) class Float(Descriptor): pass @Unsigned class UnsignedFloat(Float): pass
@Typed(str) class String(Descriptor): pass @MaxSized class SizedString(String): pass # 这种方式定义的类跟之前的效果一样,而且执行速度会更快。
设置一个简单的类型属性的值,装饰器方式要比之前的混入类的方式几乎快100%
# 上面装饰器装饰类的方式代码详解 def Typed(expected_type, cls=None): if cls is None: return lambda cls: Typed(expected_type, cls) super_set = cls.__set__ def __set__(self, instance, value): if not isinstance(value, expected_type): raise TypeError('expected ' + str(expected_type)) super_set(self, instance, value) cls.__set__ = __set__ return cls def MaxSized(cls): super_init = cls.__init__ def __init__(self, name=None, **opts): if 'size' not in opts: raise TypeError('missing size option') super_init(self, name, **opts) cls.__init__ = __init__ super_set = cls.__set__ def __set__(self, instance, value): if len(value) >= self.size: raise ValueError('size must be < ' + str(self.size)) super_set(self, instance, value) cls.__set__ = __set__ return cls # 描述器 class Descriptor: def __init__(self, name=None, **opts): self.name = name for key, value in opts.items(): setattr(self, key, value) def __set__(self, instance, value): instance.__dict__[self.name] = value # String = Typed(str)(String)详解如下: # 先执行Typed(str):Typed(expected_type, cls=None) # expected_type=str,cls=None,cls=None返回一个lambda函数:lambda cls: Typed(expected_type, cls) # 这个lambda函数固定了expected_type = str,需要再接收一个cls的参数, # 再执行:Typed(str)(String),Typed(str)返回的事一个lambda函数,lambda函数刚好需要一个cls # 现在lambda函数(String类)等同执行Typed(str, cls=String) # super_set = String.__set__ ,String没有双下set方法会找到Descriptor这个构造器的双下set方法赋值给super_set # 然后重构__set__方法,新构建的双下__set__是先判断value的类型等不等于预期然后调用super_set # 相当于使用装饰器给String这个类添加了内部代码,固定了expected_type=str,内部重写了个__set__方法, # 在Descriptor这个基类的基上重构__set__ @Typed(str) # String = Typed(str)(String) class String(Descriptor): pass @MaxSized # SizedString = MaxSized(SizedString) # SizedString类继承String类,然后使用装饰器MaxSized, # MaxSized(SizedString): # super_init = SizedString.__init__,SizedString没有__init__, # 第一父类String也没有__init__,所以调用Descriptor这个构造器的__init__ # 然后重构__init__,如果实例化的时候没有传size=xxx参数就报错TypeError: missing size option,然后调用Descriptor的__init__ # super_set = cls.__set__,SizedString没有__set__,String有__set__(被上面装饰器重构的__set__),找到后存储到super_set # 然后重构__set__方法,判断传进来的value的长度是不是小于size,大于报错,然后调用super_set class SizedString(String): pass class Stock: name = SizedString('name', size=8) # 类继承顺序:(<class '__main__.SizedString'>, <class '__main__.String'>, # <class '__main__.Descriptor'>, <class 'object'>) # SizedString('name', size=8)调用被@MaxSized装饰后重构的Descriptor的__init__方法, # 先判断有没有size=xxx位置参数,然后把属性写到name这个实例化对象里,-- # shares = UnsignedInteger('shares') # price = UnsignedFloat('price') def __init__(self, name, shares, price): self.name = name # 类继承顺序:(<class '__main__.SizedString'>, <class '__main__.String'>, # <class '__main__.Descriptor'>, <class 'object'>) # self.name继承String和Descriptor,所以self.name是一个描述器 # self.name = name触发重构后的__set__方法,先判断MaxSized装饰里面的__set__方法,长度是不是小于预期size8 # 然后判断Typed里面的__set__装饰的代码,if not isinstance(value, expected_type):类型等不等于预期 # 连哥哥都通过了才调用Descriptor的__set__,往实例对象self里面写入键值对 # self.shares = shares # self.price = price
类装饰器的本质还是给类里重构代码,重构__init__或者__set__方法,本质上还是重构 SizedString这个基类,通过继承和类装饰器等方法
26:实现自定义容器 自定义的类来模拟内置的容器类功能,比如列表和字典
# collections 定义了很多抽象基类 # 自定义容器类的时候它们会非常有用。 比如你想让你的类支持迭代,那就让你的类继承 collections.Iterable就可以了 import collections class A(collections.Iterable): pass a = A() # TypeError: Can't instantiate abstract class A with abstract method __iter__ # 需要实现 collections.Iterable 所有的抽象方法,否则会报错: # 实现 __iter__() 方法就不会报错了(参考4.2和4.7小节) import collections.abc class A(collections.abc.Iterable): def __iter__(self): return '33333' a = A()
# 实例化一个对象,在错误提示中可以找到需要实现哪些方法import collections.abc import bisect # collections.abc.Sequence() # TypeError: Can't instantiate abstract class Sequence with abstract methods __getitem__, __len__class SortedItems(collections.abc.Sequence): def__init__(self, initial=None): self._items = sorted(initial) if initial isnot None else [] # 所需序列方法def__getitem__(self, index): return self._items[index] def__len__(self): return len(self._items) # 在正确位置添加项的方法def add(self, item): bisect.insort(self._items, item) items = SortedItems([5, 1, 3]) print(list(items)) # [1, 3, 5] print(items[0], items[-1]) # 1 5 items.add(2) print(list(items)) # [1, 2, 3, 5]
# SortedItems跟普通的序列没什么两样,支持所有常用操作,包括索引、迭代、包含判断,甚至是切片操作
# 使用到了 bisect 模块,它是一个在排序列表中插入元素的高效方式。可以保证元素插入后还保持顺序
# 上面的例子SortedItems不继承collections.abc.Sequence只要实现了__getitem__和__len__就能实现一个序列类
# 使用 collections 中的抽象基类可以确保你自定义的容器实现了所有必要的方法。
还能简化类型检查。 你的自定义容器会满足大部分类型检查需要 import collections.abc import bisect class SortedItems(collections.abc.Sequence): def __init__(self, initial=None): self._items = sorted(initial) if initial is not None else [] # 所需序列方法 def __getitem__(self, index): return self._items[index] def __len__(self): return len(self._items) # 在正确位置添加项的方法 def add(self, item): bisect.insort(self._items, item) items = SortedItems() print(list(items)) # [] print(isinstance(items, collections.abc.Iterable)) # 检查items对象是否可迭代 True print(isinstance(items, collections.abc.Sequence)) # 检查items对象是否是一个序列 True print(isinstance(items, collections.abc.Container)) # 容器 True print(isinstance(items, collections.abc.Sized)) # 大小 True print(isinstance(items, collections.abc.Mapping)) # 映射 False # 如果class SortedItems():不继承collections.abc.Sequence,那么属性判断的时候只有是sized序列返回True items = SortedItems() print(list(items)) # [] print(isinstance(items, collections.abc.Iterable)) # 检查items对象是否可迭代 False print(isinstance(items, collections.abc.Sequence)) # 检查items对象是否是一个序列 False print(isinstance(items, collections.abc.Container)) # 容器 False print(isinstance(items, collections.abc.Sized)) # 大小 True print(isinstance(items, collections.abc.Mapping)) # 映射 False
# collections 中很多抽象类会为一些常见容器操作提供默认的实现,
这样一来你只需要实现那些你最感兴趣的方法即可。假设你的类继承自 collections.MutableSequence import collections.abc class Items(collections.abc.MutableSequence): def __init__(self, initial=None): self._items = list(initial) if initial is not None else [] # 所需序列方法,必须重构这些方法才是SutableSequence类型,不会报错 def __getitem__(self, index): print('Getting:', index) return self._items[index] def __setitem__(self, index, value): print('Setting:', index, value) self._items[index] = value def __delitem__(self, index): print('Deleting:', index) del self._items[index] def insert(self, index, value): print('Inserting:', index, value) self._items.insert(index, value) def __len__(self): print('Len') return len(self._items) # 上面实现得是一个序列类(拥有序列的基本操作方法) # 如果你创建 Items 的实例,你会发现它支持几乎所有的核心列表方法(如append()、remove()、count()等 a = Items([1, 2, 3]) print(len(a)) # 调用__len__方法, 打印Len,3 a.append(4) # append操作先调用__len__方法查询长度,然后再调用insert方法往最后一个插入数据 打印:Len,Inserting: 3 4 a.append(2) # Len,Inserting: 3 2 print(a.count(2)) # count计算2这个数字出现的次数,出现2次,需要循环调用__getitem__遍历查询每一个元素然后统计次数 # 打印:Getting: 0,Getting: 1,Getting: 2,Getting: 3,Getting: 4,Getting: 5,2 a.remove(3) # remove删除3这个元素,需要循环调用__getitem__然后找到3这个元素后执行__delitem__ # 打印:Getting: 0,Getting: 1,Getting: 2,Deleting: 2
必须要继承collections.abc.MutableSequence这个序列的抽象类才能是一个完整的序列,才能调用append,remove等各种方法,
调用append和remove这些方法本质上也是调用Items类内部自己实现的一些双下__getitem__等一些方法的组合
27:属性的代理访问 某个实例的属性访问代理到内部另一个实例中去,目的可能是作为继承的一个替代方法或者实现代理模式
# 代理是一种编程模式,它将某个操作转移给另外一个对象来实现 # 简单代理 class A: def spam(self, x): pass def foo(self): pass class B1: """简单的代理""" def __init__(self): self._a = A() def spam(self, x): # 委托给内部自我。self._a实例 return self._a.spam(x) def foo(self): # 委托给内部自我。self._a实例 return self._a.foo() def bar(self): pass # b的__init__里定义一个要代理a类的实例然后外包访问b的实例实现和方法直接调用a的属性和方法
# 如果有大量的方法需要代理, 那么使用 __getattr__() # __getattr__ 方法是在访问attribute不存在的时候被调用 class A: def spam(self, x): return f'Spam:{x}' def foo(self): return 'Foo' class B2: """使用__getattr__的代理,代理方法比较多时候""" def __init__(self): self._a = A() def bar(self): pass # 公开在类A上定义的所有方法 def __getattr__(self, name): """ 这个方法在访问的attribute不存在的时候被调用 __getattr__() 方法实际上是一种回退方法 仅在找不到属性时调用 """ return getattr(self._a, name) b = B2() b.bar() # 调用B2.bar(b) print(b.spam(100)) # Spam:100 调用 B2.__getattr__('spam') 以及 A.spam()
# 输出:Spam:100 b.spam(100) = B2.__getattr__(b, 'spam')(100)
# b.spam,先从b这个实例里找spam属性,找不到找上层的类B2,B2的类里还是找不到spam属性找父类objcet还是找不到
# 通过mro继承顺序都找不到的spam属性时候就会调用__getattr__的方法,
# b.spam = B2.__getattr__(b, 'spam') = getattr(b._a, 'spam')=getattr(A(), 'spam')
# 等同从对象A()内存空间里面找到'spam'这个函数的内存地址然后返回,A()对象没有这个内存地址从A类里找spam属性
# A类里找到spam属性后返回的是'spam'这个函数的内存地址,内存地址+括号才是调用
# 所以b.spam返回的是从A类里找到的spam这个返回的内存地址b.spam(100) = A().spam(100)
# 实现代理模式 # 环绕另一个对象的代理类,但也暴露了它的公共属性 class Proxy: def __init__(self, obj): self._obj = obj # obj是一个实例 # 将属性查找委托给内部obj def __getattr__(self, name): print('getattr:', name) return getattr(self._obj, name) # 委托属性分配 def __setattr__(self, name, value): if name.startswith('_'): super().__setattr__(name, value) else: print('setattr:', name, value) setattr(self._obj, name, value) # 委托属性删除 def __delattr__(self, name): if name.startswith('_'): super().__delattr__(name) else: print('delattr:', name) delattr(self._obj, name) # 使用这个代理类时,你只需要用它来包装下其他类即可: class Spam: def __init__(self, x): self.x = x def bar(self, y): print('Spam.bar:', self.x, y) s = Spam(2) # 创建一个实例.内部存储{'x': 2} p = Proxy(s) # 围绕它创建一个代理 # 访问代理服务器 # print(p.x) # 输出:getattr: x,2 # p是Proxy的实例,没有x属性,找不到x属性就调用 __getattr__方法 # Proxy.__getattr__(p, 'x')——> getattr(p._obj, 'x')——>getattr(s, 'x') # 最终return返回的就是s实例里找'x'属性的值 # p.bar(3) # 输出:getattr: bar,Spam.bar: 2 3 # p.bar=Proxy.__getattr__(p, 'bar')——> getattr(p._obj, 'bar')——>getattr(s, 'bar') # p.bar=本质上是从s对象里找到'bar'属性,然后调用bar(3) ,打印'Spam.bar:',self.x=2, y=3 # p.x = 37 # 输出:setattr: x 37 将s.x更改为37 # p.x = 37,不管p里面有没有x属性,本质上调用p对象里面的__setattr__方法 # 走else,打印'setattr:', name='x', value=37, # 然后setattr(self._obj, name, value),往p._obj=s对象里写'x'=37这个属性
# 通过自定义属性访问方法,你可以用不同方式自定义代理类行为(比如加入日志功能、只读访问等)
# 代理类可以作为继承的替代方案 # 继承的例子 class A: def spam(self, x): print('A.spam', x) def foo(self): print('A.foo') class B(A): def spam(self, x): print('B.spam') super().spam(x) def bar(self): print('B.bar') # 使用代理类的例子 class A: def spam(self, x): print('A.spam', x) def foo(self): print('A.foo') class B: def __init__(self): self._a = A() def spam(self, x): print('B.spam', x) self._a.spam(x) def bar(self): print('B.bar') def __getattr__(self, name): return getattr(self._a, name) # 当实现代理模式时,有些细节需要注意。 首先,__getattr__() 实际是一个后备方法,只有在属性不存在时才会调用。
因此,如果代理类实例本身有这个属性的话,那么不会触发这个方法的。
另外,__setattr__() 和 __delattr__() 需要额外的魔法来区分代理实例和被代理实例 _obj 的属性。
一个通常的约定是只代理那些不以下划线 _ 开头的属性(代理类只暴露被代理类的公共属性)
# __getattr__() 对于大部分以双下划线(__)开始和结尾的属性并不适用 class ListLike: """__getattr__对于双下划线开始和结尾的方法是不能用的,需要一个个去重定义""" def __init__(self): self._items = [] def __getattr__(self, name): return getattr(self._items, name) # 如果是创建一个ListLike对象,会发现它支持普通的列表方法,如append()和insert(), 但是却不支持len()、元素查找等 a = ListLike() a.append(2) a.insert(0, 1) a.sort() len(a) # 报错:TypeError: object of type 'ListLike' has no len()
a.__len__() 想要求len长度需要这也调用才不会报错,
因为len(a)不会调用__getattr__方法,只会当前a对象的类ListLike里面的__len__方法,ListLike类里没有__len__方法 # 为了让ListLike支持这些方法,必须手动的实现这些方法代理: class ListLike: """__getattr__对于双下划线开始和结尾的方法是不能用的,需要一个个去重定义""" def __init__(self): self._items = [] def __getattr__(self, name): return getattr(self._items, name) # Added special methods to support certain list operations def __len__(self): return len(self._items) def __getitem__(self, index): return self._items[index] def __setitem__(self, index, value): self._items[index] = value def __delitem__(self, index): del self._items[index] a = ListLike() a.append(2) a.insert(0, 1) a.sort() print(len(a)) # 2
28:在类中定义多个构造器 __init__()
方法外,类中其他初始化方法
# 实现多个构造器,需要使用到类方法 import time class Date: """方法一:使用类方法""" # 主构造函数 def __init__(self, year, month, day): self.year = year self.month = month self.day = day # 替代构造函数 @classmethod def today(cls): t = time.localtime() return cls(t.tm_year, t.tm_mon, t.tm_mday)
# return Date(t.tm_year, t.tm_mon, t.tm_mday)返回的本质上还是Date这个类的实例,时间是今天的 a = Date(2012, 12, 21) # 主要的,重要的 b = Date.today() # 交替
# 本质上就是一个类构造了两个返回对象,__init__返回的是自己可以随意控制输入的时间来实例化
# today是当前日期输入来实例化
# 类方法的一个主要用途就是定义多个构造器。它接受一个 class 作为第一个参数(cls)。 # 这个类被用来创建并返回最终的实例。在继承时也能工作的很好 import time class Date: """方法一:使用类方法""" # 主构造函数 def __init__(self, year, month, day): self.year = year self.month = month self.day = day # 替代构造函数 @classmethod def today(cls): t = time.localtime() return cls(t.tm_year, t.tm_mon, t.tm_mday) class NewDate(Date): pass c = Date.today() # Creates an instance of Date (cls=Date) d = NewDate.today() # Creates an instance of NewDate (cls=NewDate)
类方法也能被完美继承下来
29:创建不调用init方法的实例 想创建一个实例,希望绕过执行 __init__()
方法
# 通过 __new__() 方法创建一个未初始化的实例 class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day d = Date.__new__(Date) # __new__方法是类方法,需要传类来是实例化,所以Date.__new__(Date) d # <__main__.Date object at 0x000001E80A139E20>
d.year # 报错:AttributeError: 'Date' object has no attribute 'year'
Date实例的属性year还不存在,需要手动初始化
data = {'year': 2012, 'month': 8, 'day': 29}
for key, value in data.items():
setattr(d, key, value)
print(d.year) # 2012
# 反序列对象或者实现某个类方法构造函数时需要绕过 __init__() 方法来创建对象。 # 如:Date来讲,有时候你可能会像下面这样定义一个新的构造函数 today() from time import localtime class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def today(cls): d = cls.__new__(cls) t = localtime() d.year = t.tm_year d.month = t.tm_mon d.day = t.tm_mday return d now = Date.today() print(now.day) # 27 print(now.month) # 8 # 反序列化JSON数据时产生一个如下的字典对象,换成一个Date类型实例 data = {'year': 2012, 'month': 8, 'day': 29} _day = Date(data['year'], data['month'], data['day']) print(_day.day) # 29
# 通过这种非常规方式来创建实例的时候,最好不要直接去访问底层实例字典,
除非你真的清楚所有细节。 否则的话,如果这个类使用了 __slots__ 、properties 、descriptors 或其他高级技术的时候代码就会失效。
而这时候使用 setattr() 方法会让你的代码变得更加通用
多使用setattr()
30:利用Mixins扩展类功能 很多有用的方法,想使用它们来扩展其他类的功能,这些类并没有任何继承的关系,不能简单的将这些方法放入一个基类,然后被其他类继承。
# 某个库提供了一些基础类,可以利用它们来构造你自己的类 # 扩展映射对象,给它们添加日志、唯一性设置、类型检查等等功能 from collections import defaultdict class LoggedMappingMixin: """ 添加日志以获取/设置/删除调试操作。 """ __slots__ = () # 混入类都没有实例变量,因为直接实例化混入类没有任何意义 def __getitem__(self, key): print('Getting ' + str(key)) return super().__getitem__(key) def __setitem__(self, key, value): print('Setting {} = {!r}'.format(key, value)) return super(LoggedMappingMixin, self).__setitem__(key, value) def __delitem__(self, key): print('Deleting ' + str(key)) return super().__delitem__(key) class SetOnceMappingMixin: """ 只允许设置一次密钥。 """ __slots__ = () def __setitem__(self, key, value): if key in self: raise KeyError(str(key) + ' already set') return super().__setitem__(key, value) class StringKeysMappingMixin: """ 仅将键限制为字符串 """ __slots__ = () def __setitem__(self, key, value): if not isinstance(key, str): raise TypeError('keys must be strings') return super().__setitem__(key, value) # 这些类单独使用起来没有任何意义,事实上如果你去实例化任何一个类,除了产生异常外没任何作用。 # 它们是用来通过多继承来和其他映射对象混入使用的 class LoggedDict(LoggedMappingMixin, dict): pass d = LoggedDict() d['x'] = 23 # 调用当前LoggedDict类的__setitem__,LoggedDict类当前没有__setitem__方法,找第一父类LoggedMappingMixin的__setitem__方法 # 先:print('Setting {} = {!r}'.format(key, value)) 打印:Setting x = 23 # 然后:super(LoggedMappingMixin, self).__setitem__(key, value),self是d实例 # super(LoggedMappingMixin)找LoggedDict继承顺序表LoggedMappingMixin爹的下一个爹dict,调用dict类的.__setitem__ # d继承了dict是一个字典,等同 dict.__setitem__(d, 'x', 23) 所以最终会在这个字典里写入'x'=23这个键值对 # print(d) # 输出:{'x': 23} print(d['x']) # 逻辑和上面了类似,先调用LoggedMappingMixin父类__getitem__打印Getting x, # 然后LoggedMappingMixin父类的return super().__getitem__(key)调用dict类的__getitem__方法 # d['x']本质上等同 dict.__getitem__(d, 'x')打印d这个字典对象对应键x的值 del d['x'] # 逻辑和上面了类似,先调用LoggedMappingMixin父类__delitem__打印Deleting x, # 然后LoggedMappingMixin父类的return super().__delitem__(key)调用dict类的__delitem__方法 # del d['x']本质上等同 dict.__delitem__(d, 'x')山粗dict这个字典里面的x print(d) # {} print('----------------------------------------------------------') class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict): pass d = SetOnceDefaultDict(list) # 默认字典,值默认是一个list列表 # d['z'] = 'z' # d是一个默认字典,d['z'] = 'z'会调用SetOnceDefaultDict类的__setitem__方法,逻辑和上类似, # 先调用SetOnceMappingMixin的__setitem__方法 # 然后return super().__setitem__(key, value)调用defaultdict默认字典类的__setitem__方法 # 等同defaultdict.__setitem__(d, 'z', 'z'),会在d这个默认字典存一对键值 d['x'].append(2) # {'x': [2]} # 先调用d['x'], d是默认字典,d['x']检查到'x'这个键在字典d里不存在,那么先调用__setitem__方法 # 先调用SetOnceMappingMixin的__setitem__方法, if key in self:不通过,然后return super().__setitem__(key, value) # 调用defaultdict默认字典类的__setitem__方法,defaultdict.__setitem__(d, 'z', []),先在d这个默认字典里创建一个'x'=[]的键值 # 然后再d['x'].append(2),d['x']返回[],往[]里增加2 # print(d) # SetOnceDefaultDict(<class 'list'>, {'x': [2]}) d['x'].append(3) # d['x'],在默认字典d中能找到'x'这个键,所以不会调用__setitem__方法,直接找到d['x']这个值往里增加元素3 # print(d) # SetOnceDefaultDict(<class 'list'>, {'x': [2, 3]}) d['x'] = 23 # KeyError: 'x already set' # d['x'] = 23 不会判断'x'这个键在不在字典d里,直接调用__setitem__方法 # 先调用SetOnceMappingMixin的__setitem__方法,if key in self: # 当前key = ’x‘, self = d,d这个默认字典有'x'这个键,走if里面的语句raise KeyError(str(key) + ' already set')报错
这个例子中,可以看到混入类跟其他已存在的类(比如dict、defaultdict和OrderedDict)结合起来使用,一个接一个。 结合后就能发挥正常功效了。
# 混入类在标准库中很多地方都出现过,通常都是用来像上面那样扩展某些类的功能。
它们也是多继承的一个主要用途。比如,当你编写网络代码时候,
你会经常使用 socketserver 模块中的 ThreadingMixIn 来给其他网络相关类增加多线程支持。 例如,下面是一个多线程的XML-RPC服务 from xmlrpc.server import SimpleXMLRPCServer from socketserver import ThreadingMixIn class ThreadedXMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer): pass 同时在一些大型库和框架中也会发现混入类的使用,用途同样是增强已存在的类的功能和一些可选特征。 对于混入类,有几点需要记住。首先是,混入类不能直接被实例化使用。 其次,混入类没有自己的状态信息,
也就是说它们并没有定义 __init__() 方法,并且没有实例属性。 这也是为什么我们在上面明确定义了 __slots__ = ()
# 使用类装饰器实现混入类def LoggedMapping(cls): """第二种方式:使用类装饰器""" # 得到传进来的cls类的__getitem__,__setitem__,__delitem__方法存储起来 # 然后下面重构__getitem__,__setitem__,__delitem__方法 # 最后把__getitem__,__setitem__,__delitem__方法放到cls这个类里重写进去
# 方法写到cls里面后再return返回cls这个类 cls_getitem = cls.__getitem__ cls_setitem = cls.__setitem__ cls_delitem = cls.__delitem__ def __getitem__(self, key): print('Getting ' + str(key)) return cls_getitem(self, key) def __setitem__(self, key, value): print('Setting {} = {!r}'.format(key, value)) return cls_setitem(self, key, value) def __delitem__(self, key): print('Deleting ' + str(key)) return cls_delitem(self, key) cls.__getitem__ = __getitem__ cls.__setitem__ = __setitem__ cls.__delitem__ = __delitem__ return cls @LoggedMapping # LoggedDict = LoggedMapping(LoggedDict) class LoggedDict(dict): pass a = LoggedDict() a['x'] = 10 # 打印:Setting x = 10
# 上面的方法等同时重构类 LoggedDict的__getitem__,__setitem__,__delitem__然后再写入LoggedDict类里
31:实现状态对象或者状态机 实现一个状态机或者是在不同状态下执行操作的对象
# 对象会根据状态的不同来执行不同的操作 # 如下的一个连接对象 class Connection: """普通方案,好多个判断语句,效率低下~~""" def __init__(self): self.state = 'CLOSED' def read(self): if self.state != 'OPEN': raise RuntimeError('Not open') print('reading') def write(self, data): if self.state != 'OPEN': raise RuntimeError('Not open') print('writing') def open(self): if self.state == 'OPEN': raise RuntimeError('Already open') self.state = 'OPEN' def close(self): if self.state == 'CLOSED': raise RuntimeError('Already closed') self.state = 'CLOSED' # 这样写代码太复杂了,很多的条件判断。执行效率变低,
因为一些常见的操作比如read()、write()每次执行前都需要执行检查。
# 每个状态定义一个对象 class Connection1: """新方案——对每个状态定义一个类""" def __init__(self): self.new_state(ClosedConnectionState) def new_state(self, newstate): self._state = newstate # Delegate to the state class def read(self): return self._state.read(self) def write(self, data): return self._state.write(self, data) def open(self): return self._state.open(self) def close(self): return self._state.close(self) # 连接状态基类 class ConnectionState: @staticmethod def read(conn): raise NotImplementedError() @staticmethod def write(conn, data): raise NotImplementedError() @staticmethod def open(conn): raise NotImplementedError() @staticmethod def close(conn): raise NotImplementedError() # 不同国家的执行情况 class ClosedConnectionState(ConnectionState): @staticmethod def read(conn): raise RuntimeError('Not open') @staticmethod def write(conn, data): raise RuntimeError('Not open') @staticmethod def open(conn): conn.new_state(OpenConnectionState) @staticmethod def close(conn): raise RuntimeError('Already closed') class OpenConnectionState(ConnectionState): @staticmethod def read(conn): print('reading') @staticmethod def write(conn, data): print('writing') @staticmethod def open(conn): raise RuntimeError('Already open') @staticmethod def close(conn): conn.new_state(ClosedConnectionState) c = Connection() print(c._state) # <class '__main__.ClosedConnectionState'> c.read() # RuntimeError: Not open
本质上在Connection这个类实例化的时候写一个实例属性self._state = 状态类
后面Connection这个类里面的read,write,open,close等操作都是调用状态类的read,write,open,close方法
# 代码中出现太多的条件判断语句的话,代码就会变得难以维护和阅读。
# 解决方案是将每个状态抽取出来定义成一个类
# 基类ConnectionState中定义的 NotImplementedError 是为了确保子类实现了相应的方法
32:通过字符串调用对象方法 getattr()
# 字符串形式的方法名称,想通过它调用某个对象的对应方法。 # 使用 getattr() import math class Point: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Point({!r:},{!r:})'.format(self.x, self.y) def distance(self, x, y): return math.hypot(self.x - x, self.y - y) p = Point(2, 3) print(getattr(p, 'distance')(0, 0)) # 3.6055512754639896 # 这里调用:p.distance(0, 0)这个方法
# 另外一种方法是使用 operator.methodcaller() import math import operator class Point: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Point({!r:},{!r:})'.format(self.x, self.y) def distance(self, x, y): return math.hypot(self.x - x, self.y - y) p = Point(2, 3) print(operator.methodcaller('distance', 0, 0)(p)) # 打印:3.6055512754639896 # 需要通过相同的参数多次调用某个方法时,使用 operator.methodcaller 就很方便了。 比如你需要排序一系列的点, points = [ Point(1, 2), Point(3, 0), Point(10, -3), Point(-5, -7), Point(-1, 8), Point(3, 2) ] # 按与原点的距离排序(0,0) points.sort(key=operator.methodcaller('distance', 0, 0))
key=operator.methodcaller('distance', 0, 0)等同每个points列表里的点都调用operator.methodcaller('distance', 0, 0)
每个都调用operator.methodcaller('distance', 0, 0)(point)
operator.methodcaller('distance', 0, 0)(p)
operator.methodcaller('方法字符形式',方法参数1,方法参数2)(对象)
operator.methodcaller('方法字符形式',方法参数1,方法参数2)这个类似方法一个方法,需要传递一个对象才能调用
# 调用一个方法实际上是两步独立操作,第一步是查找属性,第二步是函数调用。 # 因此,为了调用某个方法,你可以首先通过 getattr() 来查找到这个属性,然后再去以函数方式调用它即可 # operator.methodcaller() 创建一个可调用对象,并同时提供所有必要参数, 然后调用的时候只需要将实例对象传递给它即可 p = Point(3, 4) d = operator.methodcaller('distance', 0, 0) d(p)
33:实现访问者模式 处理由大量不同类型的对象组成的复杂数据结构,每一个对象都需要进行不同的处理 遍历一个树形结构,然后根据每个节点的相应状态执行不同的操作
# 有时候会构建一个由大量不同对象组成的数据结构 # 写一个表示数学表达式的程序 class Node: pass class UnaryOperator(Node): def __init__(self, operand): self.operand = operand class BinaryOperator(Node): def __init__(self, left, right): self.left = left self.right = right class Add(BinaryOperator): # 加类 pass class Sub(BinaryOperator): # 减类 pass class Mul(BinaryOperator): # 乘类 pass class Div(BinaryOperator): # 除类 pass class Negate(UnaryOperator): # pass class Number(Node): def __init__(self, value): self.value = value # 然后利用这些类构建嵌套数据结构 # 下面四行代码结合起来代表 1 + 2 * (3 - 4) / 5 t1 = Sub(Number(3), Number(4)) # (3-4) t2 = Mul(Number(2), t1) # 2*(3-4) t3 = Div(t2, Number(5)) # 2*(3-4)/5 t4 = Add(Number(1), t3) # 1 + 2*(3-4)/5
这样做对于每个表达式,每次都要重新定义一遍
# 实现一种更通用的方式让它支持所有的数字和操作符 # 使用访问者模式可以达到这样的目的 class NodeVisitor: def visit(self, node): methname = 'visit_' + type(node).__name__ meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node) def generic_visit(self, node): raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) # 为了使用这个类,可以定义一个类继承它并且实现各种 visit_Name() 方法,其中Name是node类型。 例如,如果你想求表达式的值 class Evaluator(NodeVisitor): def visit_Number(self, node): return node.value def visit_Add(self, node): return self.visit(node.left) + self.visit(node.right) def visit_Sub(self, node): return self.visit(node.left) - self.visit(node.right) def visit_Mul(self, node): return self.visit(node.left) * self.visit(node.right) def visit_Div(self, node): return self.visit(node.left) / self.visit(node.right) def visit_Negate(self, node): return -node.operand e = Evaluator() t1 = Sub(Number(3), Number(4)) t2 = Mul(Number(2), t1) t3 = Div(t2, Number(5)) t4 = Add(Number(1), t3) print(e.visit(t4)) # 等同:print(e.visit(Add(Number(1), Div(Mul(Number(2), Sub(Number(3), Number(4))), Number(5))))) # e.visit(t4) = e.visit(Add(Number(1), t3)),node = Add(Number(1), t3) # type(Add(Number(1), t3)).__name__返回add, # methname = "visit_add",然后使用getattr返回visit_add函数来计算,从self当前实例找到'visit_add'方法 # getattr(self, methname, None),如果找到methname就返回这个方法,找不到返回None # 如果meth找到返回的是None,调用generic_visit方法会raise报错, # 如果getattr(self, methname, None)找到了就return返回visit_add(node)方法调用:visit_add(Add(Number(1), t3)) # visit_add(Add(Number(1), t3))调用——>visit(node.left) + visit(node.right)(node.left=Number(1), node.right=t3) # ——>visit(Number(1)) + visit(t3) # visit(Number(1))——>调用visit_Number(Number(1))——>返回Number(1).value=1 # visit(t3) ——> visit(Div(t2, Number(5)))——>visit_Div(Div(t2, Number(5))) # ——>visit(t2) - visit(Number(5))——>visit(Mul(Number(2), t1)) - 5 # 后面的步骤和上面一样一步步分解 # 1+t3 ——>t3=t2/5——>t2=2-t1——>t1=3+4
# 一个类在一个栈上面将一个表达式转换成多个操作序列 class Node: pass class UnaryOperator(Node): def __init__(self, operand): self.operand = operand class BinaryOperator(Node): def __init__(self, left, right): self.left = left self.right = right class Add(BinaryOperator): pass class Sub(BinaryOperator): pass class Mul(BinaryOperator): pass class Div(BinaryOperator): pass class Negate(UnaryOperator): pass class Number(Node): def __init__(self, value): self.value = value class NodeVisitor: def visit(self, node): methname = 'visit_' + type(node).__name__ meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node) def generic_visit(self, node): raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) class StackCode(NodeVisitor): def generate_code(self, node): self.instructions = [] self.visit(node) return self.instructions def visit_Number(self, node): self.instructions.append(('PUSH', node.value)) def binop(self, node, instruction): self.visit(node.left) self.visit(node.right) self.instructions.append((instruction,)) def visit_Add(self, node): self.binop(node, 'ADD') def visit_Sub(self, node): self.binop(node, 'SUB') def visit_Mul(self, node): self.binop(node, 'MUL') def visit_Div(self, node): self.binop(node, 'DIV') def unaryop(self, node, instruction): self.visit(node.operand) self.instructions.append((instruction,)) def visit_Negate(self, node): self.unaryop(node, 'NEG') t1 = Sub(Number(3), Number(4)) t2 = Mul(Number(2), t1) t3 = Div(t2, Number(5)) t4 = Add(Number(1), t3) s = StackCode() print(s.generate_code(t4))
# 返回:[('PUSH', 1), ('PUSH', 2), ('PUSH', 3), ('PUSH', 4), ('SUB',), ('MUL',), ('PUSH', 5), ('DIV',), ('ADD',)]
# s.generate_code(t4) node = t4 # visit(t4)——>visit(Add(Number(1), t3))——>visit_Add(Add(Number(1), t3)) # binop(Add(Number(1), t3), 'ADD')——> # visit(node.left)——>visit(Number(1))——列表instructions中添加('PUSH', 1) # visit(node.right)——>visit(t3)——>visit(Div(t2, Number(5)))——>visit_Div(Div(t2, Number(5)))——> # binop(Div(t2, Number(5)), 'DIV')流程类似就这这也一步步往列表里加表达式来实现 # instructions.append((instruction,)) instruction='ADD',列表instructions中添加('ADD',)
# 访问者模式的好处就是通过 getattr() 来获取相应的方法,并利用递归来遍历所有的节点: def binop(self, node, instruction): self.visit(node.left) self.visit(node.right) self.instructions.append((instruction,)) # 这种技术也是实现其他语言中switch或case语句的方式。 # 如果你正在写一个HTTP框架,你可能会写这样一个请求分发的控制器 class HTTPHandler: def handle(self, request): methname = 'do_' + request.request_method getattr(self, methname)(request) def do_GET(self, request): pass def do_POST(self, request): pass def do_HEAD(self, request): pass # 访问者模式一个缺点就是它严重依赖递归,如果数据结构嵌套层次太深可能会有问题,
有时候会超过Python的递归深度限制(参考 sys.getrecursionlimit() )
34:不用递归实现访问者模式 访问者模式遍历一个很深的嵌套树形数据结构可能超过递归限制失败,使用生成器可以在树遍历或搜索算法中消除递归
# 利用一个栈和生成器以在树遍历或搜索算法中消除递归 # 栈“先进后出,后进先出”(类似类把的append+pop,类把append是往后插入,pop是往后取) import types class Node: pass class NodeVisitor: def visit(self, node): stack = [node] last_result = None while stack: try: last = stack[-1] if isinstance(last, types.GeneratorType): stack.append(last.send(last_result)) last_result = None elif isinstance(last, Node): stack.append(self._visit(stack.pop())) else: last_result = stack.pop() except StopIteration: stack.pop() return last_result def _visit(self, node): methname = 'visit_' + type(node).__name__ meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node) def generic_visit(self, node): raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) # 遍历一个表达式的树 class UnaryOperator(Node): def __init__(self, operand): self.operand = operand class BinaryOperator(Node): def __init__(self, left, right): self.left = left self.right = right class Add(BinaryOperator): pass class Sub(BinaryOperator): pass class Mul(BinaryOperator): pass class Div(BinaryOperator): pass class Negate(UnaryOperator): pass class Number(Node): def __init__(self, value): self.value = value # A sample visitor class that evaluates expressions class Evaluator(NodeVisitor): def visit_Number(self, node): return node.value def visit_Add(self, node): return self.visit(node.left) + self.visit(node.right) def visit_Sub(self, node): return self.visit(node.left) - self.visit(node.right) def visit_Mul(self, node): return self.visit(node.left) * self.visit(node.right) def visit_Div(self, node): return self.visit(node.left) / self.visit(node.right) def visit_Negate(self, node): return -self.visit(node.operand) if __name__ == '__main__': # 1 + 2*(3-4) / 5 t1 = Sub(Number(3), Number(4)) t2 = Mul(Number(2), t1) t3 = Div(t2, Number(5)) t4 = Add(Number(1), t3) # Evaluate it e = Evaluator() print(e.visit(t4)) # Outputs 0.6
# visit(t4)——>visit(Number(1))+visit(t3)——>1+visit(t3)
# visit(Number(1))return返回值为1
# visit(t4)的时候生成一个栈stack,一开始存储t4,然后存储[1+visit(t3)],然后停在这里等visit(t3)运行完有返回值,返回值为一个数字
# 所以stack栈最终存储的是一个数字[number],然后while stack:走else:里面语句把栈stack的元素返回出来赋值给last_result来返回
# visit(t4)和visit(t3)和visit(Number(1))等各种visit都是调用visit函数,他们都有自己的栈stack和每个函数调用的时候都有自己的内存空间
# 往里深入:visit(Number(t3))——visit(Div(t2, Number(5)))——>visit(t2)/5
# 往里深入:visit(t2)——>visit(Mul(Number(2), t1))——> visit(Number(2))*visit(t1)——>2*visit(t1)
# 往里深入:visit(t1)——visit(Sub(Number(3), Number(4)))——>visit(Number(3)) - visit(Number(4))返回-1
# 本质上运行逻辑如上:
# 运行visit(t4)的时候要等visit(t3)的返回值,
# 运行visit(t3)的时候要等visit(t2)的返回值,
# 运行visit(t2)的时候要等visit(t1)的返回值,
# visit(t1)返回-1,然后往上走求visit(t2),得到visit(t2)王杭走得到visit(t3),得到visit(t3)往上走得到visit(t4)
# 最终求出visit(t4)
a = Number(0)
for n in range(1, 100000):
a = Add(a, Number(n))
e = Evaluator()
e.visit(a)
# 运行报错:RecursionError: maximum recursion depth exceeded,
上面是使用递归的方法,层级太深会报错,py最多允许1000层递归
如果嵌套层次太深那么上述的Evaluator就会失效:
class Node: pass class NodeVisitor: def visit(self, node): last_result = None while last_result is None: last = node if isinstance(last, Node): node = self._visit(node) else: last_result = node return last_result def _visit(self, node): methname = 'visit_' + type(node).__name__ meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node) def generic_visit(self, node): raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) class UnaryOperator(Node): def __init__(self, operand): self.operand = operand class BinaryOperator(Node): def __init__(self, left, right): self.left = left self.right = right class Add(BinaryOperator): pass class Sub(BinaryOperator): pass class Mul(BinaryOperator): pass class Div(BinaryOperator): pass class Negate(UnaryOperator): pass class Number(Node): def __init__(self, value): self.value = value # 计算表达式的示例访问者类 class Evaluator(NodeVisitor): def visit_Number(self, node): return node.value def visit_Add(self, node): return self.visit(node.left) + self.visit(node.right) def visit_Sub(self, node): return self.visit(node.left) - self.visit(node.right) def visit_Mul(self, node): return self.visit(node.left) * self.visit(node.right) def visit_Div(self, node): return self.visit(node.left) / self.visit(node.right) def visit_Negate(self, node): return -self.visit(node.operand) # 1 + 2*(3-4) / 5 t1 = Sub(Number(3), Number(4)) t2 = Mul(Number(2), t1) t3 = Div(t2, Number(5)) t4 = Add(Number(1), t3) e = Evaluator() print(e.visit(t4)) # 这样递归更简单,类似这种树状结构的解析使用递归 # visit(t4)——visit(Add(Number(1), t3))——>_visit(Add(Number(1), t3))——>visit_Add(Add(Number(1), t3)) # node = visit(Number(1)) + visit(t3) # 继续往里深入运行: # visit(Number(1))——>_visit(Number(1))——>visit_Number(Number(1)) 返回1——>node=1 # 此时last_result还是None,node=1走else,last_result=node=1然后把1return出去,visit(Number(1))=1 # node= 1+ visit(t3) 暂停等t3运行完 # 继续往里深入运行 # visit(t3)——>visit(Div(t2, Number(5)))——>_visit(Div(t2, Number(5)))——>visit_Add(Div(t2, Number(5))) # node = visit(t2)/visit(Number(5)) # node = visit(t2)/visit(Number(5)) # node = visit(t2)/visit(Number(5)) 暂停等visit(t2)运行完,然后visit(Number(5))=5 # 继续往里深入运行 # visit(t2)如上一层层运行
class Node: passclass NodeVisitor: def visit(self, node): return self._visit(node)
def _visit(self, node): methname = 'visit_' + type(node).__name__ meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node) def generic_visit(self, node): raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) class UnaryOperator(Node): def__init__(self, operand): self.operand = operand class BinaryOperator(Node): def__init__(self, left, right): self.left = left self.right = right class Add(BinaryOperator): pass
class Sub(BinaryOperator): pass
class Mul(BinaryOperator): pass
class Div(BinaryOperator): pass
class Negate(UnaryOperator): pass
class Number(Node): def__init__(self, value): self.value = value # 计算表达式的示例访问者类class Evaluator(NodeVisitor): def visit_Number(self, node): return node.value def visit_Add(self, node): return self.visit(node.left) + self.visit(node.right) def visit_Sub(self, node): return self.visit(node.left) - self.visit(node.right) def visit_Mul(self, node): return self.visit(node.left) * self.visit(node.right) def visit_Div(self, node): return self.visit(node.left) / self.visit(node.right) def visit_Negate(self, node): return -self.visit(node.operand) # 1 + 2*(3-4) / 5 t1 = Sub(Number(3), Number(4)) t2 = Mul(Number(2), t1) t3 = Div(t2, Number(5)) t4 = Add(Number(1), t3) e = Evaluator() print(e.visit(t4))
# 最简单的写法,层层返回调用,
# visit(t4)——返回:visit(Number(1)) + visit(t3) = 1+ -0.4 = 0.6 # visit(t3)返回visit(t2)/visit(Number(5)) = -2/5=-0.4 # visit(t2)调用返回visit(Number(2)*visit(t1) = 2*-1=-2 # visit(t1)调用返回visit(Number(3))-visit(Number(4)) = -1 # 后面的visit(t1)调用返回visit(Number(3))-visit(Number(4)) = -1先运行,然后层层往上运行
# yiled迭代器版本来遍历解析表达式 import types class Node: pass class NodeVisitor: def visit(self, node): stack = [node] last_result = None while stack: try: last = stack[-1] # GeneratorType:判断last对象是不是generator 迭代器对象的类型,由生成器函数创建 if isinstance(last, types.GeneratorType): print('if') stack.append(last.send(last_result)) last_result = None # 判断last对象是不是Node类得对象 elif isinstance(last, Node): print('elif') stack.append(self._visit(stack.pop())) # 弹出stack对象的最后一个 else: print('else') last_result = stack.pop() except StopIteration: print('StopIteration') stack.pop() print(stack) return last_result def _visit(self, node): methname = 'visit_' + type(node).__name__ meth = getattr(self, methname, None) if meth is None: meth = self.generic_visit return meth(node) def generic_visit(self, node): raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) class UnaryOperator(Node): def __init__(self, operand): self.operand = operand class BinaryOperator(Node): def __init__(self, left, right): self.left = left self.right = right class Add(BinaryOperator): pass class Sub(BinaryOperator): pass class Mul(BinaryOperator): pass class Div(BinaryOperator): pass class Negate(UnaryOperator): pass class Number(Node): def __init__(self, value): self.value = value # 计算表达式的示例访问者类 class Evaluator(NodeVisitor): def visit_Number(self, node): return node.value def visit_Add(self, node): yield (yield node.left) + (yield node.right) def visit_Sub(self, node): yield (yield node.left) - (yield node.right) def visit_Mul(self, node): yield (yield node.left) * (yield node.right) def visit_Div(self, node): yield (yield node.left) / (yield node.right) def visit_Negate(self, node): yield - (yield node.operand) t1 = Sub(Number(3), Number(4)) # g4 t2 = Mul(Number(2), t1) # g3 t3 = Div(t2, Number(5)) # g2 t4 = Add(Number(1), t3) # g e = Evaluator() print(e.visit(t4)) # print(e.visit(t4)) # visit(t4)——>visit(Add(Number(1), t3)) # 走elif,stack里存放一个生成器stack=[g] # 走if里面的语句, 生成器g send进去None,stack=[g, Number(1)] # 走elif stack=[g, 1] # 走else last_result = stack.pop(),last_result=1, stack=[g] # 走if ,生成器g send进去1,stack=[g, t3],t3=Div(t2, Number(5)),last_result = None # 走elif,又放了一个生成器g2,stack=[g, g2] # 走if,生成器g2 send进去None,stack=[g, g2, t2] # 走elif,stack=[g, g2, g3] # 走if,生成器g3 send进去None, stack=[g, g2, g3, Number(2)] # 走elif, stack=[g, g2, g3, 2], # 走else,last_result = stack.pop(),last_result=2, stack=[g, g2, g3] # 走if, 生成器g3 send进去2, stack=[g, g2, g3, t1] # 走elif,又生成一个生成器g4放到stack,stack=[g, g2, g3, g4] # 走if,生成器g4 send进去None, stack=[g, g2, g3, g4, Number(3)] # 走elif,stack=[g, g2, g3, g4, 3)] # 走else,last_result = stack.pop(),last_result=3, stack=[g, g2, g3, g4] # 走if,生成器g4 send进去3,stack=[g, g2, g3, g4, Number(4)] # 走elif,stack=[g, g2, g3, g4, 4] # 走else,last_result = stack.pop(),last_result=4, stack=[g, g2, g3, g4] # 走if,生成器g4 send进去4,stack=[g, g2, g3, g4, -1] # 走else,last_result = stack.pop(),last_result=-1, stack=[g, g2, g3, g4] # 走if,报错:StopIteration,stack.pop(),把最后一个生成器g4丢了,stack=[g, g2, g3] # 走if,生成器g3 send进去-1,stack=[g, g2, g3, -2] # 走else,last_result = stack.pop(),last_result=-2, stack=[g, g2, g3] # 走if,报错:StopIteration,stack.pop() last_result=-2, stack=[g, g2] # 走if,生成器g2 send进去-2,stack=[g, g2, Number(5)] # 走elif,stack=[g, g2, 5] # 走else,last_result = stack.pop(),last_result=5, stack=[g, g2] # 走if,生成器g2 send进去5,stack=[g, g2, -2/5=-0.4] # 走else,last_result = stack.pop(),last_result=-0.4, stack=[g, g2] # 走if,报错:StopIteration,stack.pop() last_result=-0.4,stack=[g] # 走if,生成器g send进去-0.4,stack=[g,0.6] # 走else,last_result = stack.pop(),last_result=0.6, stack=[g] # 走if,报错,StopIteration,stack.pop() last_result=0.6,stack=[] # 退出 while stack:循环返回last_result=0.6 print(e.visit(t1)) # e.visit(Sub(Number(3), Number(4)))——> # 走elif,stack里存放一个生成器stack=[g] # 走if里面的语句,stack里面继续放一个Number(3),stack=[g, Number(3)], # 走elif stack=[g, 3] # 走else last_result = stack.pop(),last_result=3, stack=[g] # 走if, send进去3,stack=[g, Number(4)] # 走elif,stack=[g, 4] # 走else last_result = stack.pop(),last_result=4, stack=[g] # 走if,send进去4,stack=[g, -1] # 走else last_result = stack.pop(),last_result=-1, stack=[g] # 走if, send进去-1,报错,此时last_result=-1直接return last_result返回-1
# yield迭代器版本运行不会再报错了, a = Number(0) for i in range(1, 100000): a = Add(a, Number(i)) e = Evaluator() print(e.visit(a)) # 输出:4999950000 # 添加其他自定义逻辑也没问题 class Evaluator(NodeVisitor): ... def visit_Add(self, node): print('Add:', node) lhs = yield node.left print('left=', lhs) rhs = yield node.right print('right=', rhs) yield lhs + rhs ... e = Evaluator() e.visit(t4)
这个visit_Add的写法和yield (yield node.left) + (yield node.right)效果类似
# yield解析 def gen(): yield (yield 1) + (yield 2) g = gen() a = g.send(None) print(a) # 1 b = g.send(a) print(b) # 2 print(g.send(b)) # 3 print(g.__next__()) # 报错:StopIteration
# 避免递归的一个通常方法是使用一个栈或队列的数据结构。 # 深度优先的遍历算法,第一次碰到一个节点时将其压入栈中,处理完后弹出栈。visit() 方法的核心思路就是这样 # 当碰到yield语句时,生成器会返回一个数据并暂时挂起。 上面的例子使用这个技术来代替了递归 value = self.visit(node.left) # 换成yield语句: value = yield node.left # 它会将 node.left 返回给 visit() 方法,然后 visit() 方法调用那个节点相应的 visit_Name() 方法。
yield暂时将程序控制器让出给调用者,当执行完后,结果会赋值给value
# 递归是层层返回的思想
# yield栈和队列更复杂
t1 = Sub(Number(3), Number(4)) t2 = Mul(Number(2), t1) t3 = Div(t2, Number(5)) t4 = Add(Number(1), t3)
这里一个stack是栈队列,碰到t4这个节点,在stack栈里存储一个t4,解析t4这个节点,碰到值存储到t4
再碰到节点t3,往栈里加t3,解析t3,碰到值存储到t3,再碰到节点t2栈里存储t2,同理栈里存储t1,t1下都是值
就能计算节点t1的值,得到节点t1的值后丢到t2里,同理操作
35:循环引用数据结构的内存管理 创建了很多循环引用数据结构(比如树、图、观察者模式等),碰到了内存管理难题。
python weakref的用法(详细介绍弱引用和weakref):https://blog.csdn.net/NeverLate_gogogo/article/details/107021695
# 一个简单的循环引用数据结构例子就是一个树形结构,双亲节点有指针指向孩子节点,
孩子节点又返回来指向双亲节点。 可以使用 weakref 库中的弱引用 import weakref class Node: def __init__(self, value): self.value = value self._parent = None self.children = [] def __repr__(self): return 'Node({!r:})'.format(self.value) # 属性,该属性将父级管理为弱引用 @property def parent(self): return None if self._parent is None else self._parent() @parent.setter def parent(self, node): self._parent = weakref.ref(node) # self._parent = node def add_child(self, child): self.children.append(child) child.parent = self # 这种是想方式允许parent静默终止 root = Node('parent') c1 = Node('child') root.add_child(c1) print(c1.parent) # Node('parent') del root print(c1.parent) # None
# 循环引用的数据结构在Python中是一个很棘手的问题, # 因为正常的垃圾回收机制不能适用于这种情形 # 类仅用于说明何时发生删除 class Data: def __del__(self): print('Data.__del__') # 涉及循环的节点类 class Node: def __init__(self): self.data = Data() self.parent = None self.children = [] def add_child(self, child): self.children.append(child) child.parent = self # 垃圾回收试验 a = Data() del a # Data.__del__ a = Node() del a # Data.__del__ a = Node() a.add_child(Node()) del a # Not deleted (no message) # 最后一个的删除时打印语句没有出现。原因是Python的垃圾回收机制是基于简单的引用计数。 # 当一个对象的引用数变成0的时候才会立即删除掉。而对于循环引用引用数变成0这个条件永远不会成立。所以永远不删除 # 因此,在上面例子中最后部分,父节点和孩子节点互相拥有对方的引用,导致每个对象的引用计数都不可能变成0
# Python有另外的垃圾回收器来专门针对循环引用的,但是不知道它什么时候会触发。 # 可以手动的触发它,但是代码看上去垃圾 import gc class Data: def __del__(self): print('Data.__del__') class Node: def __init__(self): self.data = Data() self.parent = None self.children = [] def add_child(self, child): self.children.append(child) child.parent = self a = Node() a.add_child(Node()) gc.collect() # 暴力回收
# 如果循环引用的对象自己还定义了自己的 __del__() 方法,那么会让情况变得更糟糕。 # 像下面这样给Node定义自己的 __del__() 方法 class Data: def __del__(self): print('Data.__del__') # 涉及循环的节点类 class Node: def __init__(self): self.data = Data() self.parent = None self.children = [] def add_child(self, child): self.children.append(child) child.parent = self # 永远不要这样定义。 # 这里只是为了说明病理行为 def __del__(self): del self.data del self.parent del self.children # 这种情况下,垃圾回收永远都不会去回收这个对象的,还会导致内存泄露。 # 如果你试着去运行它会发现,Data.__del__ 消息永远不会出现了,甚至在你强制内存回收时 # a = Node() # a.add_child(Node()) # del a # No message (not collected) # import gc # gc.collect() # No message (not collected) # 弱引用消除了引用循环的这个问题,本质来讲,弱引用就是一个对象指针,它不会增加它的引用计数。 你可以通过 weakref 来创建弱引用 import weakref a = Node() a_ref = weakref.ref(a) print(a_ref) # <weakref at 0x0000026A83D7E6D0; to 'Node' at 0x0000026A83FFEFD0> # 为了访问弱引用所引用的对象,你可以像函数一样去调用它即可。 # 如果那个对象还存在就会返回它,否则就返回一个None。 由于原始对象的引用计数没有增加,那么就可以去删除它了 del a print(a_ref()) # None # 弱引用技术,你会发现不再有循环引用问题了,一旦某个节点不被使用了,垃圾回收器立即回收它
36:让类支持比较操作
# Python类对每个比较操作都需要实现一个特殊方法来支持。 # 为了支持>=操作符,你需要定义一个 __ge__() 方法。 # 定义一个方法没什么问题,但如果要你实现所有可能的比较方法那就有点烦人了 # 装饰器 functools.total_ordering 就是用来简化这个处理的 # 使用functools.total_ordering装饰一个类,定义一个 __eq__() 方法, 外加其他方法(__lt__, __le__, __gt__, or __ge__)中的一个即可。 然后装饰器会自动为你填充其它比较方法 # 构建一些房子,然后给它们增加一些房间,最后通过房子大小来比较它们 from functools import total_ordering class Room: def __init__(self, name, length, width): self.name = name self.length = length self.width = width self.square_feet = self.length * self.width @total_ordering class House: def __init__(self, name, style): self.name = name self.style = style self.rooms = list() @property def living_space_footage(self): return sum(r.square_feet for r in self.rooms) # 面积 def add_room(self, room): self.rooms.append(room) def __str__(self): return '{}: {} square foot {}'.format(self.name, self.living_space_footage, self.style) def __eq__(self, other): return self.living_space_footage == other.living_space_footage def __lt__(self, other): return self.living_space_footage < other.living_space_footage # 给House类定义了两个方法:__eq__() 和 __lt__() ,它就能支持所有的比较操作 h1 = House('h1', 'Cape') h1.add_room(Room('Master Bedroom', 14, 21)) h1.add_room(Room('Living Room', 18, 20)) h1.add_room(Room('Kitchen', 12, 16)) h1.add_room(Room('Office', 12, 12)) print(h1.rooms) h2 = House('h2', 'Ranch') h2.add_room(Room('Master Bedroom', 14, 21)) h2.add_room(Room('Living Room', 18, 20)) h2.add_room(Room('Kitchen', 12, 16)) h3 = House('h3', 'Split') h3.add_room(Room('Master Bedroom', 14, 21)) h3.add_room(Room('Living Room', 18, 20)) h3.add_room(Room('Office', 12, 16)) h3.add_room(Room('Kitchen', 15, 17)) houses = [h1, h2, h3] print('Is h1 bigger than h2?', h1 > h2) # prints True print('Is h2 smaller than h3?', h2 < h3) # prints True print('Is h2 greater than or equal to h1?', h2 >= h1) # Prints False print('Which one is biggest?', max(houses)) # Prints 'h3: 1101-square-foot Split' print('Which is smallest?', min(houses)) # Prints 'h2: 846-square-foot Ranch'
# total_ordering 装饰器,它就是定义了一个从每个比较支持方法到所有需要定义的其他方法的一个映射而已。 # 比如你定义了 __le__() 方法,那么它就被用来构建所有其他的需要定义的那些特殊方法。
实际上就是在类里面像下面这样定义了一些特殊方法 class House: def __eq__(self, other): pass def __lt__(self, other): pass # total_ordering创建的方法如下 __le__ = lambda self, other: self < other or self == other __gt__ = lambda self, other: not (self < other or self == other) __ge__ = lambda self, other: not (self < other) __ne__ = lambda self, other: not self == other 自己实现每个魔术放封也可以,使用 @total_ordering 可以简化代码
37:创建缓存实例 创建一个类的对象时,如果之前使用同样参数创建过这个对象, 返回它的缓存引用
# 希望相同参数创建的对象是单例的 # 比如 logging 模块,使用相同的名称创建的 logger 实例永远只有一个 import logging a = logging.getLogger('foo') b = logging.getLogger('bar') print(a is b) # False c = logging.getLogger('foo') print(a is c) # True
# 为了实现类似这种单例模式,需要使用一个和类本身分开的工厂函数 import weakref # 有问题的班级 class Spam: def __init__(self, name): self.name = name # 缓存支持 _spam_cache = weakref.WeakValueDictionary()
# 这里使用一个普通的字典也是ok的,但是普通字典是强引用,WeakValueDictionary()是弱引用
def get_spam(name): if name not in _spam_cache: s = Spam(name) _spam_cache[name] = s else: s = _spam_cache[name] return s a = get_spam('foo') b = get_spam('bar') print(a is b) # False c = get_spam('foo') print(a is c) # True
# 跟之前那个日志对象的创建行为是一致的
# 编写一个工厂函数来修改普通的实例创建行为通常是一个比较简单的方法 # 但是重构__new__() 才是最完美的 # 注意:此代码不太有效 import weakref class Spam: _spam_cache = weakref.WeakValueDictionary() def __new__(cls, name): if name in cls._spam_cache: return cls._spam_cache[name] else: self = super().__new__(cls) cls._spam_cache[name] = self # cls.inner(self, name) return self def __init__(self, name): print('Initializing Spam') self.name = name # def inner(self, name): # print('Initializing Spam') # self.name = name # 看起来好像可以达到预期效果,s 和 t返回的是同一个类 # 但是问题是 __init__() 每次都会被调用,不管这个实例是否被缓存了 # 这种方法并不可取,因为两次调用了__init__虽然返回的是同一个实例 s = Spam('Dave') t = Spam('Dave') print(s is t) # True
如果想不调用两次__init__那么就不构造__init__方法,构造个inner方法来实例化属性,在__new__方法中调用
这样使用类属性_spam_cache = weakref.WeakValueDictionary(),使用.WeakValueDictionary()表示弱引用
如果使用字典{}的话那么就算外面s和t删除,但是字典里面还是强引用了Spam('Dave')这个对象,有个强引用指向了Spam('Dave'),
Spam('Dave')这个对象不会删除
但是使用weakref.WeakValueDictionary()表示这个字典弱引用Spam('Dave')这个对象,代码里把s=Spam('Dave')和t = Spam('Dave')
这两个强指向del删除后Spam('Dave')这个对象会自动在内存里清除
del s删除的不是对象,而是删除s变量指向Spam('Dave')对象这个强引用,强引用计数-1,强引用计数=0的时候对象Spam('Dave')删除
# 上面(weakref.WeakValueDictionary())我们使用到了弱引用计数,对于垃圾回收来讲是很有帮助的, # 当我们保持实例缓存时,你可能只想在程序中使用到它们时才保存。 # 一个WeakValueDictionary 实例只会保存那些在其它地方还在被使用的实例 # 否则的话,只要实例不再被使用了,它就从字典中被移除了 class Spam: def __init__(self, name): self.name = name import weakref _spam_cache = weakref.WeakValueDictionary() def get_spam(name): if name not in _spam_cache: s = Spam(name) _spam_cache[name] = s else: s = _spam_cache[name] return s a = get_spam('foo') b = get_spam('bar') c = get_spam('foo') print(list(_spam_cache)) # ['foo', 'bar'] # print(dict(_spam_cache)) # {'foo': <__main__.Spam object at 0x0000013DF5E6EF70>, # # 'bar': <__main__.Spam object at 0x0000013DF5E6EF10>} del a del c print(list(_spam_cache)) # ['bar'] del b print(list(_spam_cache)) # []
# 更高级实现 # 使用到了一个全局变量,并且工厂函数跟类放在一块。我们可以通过将缓存代码放到一个单独的缓存管理器中import weakref class CachedSpamManager: def__init__(self): self._cache = weakref.WeakValueDictionary() def get_spam(self, name): if name notin self._cache: s = Spam(name) self._cache[name] = s else: s = self._cache[name] return s def clear(self): self._cache.clear() class Spam: manager = CachedSpamManager() def__init__(self, name): self.name = name def get_spam(name): return Spam.manager.get_spam(name) # 这样的话代码更清晰,并且也更灵活,我们可以增加更多的缓存管理机制,只需要替代manager即可 # 暴露了类的实例化给用户,用户很容易去直接实例化这个类,而不是使用工厂方法 a = Spam('foo') b = Spam('foo') # print(a is b) # False # 直接实例化Spam传参相同返回的还是两个不同的对象 # 使用工厂函数CachedSpamManager()来创建spam对象 # 这样传参相同的话返回的是同一个对象 c = CachedSpamManager() # 工厂实例化,工厂用来制造spam类的 a = c.get_spam('liergou') b = c.get_spam('liergou') f = c.get_spam('erbizi') print(a is b) # Trueprint(a is c)
# 上面的例子还是暴露了Spam类给用户,用户很容易去直接实例化这个类,而不是使用工厂方法 a = Spam('foo') b = Spam('foo') a is b # 返回false
# 代码的持续优化, # 第一个是将类的名字修改为以下划线(_)开头,提示用户别直接调用它 # 第二种就是让这个类的 __init__() 方法抛出一个异常,让它不能被初始化: class Spam: def __init__(self, *args, **kwargs): raise RuntimeError("Can't instantiate directly") #_new替代构造函数,因为__init__调用报错 @classmethod def _new(cls, name): self = cls.__new__(cls) self.name = name
# 最终代码 # 修改缓存管理器代码,使用 Spam._new() 来创建实例,
而不是直接调用 Spam() 构造函数: class CachedSpamManager2: def __init__(self): self._cache = weakref.WeakValueDictionary() def get_spam(self, name): if name not in self._cache: temp = Spam3._new(name) # 修改的创作 self._cache[name] = temp else: temp = self._cache[name] return temp def clear(self): self._cache.clear() class Spam3: def __init__(self, *args, **kwargs): raise RuntimeError("Can't instantiate directly") # 替代构造函数 @classmethod def _new(cls, name): self = cls.__new__(cls) self.name = name return self
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!