跳转至

流畅的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 这些序列存放不同类型的数据
扁平序列
    strbytesbytearrymemoryview 和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如果生成器表达式是一个函数调用过程中唯一参数那么不需要额外再用括号把它围起来
2array的构造方法需要两个参数因此括号是必须的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作者的定义
原子不可变数据类型strbytes和数值类型都是可散列类型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中所有函数都是一等对象

接受函数为参数或者把函数作为结果返回的函数是高阶函数
最为人熟知的高阶函数有mapfilterreduce和applypython3中以移除


mapfilter和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

上面ABCkk指代同一个对象


==和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一些技术点分析
1isinstance(obj, abc.Mapping) 判断是否是字典
    isinstance(obj, abc.MutableSequence) 判断是否是列表
2return 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

3return [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__方法

描述符实例:验证属性 特性工厂函数借助函数式编程模式,避免重复编写读值方法和设值方法。

描述符的用法是,创建一个实例作为另一个类的类属性



觉得有用的老铁们可以"微信"赞助一杯82年的雪碧呦。
txtddd
本文阅读量  次

评论