流畅的Python学习

Posted zzulp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了流畅的Python学习相关的知识,希望对你有一定的参考价值。

流畅的Python笔记

流畅的Python笔记

1 Python数据模型

2 数据结构

2.1 内置序列类型

2.2 列表推导与生成器表达式

2.3 元组

2.4 切片

2.5 序列对象上的+与*

2.6 sorted函数与list的sort方法

2.7 其他可选类型

2.7.1 array

2.7.2 memoryview

2.7.3 deque以及其他形式的队列

3 字典与集合

3.1 可hash

3.2 常见方法

3.3 其他dict

3.4 集合

3.5 dict与set的底层实现

4 文本与字节序列

4.1 文本与字节

4.2 编解码问题

4.3 Unicode字符比较时的规范化

4.4.1 大小写折叠

4.4.2 极端方式

4.5 Unicode文本排序

4.6 支持字符串与字节序列的双模式API

5 一等函数

5.1 函数基础知识

5.2 函数参数

5.3 函数注解

5.4 函数式编程

6 使用函数实现的设计模式

7 函数装饰器和闭包

7.1 装饰器

7.2 变量作用域规则

7.3 闭包

7.4 标准库中的装饰器

8 Python的面向对象

8.1 对象标识,相等性和别名

8.2 浅复制与深复制

8.3 参数与引用

8.4 垃圾回收与引用计数

9 Python风格的对象

10 序列的修改/散列与切片

11 接口

11.1 猴子补丁

11.2 标准库中的抽象基类

11.2.1 集合基类

11.2.2 数字基类

11.3 定义并使用抽象基类

12 继承与多重继承

12.1 内置类型的派生

12.2 多重继承和方法解析顺序

13 重载运算符

14 可迭代对象与生成器

14.1 可迭代对象与迭代器

14.2 生成器

14.3 标准库中生成器

15 上下文管理器

15.1 else语句

15.2 with与上下文管理器

15.3 contextlib

16 协程

16.1 将生成器转化为协程

16.2 预激协程装饰器

16.3 让协程返回值

16.4 使用yield from

17 物理并发

17.1 并发

18 使用asyncio包

18.1 线程与协程对比

19 动态属性与特性

19.1 动态获取属性

19.2 深入特性

19.5 特性工厂函数

19.6 处理属性的相关属性与函数

19.6.1 特殊属性

19.6.2 内置函数

19.6.3 特殊方法

20 属性描述符

20.1 特性管理类

20.2 覆盖型与非覆盖型描述符对比

20.3 方法都是描述符

20.4 描述符最佳实践

21 类元编程

21.1 类工厂函数

21.2 定制描述符的类装饰器

21.3 导入时与运行时比较

21.4 元类基础

21.5 定制描述符的元类

21.6 元类特殊方法prepare

21.7 类作为对象

1 Python数据模型

这是流畅的Python第一章的扑克牌示例,使用collection中的类,并介绍了类的特殊方法

通过实现__getitem__可以使得类可以支持索引和迭代。通过实现__len__可以使得类可以计算长度。

下面的示例,使用了namedtuple类来创建新的类型Card.

from collections import namedtuple
from random import choice

Card = namedtuple('Card', ['Rank', 'Suite'])

class Deck:
    ranks = [item for item in range(2, 11)] + list('AJQK')
    suites = ['spades', 'diamonds' , 'clubs' , 'hearts']

    def __init__(self):
        self.cards = [Card(rank, suite) for rank in self.ranks for suite in self.suites]


    def __getitem__(self, idx):
        return self.cards[idx]


    def __len__(self):
        return len(self.cards)


if __name__ == '__main__':
    deck = Deck()
    print(len(deck))
    print(choice(deck))

    print(deck[0:10])

#     for card in deck:
#         print card

    print(Card('Q', 'hearts') in deck)
52
Card(Rank='K', Suite='diamonds')
[Card(Rank=2, Suite='spades'), Card(Rank=2, Suite='diamonds'), Card(Rank=2, Suite='clubs'), Card(Rank=2, Suite='hearts'), Card(Rank=3, Suite='spades'), Card(Rank=3, Suite='diamonds'), Card(Rank=3, Suite='clubs'), Card(Rank=3, Suite='hearts'), Card(Rank=4, Suite='spades'), Card(Rank=4, Suite='diamonds')]
True

这是向量类

import math

class vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, a):
        return vector(self. x + a.x, self.y + a.y)

    def __abs__(self):
        return math.sqrt(self.x*self.x + self.y*self.y)

    def __repr__(self):
        return "Vector(, )".format(self.x, self.y)

    def __mul__(self, scalar):
        return vector(self.x * scalar, self.y * scalar)

if __name__ == '__main__':
    v1 = vector(3, 4)
    v2 = vector(1, 2)
    print v1+v2
    print abs(v1)

    print v1 * 3
    pass
Vector(4, 6)
5.0
Vector(9, 12)

2 数据结构

2.1 内置序列类型

Python标准库用C实现了丰富的序列类型:

类型名称
容器序列list, tuple, collections.deques
扁平序列str, bytes, bytearray, memoryview, array.array

容器序列存放的是它们包含的对象的引用,扁平序列存放的是值而非引用。

类型名称
可变序列list, collections.deques, bytearray, memoryview, array.array
不可变序列str, bytes, tuple

2.2 列表推导与生成器表达式

列表推导可以用来创建新的列表,并提高代码的可读性。列表推导的概念可以推广到集合推导,字典推导。

使用()可以生成generator对象,以供后续的遍历。

cards = [item for item in range(1,13) if item%2==0 ]
print cards

cards = (item for item in range(1,13))
print cards

dicts = k:v for (k,v) in enumerate('ab')
print dicts

sets = x for x in range(1,6)
print sets
[2, 4, 6, 8, 10, 12]
<generator object <genexpr> at 0x7f644fd8b9b0>
0: 'a', 1: 'b'
set([1, 2, 3, 4, 5])

2.3 元组

元组不仅仅是不可变的列表,它可以用于存储不需要字段名的记录。对记录的各字段通过位置索引进行访问,或利用元组拆包获得各字段的值。

拆包操作并不局限于元组这种数据类型,任意可迭代的数据类型都可以被拆包。

拆包里面可以使用*var来接收后续的若干个元素的列表,在python2中只能在函数传递形参时拆包,在python3中则不再有这种限制。

namdtuple是一种类型创建器,可以生成一个新的类型,新类型实例化的对象拥有命名的字段,可以通过字段访问属性值。

from collections import namedtuple

bush = ('Bush', 88, 'male', 'U.S.')
name, age, sex, country = bush

print(bush)
print(name, age, sex, country)
print("%s is %d years old, sexy is %s and come from the %s"%bush)

a, b, *rest, c = range(5)
print(a, b, rest, c)

Person = namedtuple('Person', ['name', 'age', 'sex', 'country'])
p = Person(*bush)
print(p)
('Bush', 88, 'male', 'U.S.')
Bush 88 male U.S.
Bush is 88 years old, sexy is male and come from the U.S.
0 1 [2, 3] 4
Person(name='Bush', age=88, sex='male', country='U.S.')

2.4 切片

切片操作是通过切片对象来操作的,[]操作符可以生成切片对象,还可以使用逗号分隔的多个索引或切片,外部库Numpy就用了这个特性。

省略(…)可以作为切片的一部分,用于多维数组切片的快捷方式。如果x是4维的,则x[i,…]就是x[i , : , : , :]的缩写。

如果把切片放在赋值语句的左边,或用于del操作,则可以对序列进行嫁接,切除或就地修改。

s="bicycle"
op = slice(0, len(s), 3)
print(s[op])
print(s[::3])

lst = list(range(0, 6))
lst[0:3] = [100,200]
print(lst)

bye
bye
[100, 200, 3, 4, 5]

2.5 序列对象上的+与*

序列相加被实现为拼接两个序列,序列乘整数n被实现为连续重复n遍。这两个操作都重新返回一个新的对象。

但是就地操作+=和*=则会修改原始的对象。id(obj)可以查看一个对象的内在地址,可以观察对象是否为同一个。

对于tuple,若其中存在可修改的元素,对其进行修改时,虽然会抛出异常,但修改仍在异常抛出前完成了,在某些情况下可能会出现问题,因此要避免在tuple中存储可修改的元素。

a = [1, 2, 3]
b = [4,5,6]

print(4*a)
print(a+b)

print(id(a))
print(id(b))

a += b
a *= 2

print(id(a))
print(id(b))

t = (1, 2, [30, 40])
# t[2] += [50, 60]
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
[1, 2, 3, 4, 5, 6]
140242249326280
140242249323080
140242249326280
140242249323080

2.6 sorted函数与list的sort方法

sort方法会进行就地的排序并返回None,sorted则返回一个新的列表。

内置模块bisect提供了对有序列表进行二分查找与插入的功能。

import bisect

lst = list(range(0, 10, 2))
print(lst)

position = bisect.bisect(lst, 4)
print(position)

bisect.insort(lst, 7)
print(lst)
[0, 2, 4, 6, 8]
3
[0, 2, 4, 6, 7, 8]

2.7 其他可选类型

在一些性能特定的场景下,list并不是最好的选择,下面介绍一些常用的容器结构。

2.7.1 array

类似于C的数组,不仅支持list的有关操作,还支持文件读取与保存的方法。在使用array的时候需要指定其数据类型,例如b表示byte类型,d表示float类型。

array在性能上比list要高很多,因此在存储大量数据的场景下,可以使用array。

2.7.2 memoryview

memeoryview是受numpy启发而在python3中引入的,它可以包装一段内存,让用户在不复制内容的情况下操作同一片内存的不同切片。

2.7.3 deque以及其他形式的队列

deque是一个双端队列,且为线程安全的。

from array import array

floats = array('d', [1.2, 1.3, 1.4, 1.5])

print(floats)

view = memoryview(array('b', [1,2,3,4]))
print(view.cast('H').tolist())
array('d', [1.2, 1.3, 1.4, 1.5])
[513, 1027]

3 字典与集合

3.1 可hash

所谓可hash是指对象在生命周期中其值不会发生改变,这样调用对象上的hash()方法,返回的值也不会改变。python中的不可变类型如str , bytes与数值类型都是可hash的。对于不可变容器类型frozeset也是可hash的。对于tuple,其包含的元素必须是不可变的时其才为可hash的。

x = (1, 2, (3, 4))
print(hash(x))

x = (1, 2, [3, 4])
try:
    print(hash(x))
except Exception as e: 
    print(e)
-2725224101759650258
unhashable type: 'list'

3.2 字典的常见方法

dict, defaultdict与OrderdDict的常用方法如下表所示

方法说明
clear清除所有元素
copy浅复制
fromkeys(iter)将迭代中的元素设置为字典的key
get(key, [default])获取指定key的值
items()返回所有键值对
keys()返回所有key
values()返回所有value
setdefault(k, [default])设置k的值为value而不论k是否存在
pop(k, [default])弹出k对应的value
popitem()随机弹出一个键值对
update(iter)更新一批值

3.3 其他dict

defaultdict提供了字典中不存在k时,读取这个k时自动返回一个初始化值的方法。否则你需要先判断k是否存在,若不存在先初始化,若存在才能读取其值返回。可以让代码实现的更简捷。

OrderdDict提供了按插入顺序遍历其中元素的字典。

ChainMap提供了可以将多个字典串联起来,从而用一行代码查找多个字典的类型,不建议对ChainMap对象进行写操作。

Counter提供了一个统计序列中出现频次的计数器类。

from collections import ChainMap

dict1 = 
dict2 = 
dict3 = 'a': 1

map1 = ChainMap(dict1, dict2, dict3)
print(map1.get('a'))

map1['a'] = 3
print(map1.get('a'))

map1['b'] = 5
print(map1.get('b'))

print(dict1, dict2, dict3)
1
3
5
'a': 3, 'b': 5  'a': 1

3.4 集合

集合中的每个元素都是唯一的,因此集合可以用于去重。此外集合还实现了集合运算。

集合运算符作用
I集合并运算,如AIB
&集合交集运算,如A&B
-集合差集运算,如A-B

集合的字面常量使用大括号,如1, 2, 3,但如果要创建一个空集,就不能用,必须是set()。

集合操作与比较

方法说明
intersection(iter)与迭代序列的值进行交运算
union(iter)与迭代序列的值进行并运算
difference(iter)与迭代序列的值进行差运算
issubset(iter)s是否为可迭代序列的子集

isupperset(iter)|s是否可迭代序列的超集|
|isdisjoint(s)|两个集合是否不相交|

其他操作

方法说明
s.add(e)向集合中加入元素
s.clear()
s.copy()
s.dicard(e)移除元素e
s.pop()移除一个元素,返回之
s.remove(e)移除元素
import dis

a = 1, 2, 3
print(type(a))
print(a)

dis.dis('1,2,3')
<class 'set'>
1, 2, 3
  1           0 LOAD_CONST               0 (1)
              2 LOAD_CONST               1 (2)
              4 LOAD_CONST               2 (3)
              6 BUILD_SET                3
              8 RETURN_VALUE

3.5 dict与set的底层实现

dict与set 底层采用了hash表来实现存储,因此查找算法十分快,但缺点是占用空间比较大。

其在不断的插入过程中如果原有的存储空间不满足要求,算法会重建整个hash表,因此如果在遍历的时候去插入dict,可能会触发底层hash表重建,而重建过程会导致key值的重排,因此可能会跳过而无法访问一部分key值。

4 文本与字节序列

4.1 文本与字节

在python3中,字符串str的内部表示以unicode进行,当需要转换成其他编码时都要进行encode,而其他编码需要decode才可转换为unicode。
python3中bytes和bytearray用于存储字符数组,每个字符都由0-255之间的整数。bytes对像是不可变的,bytearray是可变的。str对象通过指定的编码可以生成bytes对象,str对象上不在有decode方法。可以认为str对象存储的是unicode序列,而bytes中存储的是各种实现编码的字节。因此bytes上支持decode()方法

在处理过程中对于输入,要尽可能早的将输入字节解码为unicode,而在输出时要尽可能晚的将unicode编码成字节流。标准IO库的open与write均遵照上面的原则。

b = u'中国'.encode('utf-8')
print(type(b))
print(type(b[0:1]))
print(b)

ba = bytearray(b)
print(type(ba))
print(type(ba[0:1]))
print(ba)

asc = u'中国'.encode('cp437', errors='replace')
print(asc)

try:
    c = u'中国'.decode('utf-8')
    print(type(c))
except Exception as e:
    print(e)
<class 'bytes'>
<class 'bytes'>
b'\\xe4\\xb8\\xad\\xe5\\x9b\\xbd'
<class 'bytearray'>
<class 'bytearray'>
bytearray(b'\\xe4\\xb8\\xad\\xe5\\x9b\\xbd')
b'??'
'str' object has no attribute 'decode'

4.2 编解码问题

语言提供了UnicodeEncodeError异常(将字符串编码为二进制序列时)与UnicodeDecodeError异常(将二进制序列转换为字符串时)。

如果要编码或解码的字符集中不包含要处理的字符,则会抛出异常。可以指定错误处理函数来防止抛出异常。常用的如errors=’ignore|replace’。ignore会悄悄的忽略错误,replace将无法处理的字符替换为?。

在python3中,源码文件中的字符默认为utf-8编码,而python2默认使用ascii编码,因此python2文件中若出现了非ascii字符,则需要在文件头显式的声明:#coding: utf8

python有个三方库叫chardetect,可以探测文件所使用的编码。

4.3 Unicode字符比较时的规范化

对于一些西欧语言,字符上面还有重音符号,这类字符可以由两种方式表示,其中一种是在后面追加一个字节表示重音符号。

两种方式会导致相同的字符串有不同长度的表示方式。对于这类字符串的比较需要使用unicodedata模块提供的Normalize函数对其进行处理。unicodedata模块是一个unicodedatabase,其中包含了所有unicode字符的定义,值,名字,类别等信息。

Normalize的第一个参数为规范化的方式,有NFC,NFD,NFKC,NFKD。NFC使用最少的码位构成等价的字符串,而NFD将组合字符分解成基本字符和单独的组合字符。而NFKC和NFKD则将组合字符分解为普通多个字符的组合,它们可以用在需要检索或索引的场合,如 42 4 2 会规范为42。

from unicodedata import normalize

s1='café'
s2='cafe\\u0301'
print(normalize('NFC',s1))
print(normalize('NFC',s2))

café
café

4.4.1 大小写折叠

先把所有文本变成小写,这个功能由str.casefold()方法支持。其与str.lower()在一些码位上会有不同的结果。

在多数场合下不区分大小写的比较都可以使用NFC规范后接casefold来解决。

4.4.2 极端方式

极端方式是去掉所有变音符号,可以采用下面的函数。其缺陷在对于非拉丁字符只去掉变音符号并不能将它们变成ASCII字符。

import unicodedata

def shave_marks(txt):
    norm_txt = unicodedata.normalize('NFD', txt)
    shaved = ''.join(c for c in norm_txt if c not unicodedata.combining(c))
    return unicodedata.normalize('NFC', shaved)

4.5 Unicode文本排序

Python比较任何类型的序列时,会逐个比较序列的元素。对字符串来说,比较的是码位。对于非ASCII字符来说,这种比较方式不尽人意。非ASCII文本的标准排序方式是使用locale.strxfrm函数,这个函数会把字符串置换成适合所在区域进行比较的形式。因此在使用此函数之前,需要设置合适的区域setlocale(LC_COLLATE, ‘zh_CN.utf8’),同时还需要操作系统支持此选项。另外locale.setlocale()方法是非线程安全的,因此最好在程序开始的地方保证只设置一次。

然而由于不同操作系统对locale支持特性的不同,推荐使用PyPI中的PyUCA库来进行unicode排序。

import pyuca

collate = pyuca.Collator()
fruit = ['苹果', '桔子']
fruit_sorted = sorted(fruit, key=collate.sort_key)
print(fruit_sorted)

4.6 支持字符串与字节序列的双模式API

这些双模式的API根据输入的不同展现出不同的行为。re和os模块中就有这样的函数。

使用re模块,如果使用字节序列,则\\d,\\w等模式只能匹配ASCII字符。如果是使用字符串模式,则能匹配ASCII之外的数字和字母。字符串正则表达式有个re.ASCII标志,可以让模式只匹配ASCII字符。

使用os模块,如果使用字符串参数,则参数会被sys.getfilesystemcoding()得到的codec进行编码,在操作系统内部则使用相同的codec进行解码。这正是我们想要的行为。为了处理那些不能如此处理的字符,则需要将字节序列参数传递给os模块。

import re
import os

print(type(u'中'))
print(type('中'.encode('utf8')))

open(u'中','w')
open('华'.encode('utf8'), 'w')

a = os.listdir('.')
b = os.listdir(b'.')

print(a)
print(b)
<class 'str'>
<class 'bytes'>
['datalab', '中', '.cache', '.local', '华', '.forever', '.ipython', '.config']
[b'datalab', b'\\xe4\\xb8\\xad', b'.cache', b'.local', b'\\xe5\\x8d\\x8e', b'.forever', b'.ipython', b'.config']

5 一等函数

5.1 函数基础知识

在python中,函数是一等的。函数可以在运行时创建,可以赋值给其他变量,可以作为参数传给函数,能作为函数的返回结果。

内置的filter, map, reduce等都是高阶函数,可以接受函数作为参数。由于python后续引入了列表推导,map, filter的作用可以被替代。在python3中他们返回的是生成器。

匿名函数可以用lambda来创建,但由于语法限制,无法实现复杂的函数。

可以用callable()来判断一个对象是否可调用。

任何python的对象,只要实现了call方法,都可以表现的像函数。

5.2 函数参数

Python提供了灵活的参数处理机制,可以传入列表和字典参数,在调用函数时使用和*展开可迭代对象。

5.3 函数注解

python3的注解可以用来说明参数及返回值。在说明参数时,注解内容存放在:后。注解返回值存放在)->后。

import inspect

def my_add(a, b):
    '''This is Add function'''
    return a + b

print(my_add.__doc__)
print(dir(my_add))

fruits=['banana', 'orange','apple', ]
sorted(fruits, key=lambda word: word[::-1])


class Test:
    def __init__(self, msg):
        self.msg = msg


    def __call__(self):
        print(self.msg)


t = Test('hello world')
t()


def func_args(first, *lst, **kwargs):
    print(first)
    print(lst)
    print(kwargs)


func_args('first', 1, 2, 3, key=1, value=2)


def func_test(a, b, c):
    print(a,b,c)

args = 'a':1, 'b':2, 'c':3
func_test(**args)

print(func_test.__defaults__)
print(func_test.__kwdefaults__)
print(func_test.__code__)


def annotation(a,b:int,c)->str:
    return 0

sig = inspect.signature(annotation)
print(sig.return_annotation)
for param in sig.parameters.values():
    print(param.annotation)
    print(param.name, param.default)
This is Add function
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
hello world
first
(1, 2, 3)
'key': 1, 'value': 2
1 2 3
None
None
<code object func_test at 0x7fec2ea2a4b0, file "<ipython-input-11-f146c66b8ebd>", line 36>
<class 'str'>
<class 'inspect._empty'>
a <class 'inspect._empty'>
<class 'int'>
b <class 'inspect._empty'>
<class 'inspect._empty'>
c <class 'inspect._empty'>

5.4 函数式编程

operator中提供了众多运算符的函数实现,可以方便的用于类似于reduce这类高阶函数中。

operator中还有一类函数,可以替代从序列中取出元素或读取对象属性的lambda表达式。itemgetter接受一个索引用于从序列中索取指定的值,而attrgetter授受一个字符串用于从对象上获取指定的属性值。

最后operator.methodcaller与functools.partial都可以绑定函数的若干参数。

from operator import add, mul, itemgetter, attrgetter, methodcaller
from functools import partial, reduce

numbers = [1,2,3,4,5]
prod = reduce(mul, numbers)

print(prod)


persons=[('Lucy', 23), ('Jim', 13), ('Lily', 18)]
print(sorted(persons, key=itemgetter(1)))

age_name = itemgetter(1, 0)
for item in persons:
    print(age_name(item))


get_doc = attrgetter('__doc__')

print(get_doc(mul))


upper = methodcaller('upper')
print(upper('hello'))

triple = partial(mul, 3)
print(triple(8))
120
[('Jim', 13), ('Lily', 18), ('Lucy', 23)]
(23, 'Lucy')
(13, 'Jim')
(18, 'Lily')
mul(a, b) -- Same as a * b.
HELLO
24

6 使用函数实现的设计模式

from abc import ABC, abstractmethod
from collections import namedtuple


Customer = namedtuple('Customer', (name, fidelity))

class LineItem:
    def __init__(self, product, quantity, price):
        self.product = product
        self.quantity = quantity
        self.price = price

    def total(self):
        return self.quantity * self.price
    pass

class Order:
    def __init__(self, customer, cart, promotion=None):
        self.custom = custom
        self.cart = list(cart)
        self.promotion = promotion

    def total(self):
        return sum(item.total() for item in self.cart)

    def due(self):
        if self.promotion is None:
            discount = 0
        else:
            discount = self.promotion.discount(self)

        return self.total() - discount

    pass

class Promotion(ABC):
    @abstractmethod
    def discount(self, order):
        pass

class FidelityPromotion(Promotion):
    def discount(self, order):
        return order.total()

class BulkItemPromotion(Promotion):
    def discount(self, order):
        discount = 0
        for item in order:
            if item.quantity > 20:
                discount += item.total() * 0.1
        return discount

class LargeOrderPromotion(Promotion):
    def discount(self, order):
        distinct_items = item.product for item in order.cart
        if len(distinct) > 10:
            return order.total()*0.07
        return 0

7 函数装饰器和闭包

7.1 装饰器

装饰器只是一种语法糖,本质是高阶函数。只需要实现一个高阶函数decorator,并在需要使用的地方,以@decorator放在要包装的函数声明之前即可。

装饰器在被装饰的函数定义之后立即运行。通常是在模块导入的时候。

7.2 变量作用域规则

Python中变量分为全局变量和局部变量。作用域因而分为全局作用域和局部作用域。

当读取一个变量时,会先在局部作用域中查找,再向全局中查找。若局部作用域中定义了和全局作用域相同的变量,则会屏蔽掉全局作用域的定义。

7.3 闭包

闭包是一种函数,它会保留定义它时存在的自由变量的绑定。这样当调用函数时,虽然自由变量的定义作用域不可用了,但是仍能使用那些绑定。

#装饰器示例代码

def my_deco(func, *args, **kwargs):
    def inner(*args, **kwargs):
        print('befor call')
        func(*args, **kwargs)
        print('end call')
    return inner()


@my_deco
def test():
    print('hello world')


test
# 变量作用域示例
a=3

def func():
    a=1
    print(a)

func()
print(a)

# 闭包示例

def make_avg():
    total, count = 0, 0

    def average(num):
        nonlocal total, count
        total += num
        count += 1
        return total / count   
    return average

avg = make_avg()

print(avg(10),avg(11),avg(12))
befor call
hello world
end call
1
3
10.0 10.5 11.0

7.4 标准库中的装饰器

@property
把读值函数映射为类的属性

@classmethod
声明方法为操作类的方法,其第一个参数为所在类的类型

@staticmetod
声明方法为类的静态方法,不需要带类的实例参数

@functools.wraps,其作用是帮忙构建行为良好的装饰器。

@functools.lru_cache(maxsize,typed),它把耗时的函数结果保存起来,避免传入相同参数时重复计算。并将最少使用的条目自动删除。它可以用来消除树形递归时重复计算的问题。

@singledispatch,它用于将装饰的函数变成一个根据第一个参数的类型进行派发的分派器函数,有点OO中的RTTI的味道。可以用于实现函数的重载。

多个装饰器可以按顺序应用到一个函数上,作用是从下向上依次应用这些叠加的装饰器。

from functools import singledispatch

@singledispatch
def my_print(x):
    pass

@my_print.register(str)
def _(s):
    print('string dump', s)

@my_print.register(int)
def _(s):
    print('int dump', s)

@my_print.register(float)
def _(s):
    print('float dump', s)

my_print('hello')
my_print(1)
my_print(0.2)
print('----------------')

def f1(func):
    print('f1 called')
    return func

def f2(func):
    print('f2 called')
    return func

@f1
@f2
def test():
    print('test called')

test()
print('----------------')

def add_args(func):
    def inner(*args, **kwargs):
        print('call test2 wraper', args, kwargs)
        func(*args, **kwargs)
    return inner

@add_args
def test2(*args,**kwargs):
    print('test2 called')

test2()

print('----------------')
def argumented_decorate(prefix):
    def wrapper(func):
        def inner(*args, **kwargs):
            print(prefix, args, kwargs)
            func(*args, **kwargs)
        return inner
    return wrapper

@argumented_decorate(prefix='output:')
def test3(*args, **kwargs):
    print('test3 called')

test3(1,2, a=1, b=2)
string dump hello
int dump 1
float dump 0.2
----------------
f2 called
f1 called
test called
----------------
call test2 wraper () 
test2 called
----------------
output: (1, 2) 'a': 1, 'b': 2
test3 called

8 Python的面向对象

8.1 对象标识,相等性和别名

python中变量都是引用,指向内存中变量的地址。
id()方法可以取得一个变量的内在地址。==判断两对象的值是否相等,而不关心二者是否指向同一个对象。is 关键字判断两个变量是滞指向一个对象。

a=1
b=1

print(id(a), id(b), a is b, a==b)

a=[1,2]
b=[1,2]

print(id(a), id(b), a is b, a==b)

a=(1, 2, [3, 4])
b=(1, 2, [3, 4])
print(id(a), id(b), a is b, a==b)

a[-1].append(5)
print(id(a), id(b), a is b, a==b)
10935488 10935488 True True
140610190950984 140610190790088 False True
140610190713264 140610198628824 False True
140610190713264 140610198628824 False False

8.2 浅复制与深复制

对于对象复制,默认是浅复制,即对于内部非值的对象,只复制引用。如果希望二者完全隔离,则需要使用标准库copy的deepcopy进行深复制。

import copy

lst1 = [1, 2, [3,4]]
lst2 = list(lst1)

print(id(lst1), id(lst2), id(lst1[-1]), id(lst2[-1]))
print(lst1==lst2, lst1 is lst2)
print(lst1[-1] is lst2[-1])


lst2 = copy.deepcopy(lst1)

print(id(lst1), id(lst2), id(lst1[-1]), id(lst2[-1]))
pri

以上是关于流畅的Python学习的主要内容,如果未能解决你的问题,请参考以下文章

流畅python学习笔记第十八章:使用asyncio包处理并发

流畅的python第七章函数装饰器和闭包学习记录

流畅的python第十五章上下文管理器和else块学习记录

流畅的python

流畅的python学习1

流畅的python和cookbook学习笔记