Python 3.8 新特性与标准库笔记
Python 3.8 新特性与标准库笔记
记 Python 3.8 引入的几个语法,顺带把常用标准库(collections、operator、itertools)和闭包、装饰器、property 这些日常会写到的东西一起整理。
海象表达式 :=
可以在表达式内部赋值,省掉一次单独的赋值语句:
if (a := 3) > 2:
print(a) # 3
while (a := a + 1) < 10:
print(a) # 4, 5, 6, 7, 8, 9
在列表推导里尤其方便,把中间结果存下来重用:
a = [j for j in range(10) if (j % 2) == 1] # [1, 3, 5, 7, 9]
c = [i for j in range(10) if (i := j % 2) == 1] # [1, 1, 1, 1, 1]
仅位置参数 /
/ 之前的参数只能按位置传,不能写成关键字参数:
def test(a, b, c, /, d, e, **kwargs):
print(a, b, c, d, e)
test('a', 'b', 'c', d='d', e='e') # a b c d e
test('a', 'b', c='c', d='d', e='e') # TypeError: missing positional argument 'c'
f-string 的 = 说明符
f'{expr=}' 会展开成"表达式文本 + 等号 + 结果",调试打印变量名和值时很省事:
a, b = 1, 2
print(f'{a=} {b=}') # a=1 b=2
print(f"{a = } {b = }") # a = 1 b = 2(等号两侧加空格会保留)
print(f"{a+b=}") # a+b=3
{} 本身要输出就双写:
a = 0
print(f"{{{a}}}") # {0}
pip 从 git 装包
pip install git+<git仓库地址>
pip install git+<git仓库地址>@<分支名称>
字符串
startswith / endswith 接受元组,可以一次判断多个前缀/后缀。传 list 或 set 不行,得先 tuple() 转一下:
if name.startswith(('http:', 'https:', 'ftp:')):
...
str.format 用 {name} 这种命名占位:
s = '{name} hahaha {test}'
print(s.format(name='kill', test='oooooooo'))
高阶函数
# map: 对每个元素调用 fun,返回新序列
list(map(lambda a: a + 1, [1, 2, 3])) # [2, 3, 4]
# reduce: 两两累计
from functools import reduce
reduce(lambda a, b: a + b, [1, 2, 3]) # 6
# filter: 按条件筛选
list(filter(lambda a: a % 2 == 1, [1, 2, 3])) # [1, 3]
字典的集合运算
字典的 keys() 返回的视图支持集合的并、交、差。值集合可能重复,要做集合运算得先转 set:
a = {'key': 1, 'value': 2, 'z': 1, 'w': 1}
b = {'key': 1, 'value': 3}
a.keys() & b.keys() # {'value', 'key'} 键的交集
a.items() & b.items() # {('key', 1)} 键值对交集
a.keys() - b.keys() # {'z', 'w'} 键的差集
# 用差集挑出想保留的键
c = {k: a[k] for k in a.keys() - {'z', 'w'}} # {'key': 1, 'value': 2}
生成器
列表推导一次性把所有元素算出来占内存,把 [] 换成 () 就是生成器,惰性求值省空间:
L = [x * x for x in range(10)] # 立刻得到完整 list
g = (x * x for x in range(10)) # generator,边迭代边算
for i in g:
print(i)
装饰器
不改原函数,在调用前后塞功能:
def trace(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f'{func.__name__}({args!r}, {kwargs!r}) -> {result!r}')
return result
return wrapper
@trace
def fibonacci(n):
if n in (0, 1):
return n
return fibonacci(n - 2) + fibonacci(n - 1)
闭包
内层函数引用了外层作用域的变量,且外层通常返回内层函数。改外层变量要用 nonlocal:
def sort_priority(values, group):
found = False
def helper(x):
nonlocal found
if x in group:
found = True
return 0, x
return 1, x
values.sort(key=helper)
return found
闭包里要改的状态一多,nonlocal 就开始别扭,这时改成一个可调用对象(实现 __call__)更清楚:
class Sorter:
def __init__(self, group):
self.group = group
self.found = False
def __call__(self, x):
if x in self.group:
self.found = True
return (0, x)
return (1, x)
sorter = Sorter(group)
numbers.sort(key=sorter)
lambda 的延迟绑定坑
lambda 捕获的是变量本身而非定义时的值,循环里很容易踩:
x = 10
a = lambda y: x + y
x = 20
print(a(10)) # 30,用的是最新的 x
# 想在定义时就把值钉住,用默认参数
x = 10
a = lambda y, x=x: x + y
x = 20
print(a(10)) # 20
# 循环里同理,n=n 把每轮的值固定下来
funcs = [lambda x, n=n: x + n for n in range(5)]
print([f(0) for f in funcs]) # [0, 1, 2, 3, 4]
面向对象的几个内置方法
__str__()把实例转成给人看的字符串;没定义就退回用__repr__()。__repr__()把实例转成字符串,惯例是让eval(repr(x)) == x成立。__format__()让对象支持format()和字符串格式化里的自定义格式码。
_formats = {
'ymd': '{d.year}-{d.month}-{d.day}',
'mdy': '{d.month}/{d.day}/{d.year}',
'dmy': '{d.day}/{d.month}/{d.year}',
}
class Date:
def __init__(self, year, month, day):
self.year, self.month, self.day = year, month, day
def __repr__(self):
return f'Date({self.year!r}, {self.month!r}, {self.day!r})'
def __str__(self):
return f'({self.year}, {self.month}, {self.day})'
def __format__(self, code):
if code == '':
code = 'ymd'
return _formats[code].format(d=self)
d = Date(2020, 2, 25)
print(format(d)) # 2020-2-25
print(format(d, 'mdy')) # 2/25/2020
@property 把方法当属性用,配 @x.setter 做读写:
class Dog:
def __init__(self):
self._age = 0
self._name = None
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
常用标准库
collections
namedtuple 给元组的字段起名字,_replace 改部分字段:
from collections import namedtuple
Stock = namedtuple('Stock', ['name', 'date', 'price'])
s = Stock('ACME', '2012-07-01', 100)
s._replace(price=123.45)
s.name # 'ACME'
Counter 统计可哈希元素出现次数,most_common(n) 取前 n 多:
from collections import Counter
words = ['look', 'into', 'my', 'eyes', 'look', 'into', 'eyes', 'the', 'eyes']
Counter(words).most_common(3) # [('eyes', 3), ('look', 2), ('into', 2)]
ChainMap 把多个字典逻辑上拼成一个(并不真合并):重复键取第一个出现的;更新/删除只作用于第一个字典。
from collections import ChainMap
a = {'x': 1, 'z': 3}
b = {'y': 2, 'z': 4}
c = ChainMap(a, b)
c['z'] # 3,取第一个字典里的
del c['x'] # 删的是 a 里的 x
operator
itemgetter 按字典键取值,常用作排序 key,支持多键:
from operator import itemgetter
rows = [
{'fname': 'Brian', 'uid': 1003},
{'fname': 'David', 'uid': 1002},
]
sorted(rows, key=itemgetter('uid'))
sorted(rows, key=itemgetter('uid', 'fname'))
attrgetter 同理,取的是对象属性。配 itertools.groupby 按某字段分组(分组前要先按该字段排序):
from itertools import groupby
rows.sort(key=itemgetter('date'))
for date, items in groupby(rows, key=itemgetter('date')):
print(date, list(items))
itertools
compress 用一个布尔序列当掩码筛选:
from itertools import compress
addresses = ['5412 N CLARK', '5800 E 58TH', '1060 W ADDISON']
counts = [0, 10, 7]
more5 = [n > 5 for n in counts]
list(compress(addresses, more5)) # ['5800 E 58TH', '1060 W ADDISON']