Python相关知识点准备
九、符合Python风格的对象
- 得益于Python的数据模型,自定义类型能够像Python内置类型那样自然。实现如此自然的行为,靠的不是继承,而是鸭子类型(duck type) ,前一章分析了Python对象的结构和行为,这一章则自己定义类,让定义的类像Python对象一样。
9.1 什么是散列表?
- 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
2.** 对象表现形式,在python中有2个函数:repr(),str()一个是适用于开发者的,一个是适用于用户的**。为了给对象提供其他表现形式,还会提供2个特殊方法 bytes 和**format**函数。 在python3中unicode 就是str类型也就是字符串类型。
9.2 classmethod有什么用?
- classmethod 的最常用的方法就是用来定义备选构造方法,是用来定义操作类,而不是定义实例方法,所以传入的参数是类本身而不是操作类。staticmethod 也会改变调用方式,但是第一个参数不是特殊参数,其实静态方法就是普通函数。只是碰巧在类定义中,而不是在模块定义里。现在我们已经对classmethod 有所了解了,而且知道staticmethod 不是特别有用。classmethod总是会返回全部参数,调用的时候。静态方法不会返回实例。
- 简单的把这几个理解为 某个类的变量共享 变量操作吧。意思就是同一个类里面要想在不同方法操作同一个变量的话就需要定义类方法,类变量 实例方法,实例变量。
9.3 classmethod和staticmethod在继承的时候有什么区别?
- 子类的实例继承了父类的static_method静态方法,调用该方法,还是调用的父类的方法和类属性。子类的实例继承了父类的class_method类方法,调用该方法,调用的是子类的方法和子类的类属性。
注意普通函数 对象是不可以调用的。
如果只看这个图,很多人可能会看的一头雾水,特别是学过完全面向对象语言的同学, Python 是双面向的,既可以面向函数编程,也可以面向对象编程,所谓面向函数就是单独一个. py 文件,里面没有类,全是一些函数,调用的时候导入模块,通过模块名.函数名()即可调用,完全不需要类,那么你可能会问,那要类还有什么毛用?** 类就是用来面向对象编程啦,类可以有自己的属性,类可以创建很多实例,每个实例可以有不同的属性,这也就保存了很多私有的数据,总之都有存在的必要.**
再来看上面这张图,在类里面定义的函数就是方法,类方法需要@ classmethod 修饰并且有个隐藏参数 cls,实例方法必须有个参数 self, 静态方法必须有 @staticmethod修饰,类和实例都可以访问静态方法,实例可以访问实例方法也可以访问类方法,类可以访问类方法也可以访问实例方法,访问实例方法必须要带参数 self, 可以理解为类其实也是一个实例,类访问实例方法不带参数会报错的.类本身可以访问函数,实例却不行.
9.4 如何让对象可散列?
- 可散列对象才能放入set中,目前来说我们定义的二维向量还是不可散列的。
**hash实现这个方法
要创建可散列的对象,不一定要实现特性,也不一定要保护实例属性,只需要正确地实现hash和eq**就好了
from array import array
import math
class Vector2d:
typecode = 'd'
def __init__(self,x,y):
self.__x = float(x)
self.__y = float(y)
@property #使得私有变量可以用self.x来访问,相当于把函数当做属性
def y(self):
return self.__y
@property
def x(self):
return self.__x
# @x.setter
# def x(self, value):
# self.__x = value#没这个方法就不可以修改
def __iter__(self):
return (i for i in (self.x,self.y))
def __hash__(self):
return hash(self.x)^hash(self.y)
9.5 property\setter\getter有什么用?
- Python的私有属性和受保护的属性,python的私有机制 实际上是改名字,为不同类别的东西加上不同的前前缀,就可以实现外部不访问变量,也能不轻易就覆盖掉。
- 在python中,python在各个实例中的
__dict__
属性中存储那个实例的所有属性。可以在类里面定义一个__slots__
变量来存储所有属性这样,在几百万个实例运行的时候,可以有效减少内存的使用。放入numpy数组里效果更好
1、实现自定义的格式代码,公开只读属性以及通过**hash()**函数支持集合和映射。
所有用于获取字符串和序列的表现形式的方法:__repr__
__format__
__str__
__bytes__
把对象转换成数字的几个方法 __abs__
__bool__
__hash__
用于测试字节序列转换和支持散列(连通__hash__的方法)eq 一起实现。
9.6 实现只读属性
9.7 实现备选构造方法(也就是工厂模式例如像ValueOf方法)
9.8 classmethod 和staticmethod的区别
就是类方法和普通函数的区别,第一个参数是否为self。。
全局的属性
十、序列的修改、散列、切片
-
基本序列协议 len getitem
-
正确表述拥有很多元素的实例
-
适当的切片支持,用于生成新的Vector实例
-
总格各个元素计算散列值
-
自定义的格式语言扩展
此外,我们还将实现**getattr** 方法实现属性的动态获取以取代以前的只读属性。
genism 利用numpy scipy 实现了自然语言处理当中的检索信息和向量空间模型,如果在实际过程中要作向量运算,应该使用Numpy和Scipy 。
# -*- coding: utf-8 -*-
# @Author: Mr.Jhonson
# @Date: 2018-01-07 13:29:50
# @Last Modified by: Mr.Jhonson
# @Last Modified time: 2018-01-07 13:29:50
from array import array
import reprlib
import math
import numbers
import operator
import functools
import itertools
class Vector:
shortcutnames = 'xyzt'
typecode = 'd'
def __init__(self,components):
self._components = array(self.typecode,components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components)#可以做到只显示部分字符串
# print(components)#
components = components[components.find('['):-1]#能不生成列表尽量不要生成列表,利用构造方法耗时耗力
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+bytes(self._components))
# def __eq__(self):
# return tuple(self)==tuple(other)
def __eq__(self):
if len(self) != len(other):
return False
for a,b in zip(self,other):
if a != b:
return False
return True
def __eq__(self):
return len(self)==len(other) and all(a==b for a,b in zip(self,other)) #只要有一个真就够了
def __abs__(self):
return math.sqrt(sum(x*x for x in self))#计算距离
def __bool__(self):
return bool(abs(self))
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
#实现序列协议
def __len__(self):
return len(self._components)
# def __getitem__(self,index):
# return self._components[index]
#重写一个能处理切片的__getitem__方法
def __getitem__(self,index):
cls = type(self)#获取实例
if isinstance(index,slice):
return cls(self._components[index]) #返回新的帝乡
elif isinstance(index,numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'.format()
raise TypeError(msg.format(cls=cls))
#动态设置属性
def __getattr__(self,name):
cls = type(self)
if len(name)==1:
pos = cls.shortcutnames.find(name)
if 0 <= pos < len(self._components):
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}'
raise AttributeError(msg.format(cls,name))
#为了避免属性设置的矛盾出现,我们要改写属性设置的逻辑,当我们设置属性的时候就会传入这两个值
def __setattr__(self,name,value):
cls = type(self)
if len(name) == 1: #设置新的属性
if name in cls.shortcutnames:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attributes 'a' to 'z' in {cls_name!r}"
else:
error = ' '
if error:
msg = error.format(cls_name = cls.__name__,attr_name = name)
raise AttributeError(msg)
super().__setattr__(name,value)#这是一个设置函数#默认情况下,在超类调用__setattr__这个方法。
def __setitem(self,index,value):
pass #支持分量赋值。
#为了把这个Vecotr变成 一个可散列对象。
def __hash__(self):
# hashes = (hash(x) for x in self._components )#生成器表达式
hashes = map(hash,self._components)#换成map函数更直观。python3则是惰性的,按需提供。
return functools.reduce(operator.xor ,hashes,0)#初始参数,如果没有,默认把第一 二个对象作为
#计算角度的
def angle(self,n):
r = math.sqrt(sum(x*x for x in self[n:]))
a = math.atan2(r,self[n-1])
if (n==len(self)-1) and (self[-1]<0):
return math.pi * 2 - a
else:
return a
def angles(self):
return (self.angle(n) for n in range(1,len(self)))
def __format__(self,fmt_spec = ''):
if fmt_spec.endswith('h'):#超球面坐标:
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],self.angles())
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
components = (format(c,fmt_spec) for c in coords)
return outer_fmt.format(','.join(components))
vec = Vector(range(200))
vec.gs = 5 #动态设置值
vec.x #动态获取值
vec.q #没有值抛出异常
-
如果大量使用isinstance 表明面向对象设计得不好,不过再**getitem**方法里面使用切片是合理的。
-
接下来是实现Vector 动态存储对象。当属性查找失败的时候,Python 会调用**getattr方法,简单来说,对于obj.x python会检查该对象有没有这个属性 到obj.class中去找,如果找不到,会顺着继承树去查找,如果依旧找不到的话,调用getattr**方法。还分找到时是否为描述符、数据描述符和非数据描述符的区别。
-
序列的两个协议 len getitem 和 定义了 getattr 一般就要定义 setattr
-
实现**hash**方法时,我们用了大量的生成器表达式,和reduce函数把异或运算符运用到了极致,实现了计算聚合可散列值,而且最后用了内置的归约函数 all 达到了更高效的计算。(__eq__函数)
10.1 OrderDict 是一个双向链表 维护key的顺序,以及一个map,hash。
-
**归约函数(any,all,sum,reduce)**把有限的序列或者可迭代对象变成一个聚合的结果。
-
映射规约:把函数应用到各个元素上,生成一个新序列,(map,reduce)然后计算聚合值(规约,reduce)
-
在实现**eq**方法的时候,为了提高效率,也就是不复制两个元组比较,而是用zip 函数,生成一个元组的生成器。
-
为了避免在for循环迭代元素的过程中不处理索引变量或者轻松并行跌打两个或者更多的可迭代对象,其中一个内置zip函数就是这个功能。返回的元组可以拆包。还有enumerate函数,可以处理索引,也是一个生成器函数。
十一、从协议到抽象基类
- 在python里面,没有interface这个关键字,除了抽象基类,每个类都有接口(协议)非正式的接口:类实现或继承的公开属性,包括特殊方法,如**getitem** 或 add
11.1 python中没有接口这个概念、那有抽象基类和混合类
-
对Python来说,X类对象、X协议、X接口都是同一个意思。在某一个类中,即时没有实现 序列的**contains接口,但是如果我们实现了getitem** ,那么Python会自动实现 iter contain 只是迭代操作
-
在我们以前实现的一个FrenchDeck类无法用shuffle洗牌,会报错。说不支持item assignmnt
-
本章讨论的是鸭子类型:对象的类型无关紧要,只要实现了相应的协议就可以了。(忽略对象真正的类型,转而关注对象有没有实现所需的方法,签名与语义。)
-
鸭子类型、白鹅类型:只要cls是抽象基类,cls的元类是abc.Metaclass 就可以使用isinstance(obj,cls)
isinstance(the_arg,collections.abc.Sequence)
isinstance(Struggle(),abc.Sized)#只要实现了 __len__方法,就是这一类
-
即时是抽象基类,也不要滥用isinstance接口,用得多了可能导致代码异味,即表明面向对象设计得不好,在一连串的if/elif/elif/else中使用isinstance做检查,然后根据对象的类型执行不同的操作,通常是不好的做法,此时应该用多态,也就是采用一定的方式定义类,让解释器把调用分派给正确的方法而不是用
if/elif/elif/else
来硬编码分派逻辑。 -
具体使用的时候有一个例外,也就是接受一个字符串或者字符序列的时候,也就是说isinstance(x,str)就可以
isinstance(x,abc.MutableSequence)
import collections
Card = collections.namedtuple('Card',['rank','suit'])
class FrenchDeck2(collections.MutableSequence):
ranks = [str(n) for n in range(2,11)]+list('JQKA')
suits = 'spades diamond 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,pos):
return self._cards[pos]#支持随机抽选
def __setitem__(self,pos,value):#支持洗牌
self._cards[pos] = value
def __delitem__(self,pos):#继承MutableSequence 必须实现的方法
del self._cards[pos]
def __insert__(self,pos,value):
self._cards.insert(pos,value)
from random import shuffle
shuffle(deck)
deck[:5]#可以支持打乱了,因为FrenchDeck实现了可变序列协议所需要的方法
11.2 python 中的集合类
TombolaList.mro#method reslovtion order 方法解析序列 真实的父类就是 list object
11.3 python中如何定义基类定义抽象方法注册虚拟子类
#从有限的集合中去挑选物品的机器,选出的物品是无重合的,直到选完为止。
import abc
class Tombola(abc.ABC):
@abc.abstractmethod
def load(self,iterable):
"""从可迭代对象里面添加元素。"""
@abc.abstractmethod#抽象方法的装饰器,且抽象方法必须被覆盖。
def pick(self):
"""随机删除元素,然后将其返回"""
def loaded(self):
"如果至少有一个元素,返回True"
return bool(self.inspect())
def inspect(self):
items = []
while True:
try:
items.append(self.pick())
except LookupError:
break
self.load(items)
return tuple(sorted(items))
from random import randrange
@Tombola.register
class TombolaList(list):
def pick(self):
if self:
pos = randrange(len(self))
return self.pop(pos)
else:
raise LookupError('pop from empty Tombalist')
load = list.extend
def loaded(self):
return bool(self)
def inspect(self):
return tuple(sorted(self))
如果真实子类没有实现抽象方法的话无法实例化
-
抽象基类如何注册子类,如何检查注册子类。抽象基类的抽象方法必须实现、还有很多协议要理解,这样可以增加动态语言的特性。
-
在猴子补丁当中动态添加了一个协议就可以支持洗牌。(协议的更改可以更改类的特性。)
-
如果一门语言很少隐式转换数据类型,这就是强语言类型python、java c++ 而JavaScript 和php Perl 就是弱的
在编译时检查的静态,在运行时检查的是动态。(静态要声明类型)
利用abc来注册虚拟子类,用subclass,isinstance是可以检查出来的。
十二、继承的优缺点
12.1 如果要重写list、dict应该用collection提供的类
- 内置类型子类化很麻烦,如dict和list类型,即使子类化之后也不能调用子类覆盖的特殊方法。
- 所以不要直接子类化内置类型,因为它不会调用我们缩写的覆盖的特殊方法,而且也不符合面向对象编程的特点,面向对象应该是从self实例所属的类开始搜索方法,即使在超类中实现的类调用也是如此。但我们可以使用collections里面已经经过特殊设计,易于扩展的
collections.Userdict
UserList
UserString
设置值的时候每一个覆盖方法都可以hook住。
12.2 python继承解析顺序(mro解决了菱形继承的问题)
-
多重继承和方法解析顺序。(任何实现多重继承的语言都存在潜在的命名冲突,这种冲突由不相关的祖先类实现的同名方法引起的)应该是左至右。解析。
-
把接口继承和实现继承分开,使用多重继承的时候,一定要明确一开始为什么要创建子类,主要原因能有**:1.继承接口,创建子类性,实现是什么关系2.继承实现,通过重用避免代码重复**。使用抽象基类显示表示接口,通过混入重用代码。
混入类:为多个不相关的类实现方法,所以可以称之为混入类。在名称中明确声明XviewMixin
不要子类话多个具体类,也就是说最多一个具体类其他都是抽象类或者混入类,如List、Dict MutableSequence
没有方法和属性,只进行集成的就是聚合类 可以实现更好的扩展
-
不要子类化内置类型,应该使用collections.UserDict UserString UserList等易于扩展的类
-
讨论了多重继承的双刃性,我们说明了
__mro__
属性,及super
方法,传入两个参数(要搜索目标父类的类,传入的参数是什么)#其实就是看某一个类的某一个超类方法的方法解析顺序。默认看本身父类
super(type, obj): obj must be an instance or subtype of type要传入一个子类或者实例才能访问这个方法。 -
在开发过程中,除非是开发框架,否则的话极少运用到多重继承,并自己编写抽象积累,设计个各类的结构,如何继承如何混入,如何聚合,如何抽象,以及如何具体化。java不支持多重继承(也就是不支持混入类),但可以实现多个接口。
12.3 什么是菱形继承问题
可以使用类名调用来解决奇异
十三、正确重载运算符
13.1 如何正确重载运算符?考虑到各种传递性、验证结果
- 三个一元运算符的重载。neg pos ~invert 整数按位取反定义为~x==-(x+1),运算符有个基本的规则,也就是始终返回self 返回原本的实例对象,本来改变其原来的结果。
# -*- coding: utf-8 -*-
# @Author: Mr.Jhonson
# @Date: 2018-01-07 13:29:50
# @Last Modified by: Mr.Jhonson
# @Last Modified time: 2018-01-07 13:29:50
from array import array
import reprlib
import math
import numbers
import operator
import functools
import itertools
class Vector:
shortcutnames = 'xyzt'
typecode = 'd'
def __init__(self,components):
self._components = array(self.typecode,components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
components = reprlib.repr(self._components)#可以做到只显示部分字符串
# print(components)#
components = components[components.find('['):-1]#能不生成列表尽量不要生成列表,利用构造方法耗时耗力
return 'Vector({})'.format(components)
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)])+bytes(self._components))
# def __eq__(self):
# return tuple(self)==tuple(other)
# def __eq__(self):
# if len(self) != len(other):
# return False
# for a,b in zip(self,other):
# if a != b:
# return False
# return True
def __eq__(self,other):
if isinstance(other,Vector):
return len(self)==len(other) and all(a==b for a,b in zip(self,other)) #只要有一个真就够了
else:
return NotImplemented
def __abs__(self):
return math.sqrt(sum(x*x for x in self))#计算距离
def __neg__(self):
return Vector(-x for x in self)
def __pos__(self):#先定义+是什么含义
return Vector(self)#这是运算符操作 不是add add是两个数。
def __bool__(self):
return bool(abs(self))
def __add__(self,other):#再定义+这个操作 other就是 +之后返回的对象
try:
pairs = itertools.zip_longest(self,other,fillvalue=0.0)
return Vector(a+b for a,b in pairs)
except TypeError:
return NotImplemented
def __radd__(self,other):
return self + other
def __mul__(self,scalar):
if isinstance(scalar,numbers.Real):
return Vector(n * scalar for n in self)
else:
return NotImplemented
def __rmul__(self,scalar):
return self * scalar
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv)
#实现序列协议
def __len__(self):
return len(self._components)
# def __getitem__(self,index):
# return self._components[index]
#重写一个能处理切片的__getitem__方法
def __getitem__(self,index):
cls = type(self)#获取实例
if isinstance(index,slice):
return cls(self._components[index])
elif isinstance(index,numbers.Integral):
return self._components[index]
else:
msg = '{cls.__name__} indices must be integers'.format()
raise TypeError(msg.format(cls=cls))
#动态设置属性
def __getattr__(self,name):
cls = type(self)
if len(name)==1:
pos = cls.shortcutnames.find(name)
if 0 <= pos < len(self._components):
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}'
raise AttributeError(msg.format(cls,name))
#为了避免属性设置的矛盾出现,我们要改写属性设置的逻辑,当我们设置属性的时候就会传入这两个值
def __setattr__(self,name,value):
cls = type(self)
if len(name) == 1:
if name in cls.shortcutnames:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attributes 'a' to 'z' in {cls_name!r}"
else:
error = ' '
if error:
msg = error.format(cls_name = cls.__name__,attr_name = name)
raise AttributeError(msg)
super().__setattr__(name,value)#这是一个设置函数#默认情况下,在超类调用__setattr__这个方法。
def __setitem(self,index,value):
pass #支持分量赋值。
#为了把这个Vecotr变成 一个可散列对象。
def __hash__(self):
# hashes = (hash(x) for x in self._components )#生成器表达式
hashes = map(hash,self._components)#换成map函数更直观。python3则是惰性的,按需提供。
return functools.reduce(operator.xor ,hashes,0)#初始参数,如果没有,默认把第一 二个对象作为
#计算角度的
def angle(self,n):
r = math.sqrt(sum(x*x for x in self[n:]))
a = math.atan2(r,self[n-1])
if (n==len(self)-1) and (self[-1]<0):
return math.pi * 2 - a
else:
return a
def angles(self):
return (self.angle(n) for n in range(1,len(self)))
def __format__(self,fmt_spec = ''):
if fmt_spec.endswith('h'):#超球面坐标:
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],self.angles())
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
components = (format(c,fmt_spec) for c in coords)
return outer_fmt.format(','.join(components))
def __matmul__(self,other):
try:
return sum(a*b for a,b in zip(self,other))
except TypeError:
return NotImplemented
def __rmatmul__(self,other):
return self @ other
-
本章首先说明了Python对运算符重载的措施加的一些限制,禁止重载内置类型的运算符,而且限于重载现有的运算符。
-
然后介绍了如何重载一元运算符,并实现了
__add__
__radd__
__matmul__
__rmatmul__
的方法,返回的类型应该是新对象,且不能修改操作数。 -
如果操作数的类型不同,我们要检出不能处理的操作数。本章使用两种方式:1.使用鸭子类型,检查出不支持抛出异常,2.显式的使用isinstance 来检查,两种用法各有千秋,鸭子类型更灵活,显式操作更清楚。
-
对于不可变类型和可变类型来说,都支持
__add__
可以让a += b => a = a + b
但是对于不可变类型,还可以实现__iadd__
来进行就地修改操作,从而不创建新对象。 -
运算符重载省去了很多麻烦,让计算科学数学方面的应用得到更灵活,但是同时在性能安全方面会有所缺点。
13.2 运算操作符
十四、可迭代对象、迭代器和生成器
-
迭代是数据处理的基石,扫描内存放不下的数据集时,我们要找到一种
惰性
获取数据的方式,即按需一次获取一个数据项。这就是迭代器模式。Python中利用yield
来生成生成器,迭代器用于从集合中提取元素,而生成器用于凭空生成元素。 -
在Python中所有集合都可以用于
迭代
:1.for循环,2.构建扩展集合类型3.逐行遍历文本文件 4.列表推导,字典推导和集合推导
5.元组拆包 6.调用函数时,使用*
拆包实参
14.1 for in的过程是怎么样的?有什么后路?
- 序列是可以迭代的原因。解释器需要迭代对象x 的时候,会自动调用iter(x),过程如下:
a. 检查是否实现了__iter__
方法,如果有就调用他,获取一个迭代器。
b. 如果没有__iter__
方法,但是实现了__getitem__
方法,Python会从0开始创建一个迭代器,尝试按顺序获取元素。
c.如果尝试失败,Python会抛出TypeError的异常 #迭代器的法则
- 标准的迭代器接口有两个方法:
__next__
返回下一个可用元素,如果没有元素了,抛出StopIteration
__iter__
返回self,以便在应该使用迭代器的过程中使用迭代器,例如for循环中
这个接口在collections.abc.Iterator里面制定,这个类中定义了**next抽象方法,继承自Iterable**
检查是不是迭代器的最好操作是isinstance(x,abc.Iterator)
14.2 迭代器是怎么操作的?
#在python中
s_1 = 'ABD'
for i in s_1:
print(i)
#相当于
s_2 = 'ABD'
it = iter(s_2)
while True:
try:
print(next(it))#
except StopIteration:#如果没有元素了,就会抛出异常
del it
break
14.3 如何实现一个迭代器?
#定义一个类,来实现迭代器的一些功能
import re
import reprlib
from collections import abc
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
# def __getitem__(self,index):
# return self.words[index]
# def __len__(self):
# return len(self.words)
def __repr__(self):
return "Sentence (%s)"%reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self,words):
self.index = 0
self.words = words
def __next__(self):
try:
word = self.words[self.index]#其实这一步就访问了__getitem__方法了,不过是内置类型的。
except IndexError:
raise StopIteration
self.index += 1
return word
def __iter__(self):
return self
可迭代对象返回迭代器,迭代器的iter返回本身。
14.4 可迭代对象和迭代器有什么区别?
-
可迭代对象
不等于迭代器,也就是说SentenceIterator
等于Sentence
Sentence实现一个iter方法,构建一个迭代器,迭代器实现__iter__
方法返回本身。也就是说,可迭代对象一定不能实现__next__方法,而迭代器一定要有__iter__不断迭代自身。
-
第三版,利用
生成器函数
来代替迭代器类。在函数定义体里面有yield
关键词的函数就是生成器函数,生成器函数可以说是生成器工厂,调用生成器函数的时候,会返回一个生成器对象。yield
这个关键字相当于已经帮我们实现了一个生成器了,我们只需要盗用它就可以了。(生成器函数里不可以有返回值) -
因为显式调用了参数,那是不是可以说for循环,只要是序列都可以成为可迭代对象。(生成器过程)
#定义一个类,来实现迭代器的一些功能
import re
import reprlib
from collections import abc
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self,text):
self.text = text
self.words = RE_WORD.findall(text)
# def __getitem__(self,index):
# return self.words[index]
# def __len__(self):
# return len(self.words)
def __repr__(self):
return "Sentence (%s)"%reprlib.repr(self.text)
def __iter__(self):
for word in self.words:
yield word
return
-
惰性实现,意识就是不着急的求值,急迫求值也就是提前求很多值,在之前版本的Sentence类都不是惰性实现的,而是先把单词构建好放入一个列表的。真正的惰性实现依赖 re.finditer
-
在写生成器表达式的时候,如果超过多行就会写生成器函数了,可以复用而且看得清。在传入生成器表达式作为参数时,如果只有一个参数,可以不用括号括起来,如果有2个参数那就需要用括号括起来了。
-
等差数列生成器。有fractions decimal int float 支持的切片索引
-
标准库中的生成器函数,用于过滤的函数有 **compress ,dropwhile,filter,filtercase,islice,takewhile **
-
用于映射的生成器函数。 accumulate\enumerate\map\startmap\ 传参数进入 然后计算结果再传进去(默认头两个参数)
5.*permutaitions\combinations\combinations_replacement\product\comb*
-
深入分析iter函数 iter可以传入两个参数,第二个是哨符,也就是终止条件。
-
介绍了迭代对象和生成器的原理机制,如何定义自己的生成器,生成器表达式和生成器函数如何使用。
-
介绍了标准库中24个生成器函数,避免重复造轮子。
-
python3.3 里面的yield from 可以返回一个生成器解决很多问题。
十五、上下文管理器和else块
-
本章主要介绍一下python中的with语句和上下文管理器,以及for while try 语句的else子句。
-
with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并负责清理上下文、**enter**返回as
fp
的对象。这么做能避免错误减少样板代码,因此API更安全而且更易用。除了自动关闭文件外,with还有很多用处。
15.1 什么情况下会执行else语句的逻辑?
-
else子句的行为如下:当仅当for语句没有被break终止的时候,就执行else语句,while语句当仅当条件为假的时候退出才会执行else语句,也是没有被break打断的情况下。 try没有异常的情况下才会执行else。在所有情况下,如果由于 break、continue、return 异常导致控制权跳到复合语句之外,else子句也会被跳过。
-
在python中try/except 实际还被用于控制流程中,它表示某一个东西不抛出异常之后才会执行某个代码块,所以,Easier to ask forgiveness than permission EAFP 比 C语言中的look before you leap
-
上下文管理器对象存在的目的是为了管理 with语句就像迭代器存在是为了管理for语句一样。 with语句是简化的try/finally 语句,这种模式保证了代码结束后执行某种操作。finally语句经常用于释放资源还原临时变更。
-
上下文管理器协议包含__enter__ exit 在with开始运行的时候,进入__enter__ 结束的时候进入__exit__以此扮演finally 的角色。最常见的就是为了保证文件关闭。
15.2 上下文管理器 __enter__返回的是对象
实现逆序输出:
class LookingGlass:
def __enter__(self):
import sys
self.original_write = sys.stdout.write
sys.stdout.write = self.reverse_write
return 'JABBE'
def reverse_write(self,text):
self.original_write(text[::-1])
def __exit__(self,exc_type,exc_value,traceback):
import sys
sys.stdout.write = self.original_write
if exc_type is ZeroDivisionError:
print("please do not divide by zero")
return True
with LookingGlass() as what:
print("我的天空。")
print(what)
## output:
#。空天的我
# EBBAJ
15.3 用装饰器实现上下文管理器 contextlib-contextmanger
15.4 java 中的finally和python的with相似,python也有finally
- contextlib 模块中的实用工具 如@context.contextmanager
with open('fine.py','r') as fp:#与函数和模板不一样。with没有定义新的作用域
src = fp.read()
本章从简单话题入手,先讨论了for while try语句的子句else,然后阐述了上下文管理器的作用,要动手实现一个__enter__ exit 的方法,最后分析了contextlib 库中的@contextmanager 上下文管理器的装饰器。这个装饰器把3个Python的特性优雅地结合在一起 with、生成器、装饰器
十六、协程
-
生成器的过程是 yield value ,一个东西调用生成器,然后yield一个值回去,让其调用程序自动执行,直到需要获取下一个值的时候,再继续执行生成器函数(从生成器中获取值)yield提供给next()的调用方 暂停执行,提出让步。
-
从句法上看,协程和生成器很像,都是在定义体中出现了yield关键字,但是yield在协程中,经常是出现在右边。
协程是一个过程,这个过程与调用方合作,产出由调用方提供的值。send方法会成为yield暂停时的值,所以只有当携程处理暂停状态下才可以send值,如果协程还没激活,就可以调用send方法或者next(协程)激活
16.1 讲述一下python中的协程
写一个带yield的函数,然后利用next()
预激、然后利用send传值、利用inspect
查看状态。因为python有些解释器存在GIL,所以只能一个线程在运行,那么就利用协程用户自己控制返回逻辑,自己控制调度来实现高效率推进。
一条线程 其他都是协助 多线程是多条线程 并进 协程序之间通信用send实现的,传值 yield接收值,inspect模块获取协程的状态。
import inspect
inspect.getgeneratorstate(my_coro)#获取协程的状态
def simple_coroutine2(a):
print("-> coroutine start a = ",a)
b = yield a
print('-> recieve b=',b)
c = yield a + b
print('-> recieve c=',c)
cor2 = simple_coroutine2(14)
#利用协程实现移动的平均值求解的过程
def averager():
total = 0.0
count = 0
average = None
while True:#无线循环的作用就是只要协程不关闭,不断发值那么这个就不会停。
term = yield average
total += term
count += 1
average = total/count
17.2 协程有哪些状态?
GEN_CREATED = 'GEN_CREATED' 创建
GEN_RUNNING = 'GEN_RUNNING' 运行
GEN_SUSPENDED = 'GEN_SUSPENDED' 挂起
GEN_CLOSED = 'GEN_CLOSED' 关闭
-
在使用协程的时候一定要预激和关闭。next(cor_my),接下来使用一个装饰器来实现。
-
参数化装饰器的时候,一般涉及两层嵌套的函数,所以使用生成函数中的装饰器 functools.wraps 可以为高级技术更好的支持。
-
在使用yield from 句法的时候协程会自动预激,所以与自己写的装饰器不兼容,但是在标准库中的asyncio.coroutine装饰器不会预激协程。
-
协程未处理的异常会向上冒泡,传给send 函数或者 next函数,利用send()传入异常终止,传入一些哨符值也可以终止协程。接下来举例说明如何利用throw及close方法控制协程
-
使用yield from 自动处理内部异常来返回移动平均值。yield from 首先是去获取一个迭代器,iter(x) 所以x可以是任何可迭代对象。
from functools import wraps
def coroutine(func):
@wraps(func)
def primer(*args,**kwargs):
gen = func(*args,**kwargs)
next(gen)
return gen
return primer
#利用协程实现移动的平均值求解的过程
@coroutine
def averager():
total = 0.0
count = 0
average = None
while True:#无线循环的作用就是只要协程不关闭,不断发值那么这个就不会停。
term = yield average
total += term
count += 1
average = total/count
finally 关键字也是无论何时都会触发,跟java类似。
让协程终止
from collections import namedtuple
Result = namedtuple('Reulst','count average')
#子生成器
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
# print("来到这一层了")
if term is None:
break
total += term
count += 1
average = total/count
# print(average)
return Result(count,average)
# return 1
#委派生成器
def grouper(results,key):
while True:
# print("调用一次委派生成器,该是传值吧")
results[key] = yield from averager()
# print("传值,计算结果",results)
#客户端代码,调用方
def main(data):
results = {}
for key,values in data.items():
group = grouper(results,key)
next(group)
for value in values:
group.send(value)
# group.send(None)
print(results)
# reports(results)
# def reports(result):
# for key,result_1 in sorted(result.items()):
# group,unit = key.split(';')
# print('{:2}{:5} averaging {:.2f}{}'.format(result_1.count,group,result_1.average,unit))
data = {
'girls;kg':
[40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
'girls;m':
[1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
'boys;kg':
[39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
'boys;m':
[1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}
- 第十四章介绍了生成器,本章介绍了推送式协程–仿真示例的出租车进程。第18章还会使用这两种技术实现异步任务。
16.3 时间驱动型框架是怎么回事?协程在特定地方让出控制权,而且接受信号。
-
从移动平均求值的协程中可以看到,协程多用来做累加器,我们还可以使用装饰器预激协程,但是yield from func() 是不需要使用预激的。。最后一个离散仿真使用生成器和线程回调实现并发。事件驱动型框架asynico的运作方式:在单个线程中使用一个主循环驱动协程执行并发活动,使用协程做面向事件型编程,协程会不断把控制权让步给主循环,激活并向前运行其他协程,从而执行各个并发活动。而多线程实现的是抢占式多任务,调度程序可以在任何时候暂停线程(即时在执行一个语句),把控制权让给其他线程。
-
asyncio 的底层基本思想就是如何在一个主循环中处理事件,以及如何通过发送数据驱动协程。这个是asyncio包底层的基本思想。我们会在第18章学习这个包。
16.4 协程线程、进程的区别联系各自特点?
协程的特点在于是一个线程执行,与多线程相比,其优势体现在:
协程的执行效率非常高。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
协程不需要多线程的锁机制。在协程中控制共享资源不加锁,只需要判断状态就好了。
Tips:利用多核CPU最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
线程是在进程之后发展出来的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。一个进程可以包含多个线程。
线程的优点是减小了程序并发执行时的开销,提高了操作系统的并发性能,缺点是线程没有自己的系统资源,只拥有在运行时必不可少的资源,但同一进程的各线程可以共享进程所拥有的系统资源,如果把进程比作一个车间,那么线程就好比是车间里面的工人。不过对于某些独占性资源存在锁机制,处理不当可能会产生“死锁”
线程有锁机制比较麻烦,但是可以并发执行了。
进程是一个程序在一个数据集中的一次动态执行过程,可以简单理解为“正在执行的程序”,它是CPU资源分配和调度的独立单位。
进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
进程的局限是创建、撤销和切换的开销比较大。
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的
使用多线程也会和多进程一样,会存在资源共享问题。如果有多个线程同时运行,而且它们试图访问相同的资源(共享的资源),这时就会出现问题。而一种可行的办法就是在使用期间必须进入锁定状态。所以一个线程可将资源锁定(例如,程序设计中的线程锁),在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用同样的资源。
16.5 并行与并发的区别?
并行
并行就是指同一时刻有两个或两个以上的“工作单位”在同时执行,从硬件的角度上来看就是同一时刻有两条或两条以上的指令处于执行阶段。所以,多核是并行的前提,单线程永远无法达到并行状态。可以利用多线程和度进程到达并行状态。另外的,Python的多线程由于GIL的存在,对于Python来说无法通过多线程到达并行状态。
并发
对于并发的理解,要从两方面去理解,1.并发设计 2.并发执行。先说并发设计,当说一个程序是并发的,更多的是指这个程序采取了并发设计。
并发设计的标准:使多个操作可以在重叠的时间段内进行 ,这里的重点在于重叠的时间内, 重叠时间可以理解为一段时间内。例如:在时间1s秒内, 具有IO操作的task1和task2都完成,这就可以说是并发执行。所以呢,单线程也是可以做到并发运行的。当然啦,并行肯定是并发的。一个程序能否并发执行,取决于设计,也取决于部署方式。例如, 当给程序开一个线程(协程是不开的),它不可能是并发的,因为在重叠时间内根本就没有两个task在运行。当一个程序被设计成完成一个任务再去完成下一个任务的时候,即便部署是多线程多协程的也是无法达到并发运行的。
并行与并发的关系: 并发的设计使到并发执行成为可能,而并行是并发执行的其中一种模式。
16.6 万恶之源还是IO操作速度,网络IO 磁盘IO导致CPU利用率降低
进程的出现是为了更好的利用CPU资源使到并发成为可能。 假设有两个任务A和B,当A遇到IO操作,CPU默默的等待任务A读取完操作再去执行任务B,这样无疑是对CPU资源的极大的浪费。聪明的老大们就在想若在任务A读取数据时,让任务B执行,当任务A读取完数据后,再切换到任务A执行。注意关键字切换,自然是切换,那么这就涉及到了状态的保存,状态的恢复,加上任务A与任务B所需要的系统资源(内存,硬盘,键盘等等)是不一样的。自然而然的就需要有一个东西去记录任务A和任务B分别需要什么资源,怎样去识别任务A和任务B等等。登登登,进程就被发明出来了。通过进程来分配系统资源,标识任务。如何分配CPU去执行进程称之为调度,进程状态的记录,恢复,切换称之为上下文切换。进程是系统资源分配的最小单位,进程占用的资源有:地址空间,全局变量,文件描述符,各种硬件等等资源。
线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西-------文本内容,不停的切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。是的,这种机制就是线程。线程共享进程的大部分资源,并参与CPU的调度, 当然线程自己也是拥有自己的资源的,例如,栈,寄存器等等。 此时,进程同时也是线程的容器。线程也是有着自己的缺陷的,例如健壮性差,若一个线程挂掉了,整一个进程也挂掉了,这意味着其它线程也挂掉了,进程却没有这个问题,一个进程挂掉,另外的进程还是活着。
16.7 要理解cpu 切换 进程 线程 一定要记住 io操作、cpu密集操作
16.8 事件驱动io有什么缺点?
异步的方式
16.9 什么是阻塞IO、什么非阻塞IO、什么是同步阻塞io、异步非阻塞io
非阻塞式I/O模型、异步与事件驱动
非阻塞式:非阻塞式通常是对于I/O操作而言的,意思就是当你请求一个系统调用的时候,不管收到什么结果函数都会立即返回,而不让线程进入休眠状态以等待I/O操作的完成。相反阻塞式I/O方式在请求一个磁盘文件时会进入线程休眠状态以等待磁盘I/O完成后再苏醒。
异步/同步与阻塞式/非阻塞式的区别在于前者主要描述消息通信机制,异步时被调用者通过通知、状态或回调来告知调用者。而后者主要描述程序在等待调用结果时的状态,非阻塞式时调用者主动以一定的间隔时间查看调用是否完成。
阻塞就是线程啥也不干 同步就是等待之后立即消费 异步就是通知的时候再去消费,异步有通知。
16.10 进程是如何切换的?
为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
- 保存处理机上下文,包括程序计数器和其他寄存器。
- 更新PCB信息。
- 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
- 选择另一个进程执行,并更新其PCB。
- 更新内存管理的数据结构。
- 恢复处理机上下文。
16.11 文件描述符是什么
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。
linux 内核态最上面1G 用户态 下面3G 32位
16.12 select,poll,epoll 的优缺点以及区别?
聊聊IO多路复用之select、poll、epoll详解
select、poll、epoll之间的区别总结[整理]
16.13 linux是如何读写文件的?
从内核文件系统看文件读写过程
文件打开的过程——调用fd=open()时操作系统所做的工作
epoll最清晰的文章
总的来说epoll 在fd多的时候划算,但是在fd少的时候linux的select poll模型还是不错的。
epoll 分析文章
大话epoll
IO多路复用,就是我们熟知的select、poll、epoll模型。从图上可见,在IO多路复用的时候,process在两个处理阶段都是block住等待的。初看好像IO多路复用没什么用,其实select、poll、epoll的优势在于可以以较少的代价来同时监听处理多个IO。
16.14 next() 对协程预激
十七、使用期物处理并发
-
python3.2引入了concurrent.futures 模块,期物是一种对象,表示异步执行的操作。这个概念的作用很大,是
concurrent.futures
模块和asyncio
包的基础。 -
网络下载的三种风格。为了高效处理网络I/O (http请求、硬盘读写、系统操作),所以了为了不浪费时间去等待响应,最好在收到网络响应之前做些其他的事。
-
以下是3个例子,一个是一张一张图片的下载,需要保存完毕才开始下载下一张图片,另外2个是并发下载,一个用了concurrent.futures 一个用了asyncio包。(7s,1.30,1.40s)(依序、多线程、异步)
-
将这3个代码放在一起比较的原因是想说明:在I/O 密集型应用中,如果代码写得正确,那么不管使用哪种并发策略(使用线程或者asyncio包),吞吐量都比依次执行的代码高很多。
#接下来是并行 concurrent.futures 实现并发下载。 这个模块主要两个特色类 一个是ThreadPoolExectuor 一个是ProcessPool
#Excutor 类 接下来使用futures.ThreadPoolExcutor 的map方法实现最简单的多线程下载的脚本
from concurrent import futures
MAX_WORKERS = 20
def download_one(cc):
image = get_flag(cc)
show(cc)
save_flag(image,cc.lower()+'.gif')
return cc
def download_many(cc_list):
workers = min(MAX_WORKERS,len(cc_list))
with futures.ThreadPoolExecutor(workers) as executor:
res = executor.map(download_one,sorted(cc_list))
return len(list(res))
-
download_one 其实是第一个脚本中的many中的循环,编写并发代码时经常这样重构:把依序执行的循环写成一个单一的函数。
-
期物是concurrent.futures 和asyncio包重要组件。从Python3.4开始,concurrent.futures.Future 和asyncio.Future这两个类作用相同,两个Future类实例都表示可能已经完成或者尚未完成的延迟计算。这与Twisted 引擎中的Deferred 类,Tornado 框架中的Future类,以及多个JavaScript库中的 Promise相似。
-
期物封装待完成的操作,可以放入队列,完成的状态可以查询,得到结果后可以获取结果或异常。
-
我们要记住通常情况下我们不应该自己创建期物,而只能由并发框架(concurrent.futures 或 asyncio )实例化,原因很简单,期物表示终将发生的事情。终将发生的事情会发生的唯一方式就是执行时间已经排定。因此只有把事件交由concurrent.futures 和asyncio 子类处理时,才会创建concurrent.future.Future 实例。
-
严格来说,我们现在使用的所有脚本都不能并行下载,concurrent.future 两个示例受GIL global interpreter Lock全局解释器锁的限制,而这个程序在单个线程中运行。那么问题就来了,既然任何时候都只允许执行一个线程的话,那为什么上面那个比下面那个快那么多?(GIL 几乎对I/O密集型任务无害。)
-
Cpython解释器本身不是线程安全的,因此有了GIL,一次只允许使用一个线程执行python字节码,因此一个python进程通常不会使用多个cpu核心。
-
在执行耗时任务时,可以使用一个内置函数或者使用一个C语言编写的扩展释放GIL。其实用C语言编程写的python库能管理GIL,自行启动操作系统线程,利用全部可用的cpu核心。这会极大增加代码的复杂度,很少有作者会这样做。然后Python标准库中所有IO阻塞型操作函数,在等待操作系统返回结果时,都会释放GIL,这意味着Python语言在这个层次上能使用多线程,IO密集型程序也能从中受益。一个Python线程等待网络响应时,阻塞型IO函数会释放GIL,再运行一个线程
10.concurrent.futures
这个模块能真正实现多线程,绕开GIL。因此,如果要做IO密集型处理,使用这个模块能绕靠GIL,利用所有可用的CPU核心。
- 下载国旗的示例或者其他IO密集型作业使用
ProcessPoolExecutor
得不到任何好处。(IO密集型、阻塞型、cpu密集型)ProcessPoolExecutor
这个模块不用传入max_worker
但是要传入最大cpu数,对于cpu密集型的处理来说,不可能超过使用要求的cpu数量。(计算型就是cpu型,在内存中完成的任务,除了显示汇总结果等操作外,没有任何IO)
#显示ThreadPoolExecutor 类的map方法
from time import sleep,strftime
from concurrent import futures
def display(*args):
print(strftime('[%H:%M:%S]'),end=' ')
print(*args)
def loiter(n):
msg = '{}loiter({}): doing nothing for {}s'
display(msg.format('\t'*n,n,n))
sleep(n)
msg = '{} loiter ({}):done'
display(msg.format('\t'*n,n))
return n * 10
def main():
display('Script Strating')
executor = futures.ThreadPoolExecutor(max_workers=4)
results = executor.map(loiter,range(10))
display('results:',results)
display('wating for individual results:')
for i,result in enumerate(results):
display('result {}:{}'.format(i,result))
- 线程和多进程的替代方案。对于CPU密集型任务来说,要启动多个进程,要规避GIL。创建多个进程的最简单的方式是使用
futures.ProcessPoolExecutor
类,不过和前面的一样,如果更复杂的就要使用到multiprocess
模块代替threading
模块。
本章开头对HTTP并发客户端和一个依序下载的客户端进行了对比,结果发现并发比依序要快很多。
我们说明了如何使用Executor.submit()创建期物,以及如何使用concurrent.futures.as_completed()函数迭代运行结束的期物。接下来我们说明了为什么尽管有GIL,python线程仍能适合IO密集型应用,标准库中每个C语言写的IO函数都会释放GIL,因此在某个线程在等待IO时,Python调度会切换到另外一个线程。还讨论了如何使用ProcessPoolExecutor类使用多进程,来绕开GIL,使用多个CPU核心来运行加密算法。
最后我们深入分析了ThreadPoolExecutor类运作方式。最后简要说明了python中threading和multiprocessing 这两个传统的线程和进程的方式。celery 是一个任务队列,用于把任务分配给不同的线程和进程,甚至是不同的设备。
《parallel programming with python 》。
《七周七并发模型》
线程和锁应该有系统程序员去管理、程序员应该去管理内存分配和释放。
17.1 阻塞非阻塞、同步异步再理解。
http://python.jobbole.com/87698/ bound method 要看
17.2 asyncio 是对协程的实现
类似于Threading 包是对线程的实现一样,python3.4之后加入的asyncio 包则是对协程的实现。我们用asyncio改写文章开头的代码,看看使用协程之后能花费多少时间、所有标准库中的阻塞io都会释放gil。
17.3 什么是python的gil?
全面为一锁意思是只能运行一个线程,取决于解释器,pypy是有的,cython也是有的,ironpython就没有。
十八、使用asyncio包处理并发
二者不同,但有联系,一个关于结构,一个关于执行。并发用于指定方案,用来解决可能并行的问题。(也就是说并发是程序员端操作的,一次活动,有很多人访问,这是一个并行的问题,但是我们要怎么处理呢,就要用并发的思想来解决。)–go语言的创造者之一
本章主要讨论以下话题:
a. 对比一个简单的多线程程序和对应的asyncio版本,说明多线程和异步任务之间的关系
b. asyncio.Future类与concurrent.futures.Future 类之间的区别。
c. 第17章中下载国旗示例的异步版本
d. 摒弃线程和进程,如何使用异步回调的凡是管理网络请求中的高并发
e. 在异步编程中,与回调相比,协程是显著提升性能的方式。
f. 如何把阻塞的事件交给线程池处理,从而避免阻塞事件循环
g. 使用asyncio编写服务器,重新审视web应用对高并发的处理方式。
h. 为很么asyncio已经准备好对python生态系统产生巨大影响。
- node.js的所有都是基于事件异步回调驱动来实现并发处理的,而asyncio是使用事件循环驱动协程实现并发。这是python种最
大,也是最雄心壮志的库之一。一个4核心的cpu能跑100多个进程,实际上这是并发而不是并行,不是真正的并行。4核心的cpu做的事情不能超过4件。
#接下来使用asyncio来实现指针旋转,也就是协程的方式
import asyncio
import itertools
import sys
@asyncio.coroutine
def spin(msg):
print("进入了spin函数了")
write,flush = sys.stdout.write,sys.stdout.flush
for char in itertools.cycle('|/-\\'):#不断循环的一个函数。生成器函数按需获取
print("正在进入循环中。")
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
try:
# print("一个循环结束了")
print("循环结束了,来到一个协程暂停初")
yield from asyncio.sleep(.1)#使用短暂休眠来接收信号
except asyncio.CancelledError:
print("收到暂停信号了")
break
write(' ' * len(status) + '\x08' * len(status))
@asyncio.coroutine
def slow_function():
print("进入了阻塞函数")
yield from asyncio.sleep(3)#两重生成器 协程间的通信 那边结束了就唤醒,模拟io操作
print("离开了阻塞函数")
return 42
@asyncio.coroutine
def supervisor():
spinner = asyncio.ensure_future(spin('thinking'))
print('spinner object:',spinner)
result = yield from slow_function()#只要这个结束了就唤醒这个函数,传回来42参数。
print("发出暂停信号了")
spinner.cancel()#发出信号 Task 对象,协程要取消
return result
def main():#主线程,其他都是协程。,如果协程需要在一段时间内什么不都不做需要用asyncio.sleep(DELAY) 而不是time.sleep()
#除非想阻塞主线程,从而冻结整个事件循环,否则不要使用time.sleep()来休眠
loop = asyncio.get_event_loop()#事件循环。
# print("loop有什么属性",dir(loop))
print("loop 到底是什么",loop)
result = loop.run_until_complete(supervisor())
print("run_until_complete运行结束了")
loop.close()
print("发出提醒了")
print('Answer :',result)
-
使用
asynico.coroutine
装饰器不是强制要求,但是强烈建议这么做,因为这样能把普通函数和协程在一众函数中突出来。如果从中还未产生值,协程可能就被垃圾回收了,(这意味着有些操作没有完成),那就可以发出警告,这个装饰器是不会预激协程的。 -
两个代码之间的联系。Task对象和
Thread
对象几乎等效。Thread用于调用可调用对象,Task对象用于驱动协程。Task对象不由自已动手实例化,而是通过把协程传给,asyncio.async
(…)函数或者loop.create_task
()方法获取。 -
在线程版的supervisor函数中,slow_function()是一个普通函数,在异步版的
slow_funtion
()中,slow_function
()函数是协程,有yield from 驱动 。 -
supervisor
协程必须在main函数中由loop.run_until_complete()方法执行。 -
协程,无需保留锁,在多个线程之间同步操作,协程本身就会同步,因为在任意一个时刻只有一个协程运行,想要交出控制权时,可以使用yield 或yield from 把控制权交还给调度程序。这就是能安全取消协程的原因,按照定义,协程只能在暂停yield出取消,因此可以处理CanceledError 来执行清理操作。
-
从期物,任务和协程中产出。
asyncio.async(coror_or_future,*,loop=None)
这个函数统一了协程和期物,第一个参数可以是二者中的任何一个,如果是future或者task本身,那么久返回它本身,如果是协程的话,就利用loop.create_task
()创建Task对象,loop关键字是可选的,如果为None的话,那么就会调用loop.get_event_loop
()函数获取事件循环对象。
python3.5之后就是async + await
操作了
-
asyncio.wait
()协程的参数是一个由期物或协程构成的可迭代对象,wait会分别把各个协程包装进一个Task对象中,最终的结果是,wait处理的所有对象通过某种方式编程Future实例,wait_coro变量中存储的正是这种对象。为了驱动协程,我们把协程穿个loop.run_until_complete() -
在以前的版本中的
get_flags
函数用到的是requests库,执行的是阻塞型io操作,为了使用asyncio包,我们必须把每个访问网络的函数改成异步版本,使用yield from 处理网络操作,这样才能把控制权交给时间驱动循环,在get_flag函数中使用yield form 意味着它必须像协程那样驱动。 -
yield from foo 句法能防止句法阻塞,因为当前协程(即包含yield from 代码的委派生成器)暂停后,(也就是说正在请求,但是那边还没响应,现在完全暂停)就如告白了一个,还没回复,就马上先去告白下一个。 foo得到响应之后,也就是说有结果之后,把结果返回到暂停的协程将其恢复。yield from 链接多个协程最终必须由不是协程的调用方驱动,调用显式或者隐式(for 循环中,)在最外层委派生成器上用next(next()).send()的方法。、链条最内层的子生成器必须是简单的生成器,只使用yield或可迭代对象。
-
asyncio
包的api使用yield from 时要注意,这两点都成立,不过要注意一下细节。
我们编写的协程链条始终通过把最外层委派生成器传给asyncio包的某个api函数如(loop.run_until_complete)
驱动,也就是说,使用asyncio包时,我们不能通过调用next(…)和.send()方法去驱动,这一点由asyncio 包实现的事件驱动去实现。 -
我们编写的协程链条,最终通过yield from 把指责托给asyncio包中某个协程函数或者携程方法
(如yield from asyncio.sleep())
或者其他库中实现的高层协议的协程,如(aiohttp.request('GET',url))
。也就是说,最内层子生成器是库中真正执行IO操作的函数,而不是我们自己编写的函数。
概括起来就是:使用asyncio
包时,我们编写的异步代码中包含由asyncio本身驱动的协程,(即委派生成器),而生成器最终最终把指责委托给第三方库(aiohttp)或者asyncio包中的协程。这种方式相当于建立其了管道,让asyncio事件循环(通过我们编写的协程,驱动执行低层异步IO操作的库函数)
-
有两个方法能避免阻塞型函数中止应用程序的进程:1.在单独的线程中运行各个阻塞操作2.把每个阻塞型操作转换成非阻塞的异步调用。
-
使用Executor 防止阻塞 事件循环 ,在17-4中有ThreadPoolExecutor 可以在一个线程阻塞了后自动释放GIL,另一个线程可以继续执行,但是在asyncio 版本中,
save_flag
文件操作系统函数会阻塞,这个问题的解决办法就是使用事件循环对象的run in Executor 方法。asyncio 的事件循环背后维护这一个ThreadPoolExecutor 对象,我们可以把可调用对象发给他执行,(相当于解决了阻塞)(loop是公用一个的)loop = asyncio.get_event_loop() loop.run_in_executor(None,func,args) -
所谓回调地狱,就是不断在函数里面调用函数,层层嵌套,这样会损失上下文的信息。但是在协程里面只需要不断地yield ,让事件循环继续运行。准备好结果后,调用.send 方法激活协程。协程不能像普通函数那样调用,必须显示排定时间,创建事件循环,loop.create_task(thre_stages(request1))
#asyncio 和 aiohttp实现的异步下载脚本
import asyncio
import aiohttp
import time
import sys
import os
import requests
POP20_CC = ('CN IN US ID BR PK NG BD RU JP MX PH VN ET EG DE IR TR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'
DEST_DIR = 'downloads/'
@asyncio.coroutine
def save_flag(img,filename):
path = os.path.join(DEST_DIR,filename)
with open(path,'wb') as fp:
fp.write(img)
yield #也变成异步保存了,只要存在阻塞就往下执行。 协程间有通信。
print("保存一张图片成功了")
def show(text):
print(text,end=' ')
sys.stdout.flush()#这是什么意思?#遇到换行才会刷新缓冲,这样能显示进度一样的显示。过程
@asyncio.coroutine
def asy_request(url):
result = requests.get(url)
image = result.text
yield result,image
@asyncio.coroutine
def get_flag(cc):
url = '{}/{cc}/{cc}.gif'.format(BASE_URL,cc=cc.lower())
print("url 构建完成")
resp = yield from aiohttp.request('GET',url)#一阻塞就往下走,直到不可以再走位置,从输出可以看出,resp停得比较多
# resp,image = yield from asy_request(url)#一阻塞就往下走,直到不可以再走位置,从输出可以看出,resp停得比较多
#改成requests 后 这个get_flag函数就不能把主动权交还个事件循环了,而是只要调用这个get_flag 就必须要完全完成才能回去。
print("resp 停完成了")
image = yield from resp.read()
print("image 停完成了,直接返回image通信")
return image
@asyncio.coroutine
def download_one(cc):
image = yield from get_flag(cc)
print("返回图片完成了cc 是list")
show(cc)
yield from save_flag(image,cc.lower()+'.gif')
return cc
def download_many(cc_list):
loop = asyncio.get_event_loop()
print("loop 创建完成了")
to_do = [download_one(cc) for cc in sorted(cc_list)]#利用协程创造期物,生成器函数 协程 需要的时候才会用到
print("to do list 创建完成了")
wait_coro = asyncio.wait(to_do)#然后去启动期物,需要他了
res,_ = loop.run_until_complete(wait_coro)#得到结果
loop.close()
return len(res)
def main(download_many):
t0 = time.time()
count = download_many(POP20_CC)
elapsed = time.time() - t0
msg = '\n{} flags downloaded in {:.2f}s '
print(msg.format(count,elapsed))
本章介绍了python在并发编程中一种全新的方式,这种方式使用了yield from 、协程、期物、asyncio事件循环。
尽管有些函数会阻塞,但是为了程序的持续运行,有两种解决方案可用,一种是使用多个线程、一种是异步调用–后者以回调或者协程的方式实现。
其实,异步编程依赖于低层线程,(甚至内核级别的线程)。异步系统能避免用户级线程的开销,这是它能比多线程系统管理更多并发连接的主要原因。本章还说明了如何使用loop.run_in_executor方法把阻塞作业委托给线程池操作。还讨论了如何使用协程解决回调的主要问题,执行分成多步的上下文丢失以及缺少处理结果所需的上下文。
最后两个示例是使用asyncio包实现了TCP 和HTTP服务器,用于用户名搜索Unicode字符。在分析HTTP服务的最后,我们讨论了如何使用js服务端提供高并发支持的重要性。使用js,客户端可以按需发送小型请求,而不用发送较大的html
十九、动态属性和特性
19.1 python中如何设置动态属性?django如何注册模型?
-
在python中,数据的属性和处理数据的方法统称属性,其实方法只是可调用的属性。除了这二者之外,我们还可以创建特性(property) ,在不改变类接口的前提下,使用(读值和设值的方法)修改数据属性。除了特性,python还提供了丰富的API,用于控制属性访问的,当我们用.来访问属性的时候,obj.attartribute 会去访问python中的内置方法 __getattr__和__setattr__两个函数,当访问的属性不存在的时候,就会抛出异常。当无法使用常规方法获取属性的时候,(实例、类、超类中找不到指定的属性)解释器才会调用特殊的__getattr__属性。
-
处理无效属性名,当属性是python中的关键字或者保留字的时候,就会出错。
-
目前,我们只介绍了如何利用@property 装饰器实现一个只读的属性,接下来我们利用它来创建一个可读可写的特性。
-
类属性是不会覆盖实例属性,实例属性也不会覆盖类属性,但是特性会覆盖实例属性。
-
本节的主要观点就是,obj.attr 这样的表达式不会从obj开始寻找attr,而是从obj.__class__开始,而且,仅当类中没有特性名为attr的特性时,python才会在obj实例中寻找。这条规则不仅适用于特性,还适用于整一个描述符–覆盖描述符。
-
特性的文档,控制台help()或IDE等工具显示特性的文档时,活动特性的__doc__属性读取。
class LineItem:
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtoal(self):
return self.weight * self.price
@property
def weight(self):
return self.__weight
@weight.setter
def weight(self,value):
if value > 0:
self.__weight = value
else:
raise ValueError('value must be > 0')
##property 的使用
# property(fget = None,fset=None,fdel=None,doc=None)#构造一个特性就是这样的
class LineItem:
def __init__(self,description,weight,price):
self.description = description
self.weight = weight#已经变成特性,也就是不可以调用方法了
self.price = price
def subtotal(self):
print("开始求值")
return self.weight * self.price
def set_weight(self,value):
print("开始设值了")
if value > 0:
self.__weight = value
else:
raise ValueError(' value must be > 0')
def get_weight(self):
print("开始获取值了")
return self.__weight
weight = property(get_weight,set_weight)#经典设置特性的形式 在有些时候比装饰器句法好,类属性
- 影响属性处理方式的特殊属性
__class__
对象所属类的引用,即obj.__class__
与type(obj)
的作用相同,python
中的某些特殊方法,例如__getattr__
,只在对象的类中寻找,不找实例中寻找
__dict__
一个映射,存储对象或类的可写属性。有__dict__
属性的对象,任何时候它都可以设置新的属性。。
__slots__
是类中的一个属性,限制了实例中允许的属性,如果__slots__
中没有 __dict__
属性的话,那么该类的实例就没有__dict__
属性。
- 处理属性的内置函数。下述五个操作符对属性读、写、内省操作
dir(obj)
–不会列出 __dict__
不会列出__bases__
__name__
mro
getattr(obj,name,default)
如果没有该属性就返回default值
hasattr(obj,name)
判断某个对象是否有某个对象 据说是利用getattr 抛出异常来判断的
setattr(obj,name,value)
设置新属性的值。或者更改某属性的值。
vars(obj)
如果没有指定参数,那么该函数的作用于local
()一样,返回本地作用域,如果指定了相应的对象,返回__dict__
属性,前提是__slots__
有该属性。
8.处理属性的特殊方法
__delattr__
(obj,name)
只要使用del语句删除属性,就会调用这个方法,del obj.attr 触发 Class.__delattr__(obj,'attr')
__dir__(self)
dir(obj)就会触发 Class.dir(obj)列出属性
__getattr__(self,name)
仅当获取属性失败的时候,搜索obj,Class
,和超类的属性之后才调用。obj.no_such_attr\ getattr(obj,name)
才会触发Class.__getattr__(obj,'no_such_attr')
__getattribute__
(self,name,value)
尝试获取指定属性和方法的时候总会调用这个函数,不过是获取特殊方法和特殊属性的时候除外。调用__getattribute__
抛出AttaributeError
时才会调用 __getattr__
方法。为了不导致无限递归,__getattribute__
方法的实现要用super().__getattribute__(self,name)
才行。
__setattr__(self,name,value)
尝试指定属性的时候,都会调用这个方法,例如obj.attr = 42 或 setattr(obj,'attr',42)
时,都会调用(Class.setattr(name,value)
本章介绍了动态编程和动态属性,利用例子演示了如何设置动态属性,以及读、写、删除属性时所印象到的方法。
还介绍了python的特性,经典形式以及装饰器的形式。
#描述符类
class Descriptor():
def __init__(self, x):
self.x = x
def __get__(self, instance, owner):
print("实现描述符的get")
return self.x
def __set__(self, instance, value):
print("实现描述符的set")
self.x = value
def __delete__(self, instance):
print("实现描述符的delete")
del self.x
class A():
x = Descriptor(10)
a = A()
a.x
A.__dict__["x"].__get__(None,A)
a.x = 8
del a.x
二十、属性描述符
学会描述符之后,不仅有更多的工具集可用,还会对Python的运作方式更深入了解,并由衷地赞叹Python设计的优雅。–python核心开发专家
描述符是对多个属性运用相同存取的一个逻辑方式,例如 Django 和SQLchemy 等ORM中的字段类型是描述符。把数据库记录中字段的数据与python对象的属性对应起来。
描述符是实现了特定协议的类,这个协议包括__get__
__set__
__delete__
。property
类就是实现了完整的描述符协议。通常,可以只实现这部分协议。其实,我们在真实的代码当中大多只看到__set__
__get__
方法
除了特性之外,使用了描述符的Python功能还有classmethod
staticmethod
#定义描述符类
class Quantity:
def __init__(self,storge_name):
self.storge_name = storge_name
def __set__(self,instance,value):
if value > 0:
instance.__dict__[self.storge_name] = value#这里必须使用dict属性,如果使用直接赋值的话
#或者内置的settr 方法的话又会触发__set__方法 也就是说 instance.storge_name = 会不断循环
else:
raise ValueError('value must be > 0')
#定义托管类
class LineItem:
weight = Quantity('weight')#所有属性都交由其描述
price = Quantity('price')
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
## 自动获取存储属性的名称 从而自动创建描述符实例
class Quantity:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storge_name = '_{}#{}'.format(prefix,index)
cls.__counter += 1
def __get__(self,instance,owner):
return getattr(instance,self.storge_name)
def __set__(self,instance,value):
if value > 0:
setattr(instance,self.storge_name,value)
else:
raise ValueError('value must > 0')
class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self,description,weight,price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
20.1 什么是描述类?有设么用?查找顺序有什么优先的吗?什么是覆盖描述符?
-
没有
__get__
的覆盖型描述符:只有__set__
操作的描述符 ,__set__
了一次之后,之后的描述符都将被遮盖。意思是当__dict__
属性中有同名属性符的时候,__set__
仍然会被触发,但是读取已经读取不到那个set的值了。(其实就是解析顺序的问题),要不要找data descriptor 。找不找得到。(1.20周记资源有链接)http://blog.csdn.net/huithe/article/details/7484606 -
没有实现
__set__
方法的非覆盖描述符,也就是non-descriptor 他的寻找次序是最低的。 -
在类中覆盖描述符是因为访问属性的解析顺序问题。直接赋值后就不是描述符状态了自然而然不会触发
__get__
__set__
方法 -
赋值的时候主要是实例的类属性进行调用描述符,访问的时候主要是查找类属性的描述符。
-
方法是描述符、函数都是非数据描述符
-
内置的property创建的其实是数据型描述符,set get 方法都实现了。,也会抛出can’t set attribute 实现只读属性。只读属性必须要设置set方法,并抛出异常,不然的话就是非数据型描述符,搜索优先级很低,就会被覆盖。
-
静态方法作为普通函数一样,实际上可以是在类中共享私有变量的特点。
理解本章的关键核心就是理解描述符类只能作为托管类的类属性存在,这是因为python属性访问的时候5个步骤,寻找数据型关符等等操作影响的结果。描述符可以为我们定义数据例如Django里面的Model提供大大的方便
只读、可读写、不可覆盖等等特性。还有property的封装实现。
二十一章 类元编程
1.除非开发框架,否则不要写元类–然而,为了寻找乐趣,或者练习相关概念,可以这么做。
2.类工厂函数 collections.namedtuple
我们把一个类名和几个属性名传给这个函数,它会创建一个tuple
子类,并提供了良好的repr展示函数。
-
type()
函数构造方法,构造新类 第一个参数是name,bases,dict 最后一个参数是映射,新的属性和方法 -
定制类装饰器实现描述符名称定制
-
头一次看github代码,这代码写得巨好呀!
https://github.com/fluentpython/example-code/blob/master/21-class-metaprog/bulkfood/model_v6.py#L24:7 -
类装饰器对子类没有影响,在初始化的时候,导入时、运行时。。导入时会定义类的主体包括方法,包括嵌套类的主体
运行时会由上而下地运行。 -
元类会深深影响子类,特别是元类初始化的时候,会替换子类的方法,所以比类装饰器带来的影响久远。
-
如前所述,type构造方法及元类的
__new__
、__init__
方法都会收到要计算的定义体中。 -
__prepare__
方法会在__new__
方法之前,__prepare__
方法返回的映射会传到new\在传到init的最后一个参数。属性的绑定