流畅的python
看书时觉得需要的就记录了下来。python的版本有点老了,目前参考价值不大。
1、数据模型
1.1、展示魔术方法
1、本例功能:通过例子展示__getitem__和__len__两个特殊方法。
2、本例代码
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]
3、本例扩展
namedtupele 用于构建只有少数属性但是没有方法的对象,比如数据库条目
Python元组的升级版本 -- namedtuple(具名元组)
因为元组的局限性:不能为元组内部的数据进行命名,所以往往我们并不知道一个元组所要表达的意义,所以在这里引入了 collections.namedtuple 这个工厂函数,来构造一个带字段名的元组。具名元组的实例和普通元组消耗的内存一样多,因为字段名都被存在对应的类里面。这个类跟普通的对象实例比起来也要小一些,因为 Python 不会用 __dict__ 来存放这些实例的属性。
namedtuple 对象的定义如以下格式:
collections.namedtuple(typename, field_names, verbose=False, rename=False)
4、参考
https://www.runoob.com/note/25726
1.2、简单二维向量
1、本例功能
一个简单的二维向量的类
2、代码
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)
2、数据结构
2.1、内置序列类型
容器序列
list、 tuple 和collections.deque 这些序列存放不同类型的数据
扁平序列
str、bytes、bytearry、memoryview 和array.array 这类序列只能容纳一种类型
可变序列
list bytearray array.array collections.deque 和 memoryview
不可变序列
tuple str bytes
2.2、列表推导和生成器表达式
1、列表推导是构建列表的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列
2、虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里面。
3、生成器表达式的语法跟推导式差不多,只不过把方括号换成圆括号而已。
4、比较代码
import timeit
TIMES = 10000
SETUP = """
symbols = '$¢£¥€¤'
def non_ascii(c):
return c > 127
"""
def clock(label, cmd):
res = timeit.repeat(cmd, setup=SETUP, number=TIMES)
print(label, *('{:.3f}'.format(x) for x in res))
clock('listcomp :', '[ord(s) for s in symbols if ord(s) > 127]')
clock('listcomp + func :', '[ord(s) for s in symbols if non_ascii(ord(s))]')
clock('filter + lambda :', 'list(filter(lambda c: c > 127, map(ord, symbols)))')
clock('filter + func :', 'list(filter(non_ascii, map(ord, symbols)))')
5、扩展
timeit
测量小代码片段的执行时间
https://docs.python.org/zh-cn/3/library/timeit.html
6、用生成器表达式初始化元组和数组
symbols = '$¢£¥€¤'
tuple(ord(symbol) for symbol in symbols)
(36, 162, 163, 165, 8364, 164)
(ord(symbol) for symbol in symbols)
<generator object <genexpr> at 0x7fe8bb4d4570>
array.array('I', (ord(symbol) for symbol in symbols))
1)如果生成器表达式是一个函数调用过程中唯一参数,那么不需要额外再用括号把它围起来
2)array的构造方法需要两个参数,因此括号是必须的,array构造方法的第一个参数制定了数组中数字的存储方式。
2.3、元组和记录
元组其实是对数据的记录:元组中每个元素都存放了记录中的一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
如果只是把元组理解成不可变的列表,那它所包含的元素的总数和他们的位置似乎就变得可有可无了。
2.3.3嵌套元组拆包
代码
metro_areas = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), # <1>
('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: # <2>
if longitude <= 0: # <3>
print(fmt.format(name, latitude, longitude))
2.3.4 具名元组
collections.namedtuple是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类。
用namedtuple构建的类的示例所消耗的内存跟元组是一样的,因为字段名都被存在对应的类里面。这个实例跟普通的对象实例比会小一点,因为python不会用__dict__来存放这些实例的属性。
代码可以参考1.1
2.4、切片
切片
list tuple 字符串这类序列类型都支持切片
试炼
l = [10,20,30,40,50,60]
l[:3]
[10, 20, 30]
l[3:]
[40, 50, 60]
2.4.2 对象切片
s='bicycle'
s[::3]
'bye'
s[::1]
'bicycle'
s[::-1]
'elcycib'
2.4.4 给切片赋值
l=list(range(10))
l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l[2:5]
[2, 3, 4]
l[2:5]=[20,30]
l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
2.5、序列
序列
2.5 对序列使用*和+
l=[1,2,3]
l*5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
*和+都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列
2.6 序列的增量赋值
增量赋值运算符+=和*=的表现取决于他们的第一个操作对象
+= 背后的特殊方法是__iadd__(就地加法),同时对可变序列(list bytearray array.array)来说,就会就地改动,就像调用了a.extend(b)。
但是如果没有实现__iadd__, a+=b这个表达式就会变成a=a+b。
2.7 list.sort 和内置的函数sorted
list.sort方法会就地排序列表,一般就地排序的返回的就是none
sorted 会新建一个列表作为返回值,可以接受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器。
2.8 用bisect来管理已排序的队列
bisect模块包含两个主要函数,bisect和insort,两个函数都通过二分查找算法查找和插入函数
代码参考
import bisect
import random
SIZE = 7
random.seed(1729)
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE*2)
bisect.insort(my_list, new_item)
print('%2d ->' % new_item, my_list)
=======================================================================
# BEGIN BISECT_DEMO
import bisect
import sys
HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]
ROW_FMT = '{0:2d} @ {1:2d} {2}{0:<2d}'
def demo(bisect_fn):
for needle in reversed(NEEDLES):
position = bisect_fn(HAYSTACK, needle) # <1>
offset = position * ' |' # <2>
print(ROW_FMT.format(needle, position, offset)) # <3>
if __name__ == '__main__':
if sys.argv[-1] == 'left': # <4>
bisect_fn = bisect.bisect_left
else:
bisect_fn = bisect.bisect
print('DEMO:', bisect_fn.__name__) # <5>
print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
demo(bisect_fn)
# END BISECT_DEMO
2.6、当列表不是首选时
当列表不是首选时
2.9.1 数组
如果我们需要一个只包含数组的列表,那么array.array比list更高效
2.9.2 内存视图
memoryview是一个内置类,让用户不复制内容的情况下操作同一个数组的不同切片。这个功能在处理大型数据集合的时候非常重要。
2.9.3 numpy和scipy
2.9.4 双向队列和其他形式的队列
from collections import deque
deque(range(10), maxlen=10)
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
dq = deque(range(10), maxlen=10)
3、字典和集合
3.1、散列的定义
散列的定义
1、官网散列的定义:https://docs.python.org/3/glossary.html#term-hashable
如果一个对象具有在其生命周期内永不改变的哈希值(它需要一个方法),并且可以与其他对象进行比较(它需要一个方法),则该对象是可哈希的。比较相等的可散列对象必须具有相同的散列值。__hash__()__eq__()
可哈希性使对象可用作字典键和集合成员,因为这些数据结构在内部使用哈希值。
Python 的大部分不可变内置对象都是可散列的;可变容器(例如列表或字典)不是;不可变容器(例如元组和冻结集)只有在其元素可哈希时才可哈希。默认情况下,作为用户定义类实例的对象是可散列的。它们都比较不相等(除了它们自己),它们的哈希值来自它们的id().
2、作者的定义
原子不可变数据类型(str,bytes和数值类型)都是可散列类型,frozenset 也是可散列的。元组只有当包含的所有元素都是可散列的情况下,他才是散列的。
如果元组里面的元素是其他可变类型的引用就不是散列的。
3、字典推导式
{a:b for a,b in ab}
4、常见的映射方法
dict defaultdict OrderedDict,后面两个数据类型是dict的变种
3.2、用setdefault处理找不到的键
用setdefault处理找不到的键
1、效率较低的处理方式,通过在get不到的值时设置默认值
程序功能:从索引中获取单词出现的频率信息,并把它们写进对应的列表里
# adapted from Alex Martelli's example in "Re-learning Python"
# http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf
# (slide 41) Ex: lines-by-word file index
# BEGIN INDEX0
"""Build an index mapping word -> list of occurrences"""
import sys
import re
WORD_RE = re.compile('\w+')
index = {}
with open(sys.argv[1], encoding='utf-8') as fp:
for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start()+1
location = (line_no, column_no)
# this is ugly; coded like this to make a point
occurrences = index.get(word, []) # <1>
occurrences.append(location) # <2>
index[word] = occurrences # <3>
# print in alphabetical order
for word in sorted(index, key=str.upper): # <4>
print(word, index[word])
# END INDEX0
2、用setdefault
# adapted from Alex Martelli's example in "Re-learning Python"
# http://www.aleax.it/Python/accu04_Relearn_Python_alex.pdf
# (slide 41) Ex: lines-by-word file index
# BEGIN INDEX
"""Build an index mapping word -> list of occurrences"""
import sys
import re
WORD_RE = re.compile('\w+')
index = {}
with open(sys.argv[1], encoding='utf-8') as fp:
for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start()+1
location = (line_no, column_no)
index.setdefault(word, []).append(location) # <1>
# print in alphabetical order
for word in sorted(index, key=str.upper):
print(word, index[word])
# END INDEX
3、扩展
enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
3.3、给字典中的键赋予默认值
给字典中的键赋予默认值
1)通过defaultdict这个类型而不是普通的dict
利用defaultdict实例而不是setdefault方法
index = collections.defaultdict(list)
2)自己定义一个dict的子类,然后在子类中实现__missing__方法。
4、--
5、一等函数
5.1、函数的介绍
在python中,所有函数都是一等对象
接受函数为参数,或者把函数作为结果返回的函数是高阶函数
最为人熟知的高阶函数有map,filter,reduce和apply(python3中以移除)
map、filter和reduce的现代替代品
列表推导式和生成器表达式具有map和filter两个函数的功能。
匿名函数
lambda关键字在python表达式内创建匿名函数
可调用对象
用户定义的函数
用def语句或lambda表达式创建
内置函数
使用c实现的函数,如果len或time.strftime
内置方法
使用c实现的方法,如dict.get
方法
在类的定义体中定义的函数
类
类的实例
如果类定义了__call__方法,那么它的实例可以作为函数调用
生成器函数
使用率yield关键字的函数或方法。返回的是生成器对象
5.2、用户自定义的可调用的类型
用户自定义的可调用的类型
"""
# BEGIN BINGO_DEMO
>>> bingo = BingoCage(range(3))
>>> bingo.pick()
1
>>> bingo()
0
>>> callable(bingo)
True
# END BINGO_DEMO
"""
# BEGIN BINGO
import random
class BingoCage:
def __init__(self, items):
self._items = list(items) # <1>
random.shuffle(self._items) # <2>
def pick(self): # <3>
try:
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage') # <4>
def __call__(self): # <5>
return self.pick()
# END BINGO
5.3、函数注解
函数注解
"""
>>> clip('banana ', 6)
'banana'
>>> clip('banana ', 7)
'banana'
>>> clip('banana ', 5)
'banana'
>>> clip('banana split', 6)
'banana'
>>> clip('banana split', 7)
'banana'
>>> clip('banana split', 10)
'banana'
>>> clip('banana split', 11)
'banana'
>>> clip('banana split', 12)
'banana split'
"""
# BEGIN CLIP_ANNOT
def clip(text:str, max_len:'int > 0'=80) -> str: # <1>
"""Return text clipped at the last space before or after max_len
"""
end = None
if len(text) > max_len:
space_before = text.rfind(' ', 0, max_len)
if space_before >= 0:
end = space_before
else:
space_after = text.rfind(' ', max_len)
if space_after >= 0:
end = space_after
if end is None: # no spaces were found
end = len(text)
return text[:end].rstrip()
# END CLIP_ANNOT
有注解的函数声明
函数声明中的各个参数可以在:之后增加注解表达式
注解中最常用的类型是类(str或int)和字符串(‘int > 0’)
注解不会做任何处理,只是存储在函数的__annotations__属性(字典中)
5.4、支持函数式编程的包
支持函数式编程的包
python的目标不是变成函数式编程语言,但是得益于operator和functools等包的支持,函数式编程也是信手拈来
opertaor模块
from tunctools import reduce
from operator import mul
def fact(n):
return reduce(mul, range(1, n+1))
functools.partial冻结参数
6、装饰器和闭包
6.1、装饰器基础
装饰器是可调用的对象,其参数是另一个函数。 nonlocal
6.2、导入时和运行时
# BEGIN REGISTRATION
registry = [] # <1>
def register(func): # <2>
print('running register(%s)' % func) # <3>
registry.append(func) # <4>
return func # <5>
@register # <6>
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3(): # <7>
print('running f3()')
def main(): # <8>
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main() # <9>
# END REGISTRATION
函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。导入时和运行时是有区别的。可以执行一下试试。
7、对象引用、可变性和垃圾回收
标识、相等性和别名
abc=123
kk=abc
kk is abc
Out[1]: True
id(abc)
Out[2]: 4543210432
id(kk)
Out[3]: 4543210432
上面ABC,kk指代同一个对象
在==和is之间选择
==比较的两个对象的值
is比较的是对象的标识
最常使用is检查变量绑定值是不是None
x is None
x is not None
is运算符比==快
元组的相对不可变性
元组不可变性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。元祖中不可变的是元素的标识。
默认做浅复制
复制了最外层容器,副本中的元素是源容器中元素的引用
深复制
副本不共享内部对象的引用
del和垃圾回收
del语句删除名称而不是对象。del命令可能会导致对象被当做垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用。
在cpython中,垃圾回收的主要算法是引用计数,当引用计数归0,对象立即被销毁
14、迭代器和生成器
14.1、什么是迭代器
所有的生成器都是迭代器
迭代器由于从集合中取出元素,生成器用于凭空生成元素。
如果对象实现了能返回__iter__方法,那么对象就是可迭代的,序列都是可以迭代的。
实现了__getitem__方法,而且其参数是从零开始的索引,这种对象也可以迭代。
python从可迭代的对象中获取迭代器
"""
Sentence: iterate over words using the Iterator Pattern, take #1
WARNING: the Iterator Pattern is much simpler in idiomatic Python;
see: sentence_gen*.py.
"""
# BEGIN SENTENCE_ITER
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self): # <1>
return SentenceIterator(self.words) # <2>
class SentenceIterator:
def __init__(self, words):
self.words = words # <3>
self.index = 0 # <4>
def __next__(self):
try:
word = self.words[self.index] # <5>
except IndexError:
raise StopIteration() # <6>
self.index += 1 # <7>
return word # <8>
def __iter__(self): # <9>
return self
# END SENTENCE_ITER
def main():
import sys
import warnings
try:
filename = sys.argv[1]
word_number = int(sys.argv[2])
except (IndexError, ValueError):
print('Usage: %s <file-name> <word-number>' % sys.argv[0])
sys.exit(1)
with open(filename, 'rt', encoding='utf-8') as text_file:
s = Sentence(text_file.read())
for n, word in enumerate(s, 1):
if n == word_number:
print(word)
break
else:
warnings.warn('last word is #%d, "%s"' % (n, word))
if __name__ == '__main__':
main()
迭代器应该实现__next__和__iter__两个方法
可迭代的对象有个__iter__方法,每次都实例化一个新的迭代器;
迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身.
迭代器可以迭代,但是可迭代的对象不是迭代器
14.2、生成器
只要python函数的定义体中有yield关键字,该函数就是生成器函数
"""
Sentence: iterate over words using a generator function
"""
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for word in self.words: # <1>
yield word # <2>
return # <3>
# done! <4>
使用itertools模块生成等差数列
gen=itertools.takewhile(lambda n:n<3, itertools.count(1, .5))
itertools.count会把内存干爆
标准库中的生成器函数
内置的和itertools 和functools中的
pyhon3.3出现的新语法,yield from
可迭代的规约函数,返回单个结果
all
any
max
min
reduce
sum
iter函数
15、上下文管理器和else
15.1、介绍
上下文管理器对象存在的目的是管理with语句,就像迭代器的存在是为了管理for语句一样
finally 子句的代码通常用于释放重要的资源,或者还原临时变更的状态。witch语句目的就是简化try/finally模式
上下文管理器协议包含__enter__和__exit__两个方法。with语句开始运行时,会在上下文管理器对象上调用__enter__方法。with语句运行结束后,
会在上下文管理器对象上调用__exit__方法。
15.2、通过类实现上下文管理器
"""
A "mirroring" ``stdout`` context.
While active, the context manager reverses text output to
``stdout``::
# BEGIN MIRROR_DEMO_1
>>> from mirror import LookingGlass
>>> with LookingGlass() as what: # <1>
... print('Alice, Kitty and Snowdrop') # <2>
... print(what)
...
pordwonS dna yttiK ,ecilA # <3>
YKCOWREBBAJ
>>> what # <4>
'JABBERWOCKY'
>>> print('Back to normal.') # <5>
Back to normal.
# END MIRROR_DEMO_1
This exposes the context manager operation::
# BEGIN MIRROR_DEMO_2
>>> from mirror import LookingGlass
>>> manager = LookingGlass() # <1>
>>> manager
<mirror.LookingGlass object at 0x2a578ac>
>>> monster = manager.__enter__() # <2>
>>> monster == 'JABBERWOCKY' # <3>
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager
>ca875a2x0 ta tcejbo ssalGgnikooL.rorrim<
>>> manager.__exit__(None, None, None) # <4>
>>> monster
'JABBERWOCKY'
# END MIRROR_DEMO_2
The context manager can handle and "swallow" exceptions.
# BEGIN MIRROR_DEMO_3
>>> from mirror import LookingGlass
>>> with LookingGlass():
... print('Humpty Dumpty')
... x = 1/0 # <1>
... print('END') # <2>
...
ytpmuD ytpmuH
Please DO NOT divide by zero!
>>> with LookingGlass():
... print('Humpty Dumpty')
... x = no_such_name # <1>
... print('END') # <2>
...
Traceback (most recent call last):
...
NameError: name 'no_such_name' is not defined
# END MIRROR_DEMO_3
"""
# BEGIN MIRROR_EX
class LookingGlass:
def __enter__(self): # <1>
import sys
self.original_write = sys.stdout.write # <2>
sys.stdout.write = self.reverse_write # <3>
return 'JABBERWOCKY' # <4>
def reverse_write(self, text): # <5>
self.original_write(text[::-1])
def __exit__(self, exc_type, exc_value, traceback): # <6>
import sys # <7>
sys.stdout.write = self.original_write # <8>
if exc_type is ZeroDivisionError: # <9>
print('Please DO NOT divide by zero!')
return True # <10>
# <11>
# END MIRROR_EX
15.3、通过生成器实现上下文管理器
contextlib模块中的实用工具
closing
suppress
@contextmanager
ContextDecorator
ExitStack
使用@contextmanager,不用编写完整的类,只需要实现一个带有yield语句的生成器,生成想让__enter__方法返回的值,yield语句之前的(enter),之后的(exit)
"""
A "mirroring" ``stdout`` context manager.
While active, the context manager reverses text output to
``stdout``::
# BEGIN MIRROR_GEN_DEMO_1
>>> from mirror_gen import looking_glass
>>> with looking_glass() as what: # <1>
... print('Alice, Kitty and Snowdrop')
... print(what)
...
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
>>> what
'JABBERWOCKY'
# END MIRROR_GEN_DEMO_1
This exposes the context manager operation::
# BEGIN MIRROR_GEN_DEMO_2
>>> from mirror_gen import looking_glass
>>> manager = looking_glass() # <1>
>>> manager # doctest: +ELLIPSIS
<contextlib._GeneratorContextManager object at 0x...>
>>> monster = manager.__enter__() # <2>
>>> monster == 'JABBERWOCKY' # <3>
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager # doctest: +ELLIPSIS
>...x0 ta tcejbo reganaMtxetnoCrotareneG_.biltxetnoc<
>>> manager.__exit__(None, None, None) # <4>
>>> monster
'JABBERWOCKY'
# END MIRROR_GEN_DEMO_2
"""
# BEGIN MIRROR_GEN_EX
import contextlib
@contextlib.contextmanager # <1>
def looking_glass():
import sys
original_write = sys.stdout.write # <2>
def reverse_write(text): # <3>
original_write(text[::-1])
sys.stdout.write = reverse_write # <4>
yield 'JABBERWOCKY' # <5>
sys.stdout.write = original_write # <6>
# END MIRROR_GEN_EX
基于生成器的上下文管理器,而且实现了异常处理
"""
A "mirroring" ``stdout`` context manager.
While active, the context manager reverses text output to
``stdout``::
# BEGIN MIRROR_GEN_DEMO_1
>>> from mirror_gen import looking_glass
>>> with looking_glass() as what: # <1>
... print('Alice, Kitty and Snowdrop')
... print(what)
...
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
>>> what
'JABBERWOCKY'
# END MIRROR_GEN_DEMO_1
This exposes the context manager operation::
# BEGIN MIRROR_GEN_DEMO_2
>>> from mirror_gen import looking_glass
>>> manager = looking_glass() # <1>
>>> manager # doctest: +ELLIPSIS
<contextlib._GeneratorContextManager object at 0x...>
>>> monster = manager.__enter__() # <2>
>>> monster == 'JABBERWOCKY' # <3>
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager # doctest: +ELLIPSIS
>...x0 ta tcejbo reganaMtxetnoCrotareneG_.biltxetnoc<
>>> manager.__exit__(None, None, None) # <4>
>>> monster
'JABBERWOCKY'
# END MIRROR_GEN_DEMO_2
The context manager can handle and "swallow" exceptions.
The following test does not pass under doctest (a
ZeroDivisionError is reported by doctest) but passes
if executed by hand in the Python 3 console (the exception
is handled by the context manager):
# BEGIN MIRROR_GEN_DEMO_3
>>> from mirror_gen import looking_glass
>>> with looking_glass():
... print('Humpty Dumpty')
... x = 1/0 # <1>
... print('END') # <2>
...
ytpmuD ytpmuH
Please DO NOT divide by zero!
# END MIRROR_GEN_DEMO_3
>>> with looking_glass():
... print('Humpty Dumpty')
... x = no_such_name # <1>
... print('END') # <2>
...
Traceback (most recent call last):
...
NameError: name 'no_such_name' is not defined
"""
# BEGIN MIRROR_GEN_EXC
import contextlib
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
msg = '' # <1>
try:
yield 'JABBERWOCKY'
except ZeroDivisionError: # <2>
msg = 'Please DO NOT divide by zero!'
finally:
sys.stdout.write = original_write # <3>
if msg:
print(msg) # <4>
# END MIRROR_GEN_EXC
16、协程-老版本
16.1、协程的4个状态
协程的4个状态
当前状态可以用inspect.getgeneratorstate()
GEN_CREATED = 'GEN_CREATED' 等待开始执行
GEN_RUNNING = 'GEN_RUNNING' 解释器正在执行
GEN_SUSPENDED = 'GEN_SUSPENDED' 在yield表达式处暂停
GEN_CLOSED = 'GEN_CLOSED' 执行结束
"""
A coroutine to compute a running average
# BEGIN CORO_AVERAGER_TEST
>>> coro_avg = averager() # <1>
>>> next(coro_avg) # <2>
>>> coro_avg.send(10) # <3>
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0
# END CORO_AVERAGER_TEST
"""
# BEGIN CORO_AVERAGER
def averager():
total = 0.0
count = 0
average = None
while True: # <1>
term = yield average # <2>
total += term
count += 1
average = total/count
# END CORO_AVERAGER
预激协程装饰器,调用send之前,一定要先调用next()。为了简化协程的方法,有时会使用一个预激装饰器。
# BEGIN CORO_DECO
from functools import wraps
def coroutine(func):
"""Decorator: primes `func` by advancing to first `yield`"""
@wraps(func)
def primer(*args,**kwargs): # <1>
gen = func(*args,**kwargs) # <2>
next(gen) # <3>
return gen # <4>
return primer
# END CORO_DECO
预激之后的
# BEGIN DECORATED_AVERAGER
"""
A coroutine to compute a running average
>>> coro_avg = averager() # <1>
>>> from inspect import getgeneratorstate
>>> getgeneratorstate(coro_avg) # <2>
'GEN_SUSPENDED'
>>> coro_avg.send(10) # <3>
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0
"""
from coroutil import coroutine # <4>
@coroutine # <5>
def averager(): # <6>
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
# END DECORATED_AVERAGER
16.2、终止协程和异常处理
在协程内部没有处理异常,协程会终止。如果试图重新激活协程,会抛出stopIteration的异常 终止协程的一种方式:发送某个哨符值。内置的None和Ellipsis等常量经常用作哨符值
16.3、yield from
可以用于简化for循环中的yield表达式。
def gen():
yield from 'ab'
yield from range(1,2)
list(gen())
yield from x 表达式对x对象所做的第一件事是,调用iter(x),从中获取迭代器。x可以是任何可迭代的对象
17、future处理并发
17.1、多线程和多进程
在io密集型应用中,如果代码写的正确,使用线程或者asyncio吞吐量都会比依序执行的代码高很多
concurrent.futures
future封装待完成的操作,可以放入队列,完成的状态可以查询,得到结果后可以获取结果。result()会阻塞调用方所在的线程,知道有结果可返回。可以
增加timeout参数,asyncio.Future.result不支持设置超时时间
"""Download flags of top 20 countries by population
ThreadPoolExecutor version 2, with ``as_completed``.
Sample run::
$ python3 flags_threadpool.py
BD retrieved.
EG retrieved.
CN retrieved.
...
PH retrieved.
US retrieved.
IR retrieved.
20 flags downloaded in 0.93s
"""
from concurrent import futures
from flags import save_flag, get_flag, show, main
MAX_WORKERS = 20
def download_one(cc):
image = get_flag(cc)
show(cc)
save_flag(image, cc.lower() + '.gif')
return cc
# BEGIN FLAGS_THREADPOOL_AS_COMPLETED
def download_many(cc_list):
cc_list = cc_list[:5] # <1>
with futures.ThreadPoolExecutor(max_workers=3) as executor: # <2>
to_do = []
for cc in sorted(cc_list): # <3>
future = executor.submit(download_one, cc) # <4>
to_do.append(future) # <5>
msg = 'Scheduled for {}: {}'
print(msg.format(cc, future)) # <6>
results = []
for future in futures.as_completed(to_do): # <7>
res = future.result() # <8>
msg = '{} result: {!r}'
print(msg.format(future, res)) # <9>
results.append(res)
return len(results)
# END FLAGS_THREADPOOL_AS_COMPLETED
if __name__ == '__main__':
main(download_many)
executor.submit方法排定可调用对象的执行时间,然后返回一个future
to_do存储各个future,后面传递给as_completed,as_completed函数在future运行结束后产出future。
阻塞型io和GIL
严格来说,我们目前测试的并发脚本都不能并行下载。使用concurrent.futures实现的那两个实例受GIL的限制。
cpython解释器本身就不是线程安全的,所以有GIL,一次只允许使用一个线程执行python字节码。一个python经常通常不能同时使用多个cpu核心。
标准库中所有执行阻塞型io操作的函数,在等待操作系统返回结果时都会释放GIL。这意味着python可以在这个层次上使用多线程。一个python线程在等待网络
响应时,阻塞型io函数会释放GIL,再运行一个线程。
通过ProcessPoolExecutor可以做cpu密集型处理,可以绕开GIL
18、asyncio
18.1、从属线程
1、从属线程的概念
在Python中,从属线程也称为守护线程(daemonthread)。与普通线程不同,从属线程会在主线程结束时自动退出,并且从属线程无法阻止程序的退出。从属线程通常用于执行一些后台任务
import threading
import time
def worker():
"""线程执行的任务"""
while True:
print('Worker')
time.sleep(1)
# 创建线程
t = threading.Thread(target=worker)
# 将线程设置为从属线程
t.setDaemon(True)
# 启动线程
t.start()
# 主线程休眠5秒钟
time.sleep(5)
print('Main thread exiting')
这个例子创建了一个名为t的线程,并将其设置为从属线程。然后,主线程休眠5秒钟后退出。由于t是从属线程,所以即使t在后台执行,程序也会在主线程退出时结束。
需要注意的是,从属线程通常不能访问共享资源,因为它们可能在主线程退出之前就被强制结束。如果需要在从属线程中使用共享资源,可以考虑使用线程同步机制来保证数据的正确性。
上述从属线程的概念从chatGpt中查询得来
https://blog.csdn.net/weixin_47831992/article/details/127387175
在不设置守护线程或者从属线程的情况下可以设置线程同步,让主线程阻塞。
18.2、线程和协程的对比
通过threading实现线程和通过asyncio实现协程
asyncio.task对象是用于驱动协程,thread对象用于调用可调用的对象
线程:
因为调度程序任何时候都能中断线程。必须记住保留锁,去保护程序中的重要部分,防止多步操作在执行的过程中中断,防止数据处于无效状态。
协程:
而协程默认会做好全方位保护,以防止中断。
对于协程来说,无需保留锁,在多个线程之间同步操作,协程自身就会同步,因为在任意时刻只有一个协程运行。
想要交出控制权时,可以使用yield或者yield from把控制权交给调度程序。(新版本的应该不是通过这两个方式来交出控制权)
18.3、协程常用的一些方法
asyncio.Future
在 Python 3.7 中,asyncio.Future 是协程中异步操作的结果容器。它允许您以非阻塞方式等待异步操作的完成,并使用它们的结果。
import asyncio
async def my_coroutine():
# 创建一个 future 对象
future = asyncio.Future()
# 让另外一个任务在5秒后设置future的结果
async def set_result():
await asyncio.sleep(5)
future.set_result('Future is done!')
asyncio.create_task(set_result())
# 使用await等待 future 完成
result = await future
print(result)
# 运行协程
asyncio.run(my_coroutine())
在上面的代码中,我们创建了一个 asyncio.Future 对象 future,并使用另一个协程 set_result() 来设置其结果。然后,我们使用 await 等待 future 对象的完成,并打印出结果。注意,set_result() 协程在后台进行,不会阻塞当前的协程。
当您需要在协程中执行 I/O 操作时,可以使用 asyncio.Future 对象来代替回调函数或线程。此外,asyncio.Future 也可用于实现协程之间的通信。
asyncio.as_completed==================================================================
asyncio.as_completed 是一个异步协程函数,用于迭代一组协程对象,并在它们完成后返回一个迭代器。它会按照任务完成的顺序生成 Future 对象。
import asyncio
async def coro1():
await asyncio.sleep(1)
return "coro1"
async def coro2():
await asyncio.sleep(2)
return "coro2"
async def main():
tasks = [coro1(), coro2()]
for task in asyncio.as_completed(tasks):
result = await task
print(result)
asyncio.run(main())
在上面的代码中,我们定义了两个协程函数 coro1 和 coro2,它们分别休眠 1 秒和 2 秒,并返回字符串 "coro1" 和 "coro2"。
在 main() 函数中,我们创建了一个任务列表 tasks,其中包含这两个协程函数。然后,我们使用 asyncio.as_completed 迭代这些任务,并等待每个任务完成后打印结果。
由于 coro1 完成时间较短,所以它的结果先被打印,然后是 coro2 的结果。
需要注意的是,asyncio.as_completed 返回的是一个迭代器,它会在所有协程完成之前一直阻塞。如果您想同时运行多个协程,可以使用 asyncio.gather 函数。
19、动态属性和特性
19.1、动态属性的一些概念
在python中,数据的属性和处理数据的方法统称属性(attribute),除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法(即读值方法和设值方法)
__getattr__方法,仅当无法使用常规方式获取属性(即在实例、类或超类中找不到指定的属性),解释器才会调用特殊的__getattr__方法
19.2、动态属性的实现
from collections import abc
from urllib.request import urlopen
import warnings
import os
import json
URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON = 'osconfeed.json'
def load():
if not os.path.exists(JSON):
msg = 'downloading {} to {}'.format(URL, JSON)
warnings.warn(msg) # <1>
with urlopen(URL) as remote, open(JSON, 'wb') as local: # <2>
local.write(remote.read())
with open(JSON) as fp:
return json.load(fp) # <3>
class FrozenJSON:
"""A read-only façade for navigating a JSON-like object
using attribute notation
"""
def __init__(self, mapping):
self.__data = dict(mapping) # <1>
print("\n")
# print(self.__data)
def __getattr__(self, name): # <2>
print(f'\n{name}\n')
# if name == 'Schedule':
# return 'you Schedule'
if hasattr(self.__data, name):
return getattr(self.__data, name) # <3>
else:
return FrozenJSON.build(self.__data[name]) # <4>
@classmethod
def build(cls, obj): # <5>
if isinstance(obj, abc.Mapping): # <6>
print("我现在是个字典")
print(obj)
return cls(obj)
elif isinstance(obj, abc.MutableSequence): # <7>
print("我现在只是个序列")
print(obj)
return [cls.build(item) for item in obj]
else: # <8>
return obj
raw_feed = load()
feed = FrozenJSON(raw_feed)
print("初始化呢")
print(feed.Schedule.speakers[0].keys())
# print(feed.Schedule.__dict__)
# print(feed.Schedule)
字典样例:
{ "Schedule":
{ "conferences": [{"serial": 115 }],
"events": [
{ "serial": 34505,
"name": "Why Schools Don´t Use Open Source to Teach Programming",
"event_type": "40-minute conference session",
"time_start": "2014-07-23 11:30:00",
"time_stop": "2014-07-23 12:10:00",
"venue_serial": 1462,
"description": "Aside from the fact that high school programming...",
"website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505",
"speakers": [157509],
"categories": ["Education"] }
],
"speakers": [
{ "serial": 157509,
"name": "Robert Lefkowitz",
"photo": null,
"url": "http://sharewave.com/",
"position": "CTO",
"affiliation": "Sharewave",
"twitter": "sharewaveteam",
"bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..." }
],
"venues": [
{ "serial": 1462,
"name": "F151",
"category": "Conference Venues" }
]
}
}
输出:
初始化呢
Schedule
我现在是个字典
{'conferences': [{'serial': 115}], 'events': [{'serial': 34505, 'name': 'Why Schools Don´t Use Open Source to Teach Programming', 'event_type': '40-minute conference session', 'time_start': '2014-07-23 11:30:00', 'time_stop': '2014-07-23 12:10:00', 'venue_serial': 1462, 'description': 'Aside from the fact that high school programming...', 'website_url': 'http://oscon.com/oscon2014/public/schedule/detail/34505', 'speakers': [157509], 'categories': ['Education']}], 'speakers': [{'serial': 157509, 'name': 'Robert Lefkowitz', 'photo': None, 'url': 'http://sharewave.com/', 'position': 'CTO', 'affiliation': 'Sharewave', 'twitter': 'sharewaveteam', 'bio': 'Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup...'}], 'venues': [{'serial': 1462, 'name': 'F151', 'category': 'Conference Venues'}]}
speakers
我现在只是个序列
[{'serial': 157509, 'name': 'Robert Lefkowitz', 'photo': None, 'url': 'http://sharewave.com/', 'position': 'CTO', 'affiliation': 'Sharewave', 'twitter': 'sharewaveteam', 'bio': 'Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup...'}]
我现在是个字典
{'serial': 157509, 'name': 'Robert Lefkowitz', 'photo': None, 'url': 'http://sharewave.com/', 'position': 'CTO', 'affiliation': 'Sharewave', 'twitter': 'sharewaveteam', 'bio': 'Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup...'}
keys
dict_keys(['serial', 'name', 'photo', 'url', 'position', 'affiliation', 'twitter', 'bio'])
1、一些技术点分析
1)isinstance(obj, abc.Mapping) 判断是否是字典
isinstance(obj, abc.MutableSequence) 判断是否是列表
2)return cls(obj)
此处咨询了一下gpt:
请问您所说的“类加参数”是指类的实例化时需要传入一些参数吗?如果是这样的话,可以在类的定义中使用构造函数(也称为初始化方法)来接收这些参数。Python中的构造函数是__init__,它会在创建对象时自动调用。以下是一个示例代码,其中包含了一个简单的类和构造函数:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person1 = Person("Alice", 25)
print(person1.name) # 输出 "Alice"
print(person1.age) # 输出 25
3)return [cls.build(item) for item in obj]
对列表里面的字典进行
4)类方法build把嵌套结构转换成实例或者实例列表
5)可能会出现关键字当做key的情况,以防万一报错可以抓住异常或者对异常的可以做一些识别的事情
19.3、使用__new__方法以灵活的方式创建对象
__new__是构建实例的特殊方法,这是个类方法(使用特殊方式处理,不需要@classmethod),必须返回一个实例。
返回的实例会作为第一个参数(即self)传给__init__方法。
因为调用__init__方法时要传入实例,而且禁止返回任何值,所以__init__方法其实是“初始化方法”。真正的构造方法是__new__。
我们几乎不需要自己编写__new__方法,因为从object类继承的实现已经足够了。
"""
explore2.py: Script to explore the OSCON schedule feed
>>> from osconfeed import load
>>> raw_feed = load()
>>> feed = FrozenJSON(raw_feed)
>>> len(feed.Schedule.speakers)
357
>>> sorted(feed.Schedule.keys())
['conferences', 'events', 'speakers', 'venues']
>>> feed.Schedule.speakers[-1].name
'Carina C. Zona'
>>> talk = feed.Schedule.events[40]
>>> talk.name
'There *Will* Be Bugs'
>>> talk.speakers
[3471, 5199]
>>> talk.flavor
Traceback (most recent call last):
...
KeyError: 'flavor'
"""
aaa={ "Schedule":
{ "conferences": [{"serial": 115 }],
"events": [
{ "serial": 34505,
"name": "Why Schools Don´t Use Open Source to Teach Programming",
"event_type": "40-minute conference session",
"time_start": "2014-07-23 11:30:00",
"time_stop": "2014-07-23 12:10:00",
"venue_serial": 1462,
"description": "Aside from the fact that high school programming...",
"website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505",
"speakers": [157509],
"categories": ["Education"] }
],
"speakers": [
{ "serial": 157509,
"name": "Robert Lefkowitz",
"photo": '',
"url": "http://sharewave.com/",
"position": "CTO",
"affiliation": "Sharewave",
"twitter": "sharewaveteam",
"bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..." },
{ "serial": 6666,
"name": "dsfa",
"photo": '',
"url": "http://adfa.com/",
"position": "ff",
"affiliation": "ffff",
"twitter": "ffff",
"bio": "Robert ´r0ml´ Lefkowitz is the CTO at Sharewave, a startup..." }
],
"venues": [
{ "serial": 1462,
"name": "F151",
"category": "Conference Venues" }
]
}
}
# BEGIN EXPLORE2
from collections import abc
from keyword import iskeyword
class FrozenJSON(object):
"""A read-only façade for navigating a JSON-like object
using attribute notation
"""
def __new__(cls, arg): # <1>
# print(f'初始化arg\t{arg}')
if isinstance(arg, abc.Mapping):
return super().__new__(cls) # <2>
elif isinstance(arg, abc.MutableSequence): # <3>
# print("是序列\n")
# print([cls(item) for item in arg])
# for item in arg:
# print(item)
# print(cls(item))
# print("\n")
return [cls(item) for item in arg]
else:
return arg
def __init__(self, mapping):
print(f'我是init{mapping}')
# print(f'init的操作{mapping}')
self.__data = {}
for key, value in mapping.items():
if iskeyword(key):
key += '_'
self.__data[key] = value
def __getattr__(self, name):
if hasattr(self.__data, name):
# print(f'\n{name}\n')
# print(self.__data)
# print(self.__data[name])
# print("\n\n")
return getattr(self.__data, name)
else:
# print(f'\n{name}\n')
# print(self.__data)
# print(self.__data[name])
# print("\n\n")
return FrozenJSON(self.__data[name]) # <4>
# END EXPLORE2
feed = FrozenJSON(aaa)
print("已经初始化了")
print(feed.Schedule.speakers[-1].name)
需要思考的问题:
super().__new__(cls) 和 [cls(item) for item in arg] 这两处的区别
我自己测试了一下,cls(arg1)会陷入__new__中的无限循环,类似于迭代器,上文中列表里面的数据进行拆分,将列表中的字典再继续循环,如果是字典就返回实例
class MyClass:
def __new__(cls, arg1):
print(f'new中{arg1}')
if arg1 == 'foo':
return arg1
else:
print("我想出去")
return cls(arg1)
def __init__(self, arg1):
self.arg1 = arg1
my_obj = MyClass("foo12")
print(my_obj) # 输出: foo
20、属性描述符
20.1、概念
描述符是对多个属性运用相同的存取逻辑的一种方式,例如django,orm,sql alchemy等orm中的字段类型都是描述符,把数据库记录中字段里的数据与python 对象的属性对应起来。
描述符是实现了特定协议类型的类,这个协议包括了__get__, __set__和__delete__方法
描述符实例:验证属性 特性工厂函数借助函数式编程模式,避免重复编写读值方法和设值方法。
描述符的用法是,创建一个实例作为另一个类的类属性
本文阅读量 次