流畅的Python笔记(上)

Python相关知识点准备

一、Python数据类型

  1. Python 数据模型其实就是对Python框架的综述,它规范了这门语言的自身构建模块,包括序列、迭代器、函数、类和上下文管理器。
import collections 
Card = collections.namedtuple('Card',['rank','suit'])
class FrenchDeck:
    ranks = [str(n) for n in range(2,11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()
        
    def __init__(self):
        self._cards = [Card(rank,suit) for suit in self.suits for rank in self.ranks ]
        
    def __len__(self):
        return len(self._cards)
        
    def __getitem__(self,position):
        return self._cards[position]
  1. obj[key] <=> obj.__getitem__(key) 实际上背后实现的原理就是这样的(当碰到特殊的句法的时候Python回使用特殊的方法去激活这些操作)

  2. 这些方法能让语言支持和实现以下的一些特点:(迭代、集合类、属性访问、运算符重载、函数和方法的调用、对象创建和销毁、字符串的形式化和格式化、管理上下文with)

  3. 在以上的程序中,实现了__getitem__的方法,然后就把这个[]索引操作给设定下来了,因为self._cards是列表,所以列表支持索引、支持反向索引、还支持迭代 如果没有实现这个方法就会报错:‘FrenchDeck’ object does not support indexing

  4. 迭代通常是隐式的,我们并没有实现__contains__这个方法,那么in操作符会按顺序做一次迭代搜索,于是in运算符就可以使用for .. in ..就是迭代

  5. 再次声明一点、特殊方法是被python解释器去调用的,我们本身不需要list.__len__() ,可以直接使用len(对象),如果是内置数据类型的调用的话,直接就读取PyVarObject的obj_size 属性,读取一个值比调用方法更快。

  6. 特殊方法的调用很多都是隐式的,比如for i in x:实际上背后调用的是iter(x),我们调用特殊方法的频率要远低于我们去实现它的次数。除了__init__,是为了再子类中调用超类的构造方法。

1.1 有哪些地方隐式调用了特殊方法?

  • for x in items 隐式调用了__contains__,__iter__.if not while not a 调用了__bool__如果没有实现就用__len__. 许多方法都对应了其隐式的方法。with .. open ..上下文管理器调用了__enter__ __exit__。s = Solution(x,y) 就是调用了初始化方法 而且还调用了__new__方法创建一个实例对象。

1.2 __repr__和__str___有什么关系?

  • 他们都是表示一个对象的方式,在输出到控制台或者其他设备时,调用,如果没有实现__str__就会调用__repr__方法,返回的是对象的hashcode 也就是地址。

1.3 obj[key] <=> obj.getitem(key) 索引访问的原理是什么?

  • 把这个[]索引操作给设定下来了,因为self._cards是列表,所以列表支持索引、支持反向索引、还支持迭代 如果没有实现这个方法就会报错。__setitem__赋值的时候就会这样。

1.4 .操作点操作的访问过程是什么?

  • 访问属性、访问方法、访问实例属性、访问类属性、访问数据描述符(覆盖)、访问非数据描述符、就是动态属性的一个访问过程
from math import hypot

class Vector:
    
    def __init__(self,x=0,y=0):
        self.x = x
        self.y = y
    def __repr__(self):
        return 'Vector(%r,%r)'%(self.x,self.y)
    
    def __abs__(self):
        return hypot(self.x,self.y)
    
    def __bool__(self):
        return bool(abs(self))
    
    def __add__(self,other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x,y)  #不改变对象本身 只是单纯返回值
    
    def __mul__(self,scalar):
        return Vector(self.x*scalar,self.y*scalar) #乘法交换律被忽略了,在
  1. Python的一个内置函数__repr__ 是能把一个对象利用字符串的形式表达出来,当我们不设定是默认返回对象类别及地址

  2. __repr____str__的区别在于后者只有在print()到终端的时候或者str()函数调用的时候才会被调用,__repr__是最后保障,因为当没有实现__str__时,系统会调用__repr__ https://stackoverflow.com/questions/1436703/difference-between-str-and-repr-in-python 回答得很精彩

  3. bool类型,python中任何要判断的地方如if while not and or 都要触及__bool__这个方法,如果对象不存在这个方法,python回去触发obj.__len__()这一个方法。如果为空则为空

  4. 一个有83个特殊方法,其中有47个用于算术符、位运算、比较操作。

通过实现特殊方法,自定义的数据结构类型可以表现得跟python内置的数据类型一样,从而让我们写出更具有Python风格的代码。
1、数据模型、对象模型
2、元对象所指的是那些对建构语言本身很重要的对象,以此为前提,协议可以看做接口。也就是说,元对象协议是对象模型的同义词,他们的意思都是构建语言的核心API。

1.4 特殊方法一览:(字符串的、数值的、集合的、迭代、属性管理的、实例创建与销毁的、属性描述符)



二、序列构成数组

  1. Python现在的风格:序列的泛型操作、内置的元组和映射类型、用缩进来架构的源码、无需声明变量的强类型。深入理解Python中的不同序列类型,不但能让我们避免重复造轮子、他们的API定义还能帮助我们定义自己的API ,设计得跟原生或者兼容未来可能会出现的类型。

2.1 Python中的序列分为可变序列和不可变序列,可以简单说一下嘛?

  • 不可变:元组、基本数字、str、bytes
  • 可变:list、bytearay、memoryview等,都实现了三个基本的协议就是__iter__ 、__len__ 、__contains__支持不同的操作符,其继承逻辑如下:
  • UML图显示了Python中序列的划分,可以分为可变序列不可变序列。大概共同的方法如下:

2.2 python2和python3有设么区别吗?

  1. 字符串编码上 在Python2中,有两种字符串类型:str类型和Unicode类型。str就是难以懂的字节数据,unicode就是可以解码用utf-8解码的编码才能正常显示中文、u'一般前面加个u'
    python3中默认把字节数据编码成strutf8的形式可以支持中文。
  2. 在python2中的列表推导式会发生内存泄露、python3修复了这个问题。

2.3 python中类似列表推导式的还有那些?通常用来做什么?

  1. 列表推导式[i for i in range(5)] [[i for i in range(4)] for j in range(5)] [i for i in range(5) if i%2==1]可以取代map\filter等操作。
  2. 字典推导式:{a:b for a in x for b in c} {a:b for a,b in q}自动解包
  3. 生成器表达式:(a for a in c) 迭代器就可以利用生成器懒加载的特性来提高效率。
  4. 列表、元组、字典推导 来生成列表、元组、字典的方式更加易读高效率、但是当代码超过3行的时候就要考虑用for循环来写程序了。(在Python2.7中列表推导没有自己的局部变量域,会使上一个同名变量发生改变,出现变量泄露的情况,在Python3之后就不会有了。)
  5. 列表推导和map、filter 效率比较https://github.com/fluentpython/example-code/blob/master/02-array-seq/listcomp_speed.py
#列表推导能做 filter 和map函数做的事,而且不用借助lambda
symbols = "$#%^$@*&"
beyond_asic = [ord(s) for s in symbols if ord(s) > 127]
beyond_asic1 = list(filter(lambda c :c > 127,map(ord,symbols)))


card = ['A','K','Q']
suit = ['diamond','club','hearts','spades']
result = [(k,v) for k in card for v in suit]#笛卡尔积的感觉

num = {(a,b) for a in range(10) for b in range(5)} #生成器表达式 列表推导换成这样

2.4 生成器迭代器和列表推导式的区别以及表达方式

  1. 生成器表达式在初始化序列的时候,比列表推导更加好,因为前者节省了内存,而不是先把一切数据排列好再放入构造器中去完成。只需要把方括号变成圆括号就好了。生成器表达式遵循迭代器协议可以逐个产出元素。

2.5 python中的拆包和打包有了解吗?zip(*iterator)

  • 拆包其实就是从元组里面逐个获取元素,zip/pack 就是打包成元组。https://www.python.org/dev/peps/pep-3132/ 任何可迭代对象都可以被拆包,但是唯一一个要求就是目标变量的个数要跟元素个数一致,或者用*来表示忽略个数。还可以用星号*来把可迭代对象拆开作为函数的参数。用*来处理剩下的元素,几种精力在我们想要的元素上,**是字典的不定长参数的表示。
metro_areas = [('Tokyo','JP',36.933,(35.689722,139.691667)),  # ➊
    ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
    ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
    ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
    ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}'
for name, cc, pop, (latitude, longitude) in metro_areas:  # ➋ #解包
    if longitude <= 0:  # ➌
        print(fmt.format(name, latitude, longitude))

2.6 python中collections中的数据结构有哪些?

  • namedtuple 有点像是一个类赋予动态名字属性和相应值的感觉
    元组已经设计好了,但作为记录的话我们还需要为其命名,namedtuple的出现帮助我们解决了这个问题。collections.namedtuple是一个工厂函数.有最基本的属性(_asdict\_make()\_fields)
  • OrderedDict保持插入顺序k的字典。
  • defaultdict 可以支持默认数据结构list int等操作。
  • dequeue 线程安全的双端队列
  • PriorityQueue 线程安全的优先级队列
  • Counter 可以用于单词计数的dict
  • UserDict*UserString*\等专门用于重写原数据类型的操作。
  • 虽然列表是一个很好模拟队列和栈的选择,但是在移动元素的时候会很耗时,所以collections.deques(双向队列)是一个很好的选择。除了deque 意外 其他的Queue\LIFOQUEUE\PriorityQueue同步的队列类、multiprocessing 有自己的队列类,是设计给进程间通信使用的\ asynico 封装了以上的类 但是为异步编程服务。 heapq没有队列类但提供了heapqpush heapqpop当做优先队列来使用。
from collections import namedtuple
City = namedtuple('City','name country population position')
tokoyo = City('Tokyo','Japan','2000,00',(32.56,42.005))
tokoyo._asdict()

2.7 python中的切片实质是什么?slice是什么?

切片,s[a?️c] 其中a是其实 b是终止 c是步长 当我们操作lis[a?️c]的时候,会创建一个切片对象slice(a?️c) 然后传入 lis.getitem(slice(start,stop,step)) 的操作,我们还可以为切片加上名字。如下:

invoice = """
... 0.....6................................40........52...55........
... 1909  Pimoroni PiBrella                    $17.50    3    $52.50
... 1489  6mm Tactile Switch x20                $4.95    2     $9.90
... 1510  Panavise Jr. - PV-201                $28.00    1    $28.00
... 1601  PiTFT Mini Kit 320x240               $34.95    1    $34.95
... """
SKU = slice(0, 6)
DESCRIPTION = slice(6, 40)  #切片命名
lines = invoice.split('\n')
for line in lines:
    print(line[DESCRIPTION])

2.8 如果我想创建多维列表并初始化有什么坑?浅复制和深复制的区别?

  1. 建立有列表组成的列表**[[]3]是简单的对象的引用,(外面的列表指向同一个引用)指向同一个地址,不可变对象的拼接就是建立一个新的对象。(内存地址还是同一个,要不同就要用[[for_ in range(5)] for _ in range(5)])生成。也就是浅复制和深复制的区别*,元组中内存地址是一样的,改一个就改了全部了。虽然元组是不可变的、但是元组中的元素时可以变的。

2.9 序列的自增原地操作是本质是什么?

  1. 序列的增量赋值,+= 是取决于特殊的方法 __iadd__(self)如何这个没有被实现的话,那么就会调用**add方法,如果调用iadd的话,那么a就会改动,相当于a.extends(b)如果没有调用的话a就不会被改动,没有新对象生成。对不可变对象进行拼接操作效率会变低,因为每次都会有一个新的对象产生销毁str除外**。

  2. 排序 list.sort() 返回的是None 在Python中如果某个操作是就地操作,不会返回新对象的时候,就会返回None 来提醒你
    str里面的所有方法就都有返回新对象,可以将其串联起来。 相反sorted会返回一个新对象。

  3. 对Python可视化原理进行分析

  4. bisect 模块的使用 以及fmt可视化漂亮输出。Python 二分查找与 bisect 模块

总结:
1.列表很少放入不同类型的对象,而元组则相反,经常放入同类型的元素,列入列表里面存入都会呱呱叫的动物。
2.list.sort sorted max min key参数很巧妙,就好像java中的compartor一样,自定义比较方法,一般配合lambda表达式使用。

2.10 元组和列表的区别

元组和列表的区别就是可变和不可变具体底层魔术方法的实现差异如下:



元组可以索引,但是不可以改变。





创建双精度array数组

2.11 数组和列表的区别






2.12 deque 双向队列的高效

2.13 queue 模块实现了多个线程安全的数据结构

三、字典和集合

  1. 字典是Python中无处不在的数据结构,即使没有直接用到他,但是在实际代码中底层确实用到了字典,如关键字参数的实现,它跟很多内置函数**builtin**.__dict__有关系

3.1 dict重写hash、eq方法

  1. 集合和字典的实现都非常依赖散列表,所以理解散列表至关重要。什么是可散列的数据类型呢?如果一个对象是可散列的,在他的声明周期中,它的散列值是不会改变的,而且这个对象需要实现**hash()方法。可散列对象还有qe()方法,如果两个可散列对象是相等的话,他们的散列值一定是相同的(如java的hashCodeeuqal方法覆盖的时候要注意反身性**、对称性传递性null)。
  2. 扁平数据类型也就是原子不可变数据类型(数值类型)
  3. 常见的映射方法:dict\defaultdict\OrderedDict 前一个是基本的,后两个是在collections模块里面的变种
  4. setdefault\来处理找不到的键。d[k]=> d.get(k,default)
  5. 在这3个映射类型的方法中,又回顾了一遍Python中定义类型的一些基本的方法**missing,contains,getitem,setitem,iter,delitem,copy…在我们更新一个键值的时候,无论是getitem**,get都显得不自然,效率低下。利用dict.setdefault(key,[]).append()可以高效解决这个问题.
    setdefault(key[, default])
  6. (如果存在就返回其值,不存在就创建新值)利用collections.defaultdict也可以实现这一点。具体的流程是这样的。当我们在defauldict 里面时,访问一个值,__getitem__触发,但是如果是d.get(k)的话就会返回None,但是在defaultdict里面会调用default_factory函数,也就是背后强大的**missing**来进一步处理。
dd = defaultdict(int)
dd.get("a")# 还是None
dd["a"]  #这个时候就会调用missing逻辑 然后返回int 0 [] 空列表
  1. 在dict里面当找不到键的时候,contains方法回去调用missing方法,所以这个方法也是必须要自己实现一次。
#定义一个新的字典,即时在访问数字的情况下把数字变成字符串来访问。
class StrKeyDict0(dict):
    
    def __missing__(self,key):
        if isinstance(key,str):#必须的,因为str部分已经判断过了,如果没有的话就会把一个逻辑弄乱
            raise KeyError(key)
        return self[str(key)]
    
    def get(self,key,default=None):
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self,key):
        return key in self.keys() or str(key) in self.keys
    
        
  1. 字典的变种:Orderdict(添加键的时候回保持顺序。)、ChainMap可以容纳个数不同的映射对象,在查找键的时候这些对象对呗当成一个整体逐个查找。、collections.Counter 这个映射类型会给键准备一个计数器,每次更新键的时候都会增加这个计数器。、Userdict 更是厉害,里面一个data属性是dict的实例,默认的数据存储在里面。
import builtins
from collections import ChainMap
a = ChainMap(locals(),globals(),vars(builtins))

from types import MappingProxyType

d = {'a':[3,5]}
d_unchange = MappingProxyType(d)

d_unchange['a'].append('a')
d[2]='B'
d_unchange['2'] = 'b'#是不可以添加键值的,一旦建立就不可以更改。

  1. 集合有交集并集的散列对象,操作十分快。{}来初始化一个集合.set的一些操作交并合对称

  2. setdict背后:散列表其实是一个稀疏数组,在数据结构教材中,每个键值对占一个表元(散列表中的单元)每个元有2个部分,一个是对键的引用,一个是对值的引用。散列表的作用就是快速存储地址的一个地方,**在字典中流程:首先计算键的散列值-》使用散列值部分数来定位一个表元-》表元为空抛出异常,否则比较键是否相等, 不相等就使用散列值的另一部分定位散列表中的一行。dict的实现及结果就是:1、键必须是可散列的2、字典在内存上耗费巨大3、键查询很快4、键的次序取决于添加次序5、往字典里添加新键可能会改变自己键的顺序。
    **

3.2 dict 实现的方法

dict实现了基本的3个魔术方法以及Mapping的一些专有方法、其中__eq__\__ne__比较重要


3.3 字典推导式





更新值的时候d[“k”]=xx

3.4 setdefault有什么用?defaultdict有一个特殊的工厂方法,可以在__missing__时实现。




3.5 getattr 和__getattribute__的区别


也就是说**getattr是用来抛出异常的最后一步到这里,当找不到属性时,也就是getattribute没有找到相应结果时就会抛出相应的异常,一般就写getattr**来实现动态加载。

3.6 对象描述器描述符(属性方法的查找顺序)


可以使对象表现出数值类似__str__


描述器就是data descriptor 是数据描述器

class Meter(object):
   """
   对于单位"米"的描述器
   """
   def __init__(self, value=0.0):
       self.value = float(value)

   def __get__(self, instance, owner):
       return self.value

   def __set__(self, instance, value):
       self.value = float(value)

class Foot(object):
   """
   对于单位"英尺"的描述器
   """
   def __get__(self, instance, owner):
       return instance.meter * 3.2808
   def __set__(self, instance, value):
       instance.meter = float(value) / 3.2808

class Distance(object):
   """
   用米和英寸来表示两个描述器之间的距离
   """
   meter = Meter(10)
   foot = Foot()
   

3.7 集合推导

3.8 集合操作(交、去重、差、并)



3.9 dict的散列特性扩容原理





四、文本和字节序列

  1. 字符的标识就是码位(10进制的数和在unicode标准中以4-6个十六进制的数表示),字符的具体表述取决于编码。编码就是码位在字节序列之间转换使用的一种算法。




4.1 python中unicode具体是怎么存在?



五、一等函数

5.1 python中一等函数是什么?高阶函数是什么?

  1. 接受函数为参数,或者把函数作为结果返回的的函数是最高阶函数。map就是一个实例,和sorted也是,key参数可以选用一个函数作为参数。map\filter 的功能随着生成器表达式和列表推导的出现功能逐渐减弱.装饰器也是一个实例@property\@staticmethod\@classmethod\@asynico.cortine
  2. 匿名函数最适合在参数中传入,也就是传参的时候写。调用运算符()除了运用在函数上面,还可以运用到可调用的其他对象上,执行callable()函数,如def lambda 定义的函数、内置函数、内置方法、类(先**new\在初始化init**一个实例\再返回一个实例)、类的实例(如果类定义了__call__方法的话,那么它的实例可以作为函数调用)

5.2 如何让类实例可调用__call__方法

python没有new关键字,类初始化的时候实际就是__new__了一个实例,然后初始化,最后返回那个实例的。

import random

class BingoCase:

    def __init__(self,items):
        self._items = list(items)
        random.shuffle(self._items)
        
        
    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LookupError("pick from empty Bingocase")
    def __call__(self):
        return self.pick()
a = BingoCase([3,5,7,2])
a()#把一个实例变成可调用的对象

5.3 python中__slots__和__dict__以及__annotaions__有什么区别?

  1. 把函数作为对象的时候相关的几个属性 __dict__函数使用这个属性来存储赋予它的用户属性。
  2. __annotations__可以在函数定义的时候添加注解同时也返回注解、不作任何检查例如def getMin("str"->s) -> "str"有点类似java中声明类型。
  3. __slots__是类属性,里面包含__dicts__当实例很多的时候也用__slots__来专门声明。

5.4 opeartor库有了解吗?

  1. operator.itemgetter\attrgtter#可以从序列中取出元素或者读出元素对象,可以实现函数式编程的特点,把运算符当做函数使用。如果attrgetter里面含有’.’ 还会深入嵌套选择元素。例如:
# lambda a,b:a*b #这么一个简单的函数配上reduce就是神器
from functools import reduce 
a = reduce(lambda a,b:a * b,range(1,5))
  1. 在operator 其他函数中_下划线开头的一般为实现细节的内容,然后再functools中的函数除了reduce比较常用外,还有methodcallerpartial最出名。methodcaller 可以执行函数如methodcaller(‘replace’,s,’*’,’ ')把字符串星号变成空格,还有就是 a= partial(mul,3) a(7)=21把一个2个参数的函数变成只需要传入一个参数的函数。partialmethod 是用于处理方法,不是处理函数。

本章主要介绍了Python函数的一些特性例如:把函数当做一个变量传入参数、返回一个函数、7种可调用的对象。高阶函数有map\filter\max\sorted\min
operator Mul、和functools的partial、reduce、partialmethod

5.5 高阶函数与一等对象的解释(可返回可传参、可接受函数作为参数的函数)






函数内省
help\dir\ 都比较好用

5.6 函数属性(闭包要注意)





函数注解的方式

六、使用一等函数实现设计模式

  1. 虽然设计模式跟所用的语言无关,但并不意味着每一个模式都能在每一门语言中使用。

  2. 策略模式:6-1表示出了策略模式对类的编排。
    定义:定义一系列的算法,把他们一一封装起来,并且使他们可以相互替换,本模式使得算法可以独立于使用它的客户而变化。
    根据客户的属性或订单中的商品来计算折扣。

例如:

  • 有1000或以上积分的用户享用5%折扣
  • 同一订单中,单个商品的数量达到20或以上可以享用10%折扣
  • 订单中的不同商品达到10或者10个以上享7%折扣
    假设一个订单只能享用一次折扣。
  1. 在设计的时候,一定要清楚的知道自己输入的是什么,输出的是什么。(类对象、还是方法对象,有什么属性,是全局的还是局部的,是可变的还是不可变的,有没有self)
from abc import ABC,abstractmethod
from collections import namedtuple

Customer = namedtuple("Customer","name fidelity")#客户加积分

class LineItem:#组成购物车
    def __init__(self,product,quantity,price):
        self.product = product
        self.quantity = quantity
        self.price = price
        
    def total(self):
        return self.price * self.quantity
    
class Order:#上下文也就是订单 用户行为 有车 有打折 有用户情况
    
    def __init__(self,customer,cart,promotion=None):#cart是购物车
        self.customer = customer
        self.cart = list(cart)
        self.promotion = promotion
        
    def total(self):
        if not hasattr(self,'__total'):
            self.__total = sum(item.total() for item in self.cart)
        return self.__total
    def due(self):
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion.discount(self)#传入一个order
        return self.total() - discount
    
    def __repr__(self):
        fmt = ''
        return fmt.format(self.total(),self.due())
    
class Promotion(ABC):
    @abstractmethod
    def discount(self,order):
        "返回折扣金额(正值)"
class FidelityPromo(Promotion):
    "积分1000或1000以上的用户"
    def discount(self,order):
        return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
    
    
class  BulkItemPromo(Promotion):
    "单个商品20个或以上的订单"
    def discount(self,order):
        discount = 0
        for item in order.cart :
            if item.quantity >= 20:
                discount += item.total() * 0.1

        return discount
    
class LargeOrderPromo(Promotion):
    
    def discount(self,order):
        discount_items = {item.product for item in order.cart}
        if len(discount_items) >= 10:
            return order.total() * 0.07
        
        return 0
        
joe = Customer('john Doe',0)
ann = Customer("Ann Smith",1100)
cart = [
    LineItem('banana',4,0.5),
    LineItem('appple',10,1.5),
    LineItem('watermellon',5,5.0)
]
Order(joe,cart,FidelityPromo())

Order(ann,cart,FidelityPromo())
banana_cart = [
    LineItem('banana',30,0.5),
    LineItem('apple',10,1.5)
]
Order(joe,banana_cart,BulkItemPromo())

函数作为参数传入不需要创建类来实例化对象传入。


class Order:#上下文
    
    def __init__(self,customer,cart,promotion=None):#cart是购物车
        self.customer = customer
        self.cart = list(cart)
        self.promotion = promotion
        
    def total(self):
        if not hasattr(self,'__total'):
            self.__total = sum(item.total() for item in self.cart)
        return self.__total
    def due(self):
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion(self)#传入一个order
        return self.total() - discount
    
    def __repr__(self):
        fmt = ''
        return fmt.format(self.total(),self.due())
    
    
def fidelity_promo(order):
    "为积分有1000的打5%的折扣"
    
    return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
def bulk_item_promo(order):
    discount = 0
    for item in order.cart:
        if item.quantity >= 20:
            discount += item.total() * 0.1

    return discount
def large_order_promo(order):
    discount_items = {item.product for item in order.cart}
    if len(discount_items) >= 10:
        return order.total() * 0.07
    return 0
  1. 命令设计模式也可以通过把函数作为参数传递而简化

七、函数装饰器和闭包

7.1 装饰器是什么?一般有什么用?

  1. 函数装饰器用于在源码中标记函数,以某种方式增强函数的行为。这是一项强大的功能,但是若想掌握,必须理解闭包。
  2. 装饰器能把被装饰的函数替换成其他函数,装饰器在加载模块的时候立即执行
  3. 一般用来做补丁、或者注册、或者hook某一事件、缓存存取
  4. 从下面这个例子可以看出,函数装饰器在导入模块时立即运行,而被装饰的函数只有在明确调用时才会运行。
  5. 装饰器函数和被装饰的函数再同一个模块中定义,实际情况是装饰器通常在一个模块中定义,然后应用到其他模块的函数上。
  6. register装饰器返回的函数跟通过参数传入的相同,实际上大多装饰器会在内部定义一个函数然后将其返回。
@decorate
def target():
    print("running target")
#等同于
target = decorate(target)
#可以理解为一个功能函数在需要的时候,可以装饰任何函数,把该函数作为参数传入
registry = []
def register(func):
    print("running retister(%s)"%func)
    registry.append(func)
    return func
@register
def f1():
    print("running f1()")
    
    
@register
def f2():
    print("running f2()")
    
@register
def f3():
    print("running f3()")

#在上节中只需要把每个促销函数加上一个装饰器


promos = []#利用装饰器来添加促销函数(对象)也就是策略
def register(func):
    promos.append(func)
    return func

@promotion
def fidelity(order):
    return order.total() * 0.05 if oder.customer.fidelity >= 1000 else 0

def best_promo(order):
    return max(promo(order) for promo in promos)

7.2 如何使用装饰器?

  1. 不过多数装饰器会修改被装饰的函数。通常,他们会在内部定义一个内部函数,然后将其返回,替换成被装饰的函数。使用内部函数的代码几乎都要靠闭包才能正常运行,为了理解闭包,我们要后退一步,先了解Python中的作用域。分清局部变量与和全局变量域(可变对象和不可变对象的严格遵守)
  2. 综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义的作用域不可用了,但是能使用那些绑定。只要函数中某个变量出现了赋值无论先后,都会被判断为局部变量,是因为在python编译函数定义体的时候出现的变化。
from dis import dis
b = []
def f1(a):
    print(a)
    print(b)
    b.append(3)

class Averager():
    def __init__(self):
        self.series = []
    
    def __call__(self,new_value):
        self.series.append(new_value)
        total = sum(self.series)
        return total/len(self.series)
        
def make_averager():
    series = []#闭包
    
    def averager(new_value):
        series.append(new_value)#自由变量
        total = sum(series)
        return total/len(series)
    return averager

7.3 利用装饰器来兼容模式

  1. 装饰器的典型行为:把装饰的函数替换成新函数,二者接受相同的参数,而且通常返回被装饰的函数本该返回的值,同时还会做些额外的操作。

def make_averager():
    count = 0 
    total = 0
    def averager(new_value):
        nonlocal count,total#把变量标记为自由变量:指的是未在本地作用域中绑定的变量
        count += 1
        total += new_value
        return total/count
    return averager#在闭包中,对于不可变类型 数字 字符串、元组、集合这样的类型来说  要声明nonlocal才能对其操作
#实现一个简单的装饰器

def clock(func):
    def clocked(*args):
        t0 = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - t0
        name = func.__doc__
        arg_str = ','.join(repr(arg) for  arg in args)
        print("[%0.8fs]%s(%s)->%r"%(elapsed,name,arg_str,result))
        return result
    return clocked
import time 

@clock
def snooze(seconds):
    time.sleep(seconds)

@clock
def factorial(n):
    "doc of fucn"
    return 1 if n<2 else n*factorial(n-1)

7.4 装饰器缺点

  1. 上面的装饰器clock有一些缺点,不支持关键字参数,而且遮盖了被装饰函数的**namedoc**属性。functool.warps装饰器把相关的属性从func复制到clock中.
import time
import functools

def clock(func):
    @functools.wraps(func)
    def clocked(*args,**kwargs):  #支持关键字参数了
        t0 = time.time()
        result = func(*args,**kwargs)
        name = func.__name__
        elapsed = time.time() - t0
        arg_lst = []
        if args:
            arg_lst.append(','.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r'%(k,w) for k,w in sorted(kwargs.items())]
            arg_lst.append(','.join(pairs))
        arg_str = ','.join(arg_lst)
        print('[%0.8fs]%s(%s)->%r'%(elapsed,name,arg_str,result))
        return result
    return clocked
            

7.5 你知道哪些装饰器?分别有什么用?

  1. Python中内置了3个装饰器property\classmethod\staticmethod 一个是变成只读属性、@property.setter、@property.getter可以实现只读属性、一个是类方法、一个是变成普通方法。
  2. 接下来讲解functools.wraps (协助构建行为良好的装饰器)
    functools.lru_cache 做备忘(会把耗时的函数结果保存起来,避免传入相同的参数重复计算,一段时间不用的缓存就会被扔掉)
  3. functools.singledispatch 单分派式函数。

11. 叠放装饰器,也就是装饰器里面有装饰器。

@d1
@d2
def f1():  =>d1(d2(f1))

12.还可以初始化带参数的装饰器,例如可以设置一个参数active=True 来判断是否启用装饰器。

要深入理解装饰器,要区分运行时和导入时,还要知道变量的作用域(不可变、可变)、闭包、和nonlocal声明,掌握nonlocal和闭包不仅对构建装饰器有帮助,还能协助你在构建GUI程序时面向事件编程,或者使用回调处理异步I/O
functool.lru_cache\functools.wraps\functools.singledispatch()

7.6 global 和nonlocal的区别

7.4 递归利用functools 的lru_cache()函数指定缓存大小。

一个是函数中有函数表示 外层不可变对象的值使其自由。


你可能感兴趣的:(Python)