note-python3.6

参考资料

资料名 链接 作者 备注
Python文档 https://www.rddoc.com/doc/Python/3.6.0/zh/
python3官方教程 http://www.pythondoc.com/pythontutorial3/index.html
interview_python https://github.com/taizilongxu/interview_python/blob/master/Readme.md taizilongxu
Effective Python https://book.douban.com/subject/26709315/ Brett Slatkin ISBN 978-7-111-52355-0
流畅的Python http://www.ituring.com.cn/book/1564 Luciano Ramalho ISBN 978-7-115-45415-7
python cookbook https://python3-cookbook.readthedocs.io/zh_CN/latest/ David Beazley, Brian K. Jones v.3.0.0
pythontutor http://pythontutor.com/visualize.html 实用工具,非教程.对理解深拷贝浅拷贝很有用

格式化输出

format()

参考

1
2
3
4
i = "my name is {name}"
i = i.format(name="hg")
print(i)
#>>> my name is hg
1
2
3
4
name = "hg"
i = f"my name is {name}"
print(i)
#>>> my name is hg

感觉还是f前缀好用

格式化字符串

在数字或字符串后跟冒号,可以格式化字符串.

1
2
3
4
5
6
7
8
9
10
print(f"|{'qcdxhg':10}|")
#>>> |qcdxhg |
# 冒号后跟数字,表示占用多大的位置.超过最大长度不作截取.
print(f"|{'qcdxhg':10.4}|{'qcdxhg':10.8}|{10 + math.pi:10.5}|{10 + math.pi:.5}|")
#>>> |qcdx |qcdxhg | 13.142|13.142|
# 冒号后面数字的小数部分,表示字符串最大长度,截去超过长度的部分,如果被截取的是小数,同时还会四舍五入,并且小数点计入总长长度计算,不计入截取位数时的计算
# 数字向右对齐,字符串向左对齐
print(f"|{10 + math.pi:10.3f}|")
#>>> | 13.142|
# 内容是数字时,冒号后面的小数部分的后面跟个f,表示保留小数后几位(依然四舍五入)
1
2
3
4
5
6
7
#除了用冒号以外,还可以用just函数来实现字符串占位长度(限字符串)
print(f"|{'123'.ljust(10)}|{'123'.center(10)}|{'123'.rjust(10)}|")
#>>> |123 | 123 | 123|

# str.zfill(x)向数字字符串左侧填充0,到达长度为x.此时"-"和"."(负号和小数点)均计数
print(f"|{str(-123.54).zfill(10):15}|")
#>>>|-000123.54 |

数字文字

可以添加下划线增加可读性

1
2
3
4
5
6
7
i = 1_000
print(i)
#>>> 1000
print(str(i))
#>>> 1000
print(repr(i))
#>>> 1000

重载运算符

运算符 运算 正向方法 反向方法 就地方法
+ 取正 __pos__
- 取负 __neg__
~ 按位取反 __invert__
== 相等 __eq__
!= 不等 __ne__
> 大于 __gt__ __lt__
< 小于 __lt__ __gt__
>= 大于等于 __ge__ __le__
<= 小于等于 __le__ __ge__
+ 加法 __add__ __radd__ __iadd__
- 减法 __sub__ __rsub__ __isub__
* 乘法 __mul__ __rmul__ __imul__
/ 除法 __truediv__ __rtruediv__ __itruediv__
// 整除 __floordiv__ __rfloordiv__ __ifloordiv__
% 取余 __mod__ __rmod__ __imod__
divmod() 整除并取余 __divmod__ __rdivmod__ __idivmod__
**, pow() 取幂 __pow__ __rpow__ __ipow__
@ 矩阵乘法 __matmul__ __rmatmul__ __imatmul__
& 按位与 __and__ __rand__ __iand__
| 按位或 __or__ __ror__ __ior__
^ 按位异或 __xor__ __rxor__ __ixor__
<< 按位左移 __lshift__ __rlshift__ __ilshift__
>> 按位右移 __rshift__ __rrshift__ __irshift__

不支持计算时,函数返回NotImplement.
分派机制见 流畅的python p312

切片

流畅的python p234解释了原理
a[i:j]表示a中的[i,j)
a[::-1] 将a倒叙.实际上用到第三个参数的时候,就是将a按每x位取一位
除了基本切片操作,还有somestr[start: end :stride],实现步进式切割,即从每n个元素中提取1个出来.

1
2
3
4
5
6
7
a = "abcdefgh"
print(a[::2])
#>>> aceg
print(a[::-1])
#>>> hgfedcba
# 通过[::-1]可实现倒序排列
# effective python建议不要指定了start或end的同时指定stride,因为难以理解. (可以分开写)

切片放在赋值语句的左边或者作为del操作的对象,可以轻松地操作序列

1
2
3
4
5
6
7
8
9
10
11
12
13
l = list(range(10))
l[2:5] = [20, 30]
print(l)
#>>> [0, 1, 20, 30, 5, 6, 7, 8, 9]
del l[5:7]
print(l)
#>>> [0, 1, 20, 30, 5, 8, 9]
l[::2] = [22, 33, 44, 55]
print(l)
#>>> [22, 1, 33, 30, 44, 8, 55]
l[2:5] = [100]
print(l)
#>>> [22, 1, 100, 8, 55]

encode和decode

str.encode()可以用于使用给定的文本编码将str转换为bytes,
bytes.decode()可以用于实现相反.

元组

元组与list类似,不同之处:

  1. 元组的元素不能修改
  2. 元组可以作为map的键,list不可以
  3. 元组的拆包
1
2
3
4
5
6
7
8
9
# 一个元组只有一个元素时,一定要在这个元素后加逗号
tup1=("sqh",)
tup2=("sqh","qjb")
print(tup1)
#>>> ('sqh',)
print(tup2)
#>>> ('sqh', 'qjb')
print(tup2[0])
#>>> sqh
1
2
3
4
5
t = ("ddd","sss")
m = {t:"aaa"}
print(m[t])
#>>> aaa
#元组作为map的键
1
2
3
4
5
6
7
8
9
a, b, c = (1, 2, 3)
print(a, b, c)
#>>> 1 2 3
#元组的平行赋值就是拆包

x, y, *rest = range(5)
print(x, y, rest)
#>>> 0 1 [2, 3, 4]
#用*处理剩下的元素

具名元组

collections.namedtuple构建的类的实例所消耗的内存跟元组是一样的,因为字段名都存在对应的类里面.这个实例跟普通的对象实例比起来也要小一些,因为python不会用__dict__来存放这些实例的属性.

1
2
3
4
5
6
7
8
from collections import namedtuple

T = namedtuple("ANAMEDTUPLE","a b c d")
t = T("A","B","C","D")
print(t)
#>>> ANAMEDTUPLE(a='A', b='B', c='C', d='D')
print(f"t.a = {t.a}")
#>>> t.a = A

字典的默认值

dict.get(k,default)

dict.setdefault(self,key,value)

1
2
3
4
5
6
7
8
9
10
11
12
d = {
"a": 0
}

print(d.get("b", 255))
#>>> 255
#dict.get(key,default)实现默认值

d.setdefault("b", 233)
print(d["b"])
#>>> 233
#dict.setdefault(self,key,value)实现默认值

collections.defaultdict(type)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import collections

d2 = collections.defaultdict(list)

print(d2)
#>>> defaultdict(<class 'list'>, {})

print(d2["a"])
#>>> []
#出现了,默认值是空list

d2["b"].append(666)
#d2["b"]的默认值是[],所以可以直接append
print(d2)
#>>> defaultdict(<class 'list'>, {'a': [], 'b': [666]})
#访问过的d2["a"]也有值了

d2["c"] = 3
print(d2)
#>>> defaultdict(<class 'list'>, {'a': [], 'b': [666], 'c': 3})
#defaultdict并没有限制数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
d3 = collections.defaultdict(tuple)
print(d3["a"])
#>>> ()

d4 = collections.defaultdict(int)
print(d4["a"])
#>>> 0

d5 = collections.defaultdict(bool)
print(d5["a"])
#>>> False

#各个数据类型的默认值貌似是bool值为False的值

missing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class strDict(dict):
def __init__(self,*args,**kwargs):
super(strDict, self).__init__(*args,**kwargs)

def __missing__(self, key):
if isinstance(key,str):
raise KeyError(key)
#如果找不到的键本来就是str,则报错

else:
return self[str(key)]
#如果找不到的键不是str,则找str化的键

def __contains__(self, key):
return key in self.keys() or str(key) in self.keys()
#先按照原本的值查找,找不到再str之后再找

a = strDict()
a["123"] = 233
#存值的时候,键是字符串

print(a[123])
#>>> 233
#取值的时候用int也能取出.

__getattr__,__getattribute__,__setattr__控制对象属性

effective python 第97页
如果类定义了__getattr__,同时系统在该类对象的实例字典(instance.__dict__)中又找不到待查属性,那么系统将调用__getattr__
与__getattr__不同,每次在对象上调用hasattr和getattr时.__getattribute__都会执行
__setattr__可以向对象中新添属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class O:
def __init__(self):
self.a = 1
self.b = 2

def __getattr__(self, item):
print(f"""not has attr named '{item}'""")

def __getattribute__(self, item):
print(f"""try to accept to '{item}'""")
return super().__getattribute__(item)

o = O()
o.__setattr__("c",3)
#>>> try to accept to '__setattr__'
setattr(o,"d",4)
print(o.__dict__)
#>>> try to accept to '__dict__'
#>>> {'a': 1, 'b': 2, 'c': 3, 'd': 4}
o.e
#>>> try to accept to 'e'
#>>> not has attr named 'e'

排序

sorted(iterable,key,reverse)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class S:
def __init__(self, v: int):
self.v = v


l = [
S(8),
S(6),
S(1),
S(2)
]

for s in l:
print(s.__dict__)
#>>> {'v': 8}
# {'v': 6}
# {'v': 1}
# {'v': 2}

l = sorted(l, key=lambda x: x.v)
for s in l:
print(s.__dict__)
#>>> {'v': 1}
# {'v': 2}
# {'v': 6}
# {'v': 8}

list.sort()是就地修改

bisect

bisect.bisect 返回一个数插入有序列表的位置,不会将这个数插入数组
bisect.insort 将一个数插入有序列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import bisect
x = list(range(20))
x = x[::3]
print(x)
#>>> [0, 3, 6, 9, 12, 15, 18]

i0 = bisect.bisect(x, 2)
print(i0)
#>>> 1
#返回应该插入的位置
print(x)
#>>> [0, 3, 6, 9, 12, 15, 18]
#原列表未被修改

i1 = bisect.insort(x, 2)
print(i1)
#>>> None
#返回None
print(x)
#>>> [0, 2, 3, 6, 9, 12, 15, 18]
#原列表已被修改

array

流畅的python P41

浅拷贝与深拷贝

流畅的python p187

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import copy
l0 = [233,255]

l1 = [0,1,2,l0,5]
#引用了l0

l2 = l1
#引用赋值

l3 = list(l1)
#构造函数实现浅拷贝

l4 = l1[:]
#list的解构(?)实现浅拷贝

l5 = copy.copy(l1)
#copy模块浅拷贝

l6 = copy.deepcopy(l1)
#copy模块深拷贝

print(l2 is l1)
#>>> True
print(l3 is l1)
#>>> False
print(l4 is l1)
#>>> False
print(l5 is l1)
#>>> False
print(l6 is l1)
#>>> False

l0.append(798)
print(l2)
#>>> [0, 1, 2, [233, 255, 798], 5]
print(l3)
#>>> [0, 1, 2, [233, 255, 798], 5]
print(l4)
#>>> [0, 1, 2, [233, 255, 798], 5]
print(l5)
#>>> [0, 1, 2, [233, 255, 798], 5]
#因为是浅拷贝,拷贝了l1中对l0的引用,所以l0更改了,l3,l4,l5也会更改

print(l6)
#>>> [0, 1, 2, [233, 255], 5]
#l6是深拷贝,没有拷贝l1对l0的引用,而是新创建了一份和l0一样的列表,因此没有改变

pythontutor执行可以清晰地看懂原理
关于浅拷贝的时候,什么时候传值,什么时候传递引用,好像是可变类型传递引用(?)(tuple深拷贝的时候,传递的还是引用)
另外,要避免可变类型作为函数参数的默认值.(流畅的python p191,还没认真看)

del和垃圾回收

python手册,data model一章中的一句话:对象绝不会自行销毁;然而,无法得到对象时,可能会被当做垃圾回收.
我对这句话的理解是,如果没有变量引用一个对象,那么这个对象就会被回收.而del则是删除变量对对象的引用.

弱引用

流畅的python p196
弱引用不会增加对象的引用数量,但是着实可以访问到对象.对象存在时,可以通过弱引用访问到对象;对象引用数量为0,被回收后,通过弱引用访问到的值为None.
不是每个python对象都可以作为弱引用的目标.如基本的list和dict的实例,不能做弱引用所指对象.而他们的子类可以,如:

1
2
class myList(list):
pass

yield

yield是协程的体现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1

f = fab(5)
print(f)
#>>> <generator object fab at 0x056AF0F0>
print(type(f))
#>>> <class 'generator'>
for n in f:
print(n,end="|")
#>>> 1|1|2|3|5|
```
私自理解为,yield可以使一个函数先后返回几个不同的值

import requests

def findBiliAidInURL(url: str):
# 在第一个yield前,找出av号然后yield出去
aid = url.split(“av”)[1]
aid = aid.split("/")[0]
yield aid

# 在第二个yield前,通过网络IO请求视频某些信息,然后yield出去
api = f"https://api.bilibili.com/x/web-interface/archive/stat"
url_ = f"https://www.bilibili.com/video/av{aid}"
headers = {
    "refer": url_,
    "Origin": "https://www.bilibili.com",
    "Host": "api.bilibili.com",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36",
}
res = requests.get(api, params={"aid": aid}, headers=headers)
res.encoding = "utf8"
yield res.text

if name == ‘main’:
res = findBiliAidInURL(“https://www.bilibili.com/video/av42684155”)
print(res)
# >>> <generator object findBiliAidInURL at 0x0352F0F0>
print(next(res))
# >>> 42684155
print(next(res))
# 在打印出下面这条信息前等待了一个网络IO的时间
# >>> {“code”:0,“message”:“0”,“ttl”:1,“data”:{“aid”:42684155,“view”:129167,“danmaku”:477,“reply”:553,“favorite”:4477,“coin”:7181,“share”:1773,“like”:7452,“now_rank”:0,“his_rank”:0,“no_reprint”:1,“copyright”:1}}

1
2
3
4

## is 和 ==
is比较二者内存地址,\=\=比较二者值.与js的\=\=和\=\=\=不同,js的\=\=和\=\=\=的差异在于是否比较等号两边的数据类型.id(x)获取x内存位置
用is判断None:

x = None
x is None

True

1
2

## 列表推导式

import math
a = [x for x in range(10) if math.sqrt(x) > 2]
print(a)
#>>> [5, 6, 7, 8, 9]
#for的时候判断

a = (“a”,“b”,“c”)
b = (“一”,“二”,“三”)
c = [(x,y) for x in a for y in b]
print©
#>>> [(‘a’, ‘一’), (‘a’, ‘二’), (‘a’, ‘三’), (‘b’, ‘一’), (‘b’, ‘二’), (‘b’, ‘三’), (‘c’, ‘一’), (‘c’, ‘二’), (‘c’, ‘三’)]
#列表推导式中多个循环实现笛卡尔积

c = [(x,y) for x in a
for y in b]
#多个循环时加上换行更易读

1
## 字典推导

class A:
def init(self, a: int):
self.x = a

ls = []
for x in range(5):
ls.append(A(int(random.random() * 100)))

ds = {a.x: a.dict for a in ls}
print(ds)

1
2
3
4
## 生成器表达式  
列表推导有个缺点:在推导过程中,对于输入序列中的每个值来说,都要创建一个仅含一项元素的全新列表,当输入数据比较大时容易占内存较大.
生成器表达式则不会有这种问题.
生成器表达式生成一个\<class'generator'\>,通过next(generator)取值.相对列表推导式的优点在于可以按需取用,避免过大占用内存

列表推导式

a = [x for x in range(100)]
print(a)

生成器表达式

b = (x for x in range(100))
try:
while True:
print(next(b),end=",")
except:
pass

a = sum(x for x in range(101))
print(a)
#>>> 5050
#如果生成器表达式是一个函数调用过程中的唯一参数,则不需要额外的括号
a = sum((x for x in range(101)))
print(a)
#>>> 5050
#效果一样的

1
2
## 可迭代对象
可迭代对象要实现\_\_iter\_\_函数.如果实现了\_\_next\_\_函数,则\_\_iter\_\_函数可return self;否则要return一个iterable,如列表,生成器等

class R:
def init(self):
self.x = 0

def __iter__(self):
    return self
    #只要实现了__iter__函数,实例自身就是一个iterable.被遍历的时候会调用__next__函数

def __next__(self):
    if self.x > 5:
        raise StopIteration
    self.x += 1
    return self.x

for i in R():
print(i)
#>>> 1

2

3

4

5

6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
```
class I:
def __init__(self):
pass

def __iter__(self):
return (int(random.random() * 100) for i in range(5))
#返回生成器

for i in I():
print(i)
#>>> 33
# 50
# 97
# 65
# 60

对象的__format__

对象的格式化输出

1
2
3
4
5
6
7
8
9
10
11
12
class People:
def __init__(self,name:str,age:int):
self.name = name
self.age = age

def __format__(self, format_spec):
return format_spec.replace("%n",self.name).replace("%a",str(self.age))

p = People("ababa",15)
f = format(p,"%n ages %a")
print(f)
#>>> ababa ages 15

对象的__slots__

定义了__slots__属性的目的是告诉解释器:“这个类中的所有实例属性都在这儿了”,这样,python会在各个实例中使用类似元组的数据结构存储实例变量,从而避免使用消耗内存的__dict__
有数以百万实例存在的时候,使用__slots__可以节省大量内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class A:
__slots__ = ("__x")
#__slots__是一个iterable,用元组好点
#只需声明实例变量名即可,不用声明方法名.

def __init__(self, a: int):
self.__x = a

def addA(self):
self.__x += 1

@property
#通过property声明的属性不用写在__slots__里
def x(self):
return self.__x

@x.setter
def x(self, x):
self.__x = x

@x.getter
def x(self):
return self.__x

a = A(5)
print(a.x)
#>>> 5

a.x = 6
print(a.x)
#>>> 6

a.addA()
print(a.x)
#>>> 7

print(vars(a))
#>>> TypeError: vars() argument must have __dict__ attribute
#不能使用vars()或者__dict__获得其属性了.

有需求的话,也可以在__slots__里写上"dict",实例也会在__dict__里存储属性.不过这样做就达不到

装饰器

装饰器能在被装饰的函数执行之前和之后分别执行一些代码,可以做到访问及修改原函数的参数和返回值,以实现约束语义,调试函数,注册函数等目标
装饰器是一个函数,接受被装饰的函数做参数,里面定义一个新的函数通常是在新的函数里执行一次被装饰的函数,然后返回新定义的函数.

1
2
3
4
5
6
7
8
9
10
11
12
def aDecorator(aFn):
def aWapper():
return "-------" + aFn() + "----------"
return aWapper

@aDecorator
def a():
return "aaa"

s = a()
print(s)
#>>> -------aaa----------

参数化装饰器

参数化的装饰器其实不是装饰器,而是装饰器的工厂函数,返回一个装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#装饰器函数工厂函数,接受参数
def aDecorateFactory(anArg="nothing"):

#装饰器函数,接受被装饰的函数做参数
def decorate(func):

#装饰后的函数,接受任意参数以传给被装饰的函数
def retfunc(*args):

#执行装饰器附加代码
print(anArg,end="")

res = func(*args)
#执行被装饰的函数,装饰后的函数返回其执行结果
return res

return retfunc
#装饰器函数返回装饰后的函数

return decorate
#工厂函数返回装饰器

@aDecorateFactory("something")
def printsth(sth):
print(sth)

printsth("666")
#>>> something666

装饰器的问题

修饰器返回了一个新的函数,并非原函数.两者在使用之外的地方可能会有不同.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def addA(s:str)->str:
return s + "aaaaaaa"

print(addA)
#>>> <function addA at 0x048ACFA8>
print(addA.__name__)
#>>> addA

def aDecorator(func):
def aWapper(*args,**kwargs):
res = func(*args,**kwargs)
print(res)
return res
return aWapper

@aDecorator
def addA2(s:str)->str:
return s + "aaaaaaa"

print(addA2)
#>>> <function aDecorator.<locals>.aWapper at 0x048D3030>
print(addA2.__name__)
#>>> aWapper

这个问题可以用functools模块的@functools.wraps()修饰器解决.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import functools

def aDecorator2(func):
@functools.wraps(func)
#重点在这行
def aWapper(*args,**kwargs):
res = func(*args,**kwargs)
print(res)
return res
return aWapper

@aDecorator2
def addA3(s:str)->str:
return s + "aaaaaaa"

print(addA3)
#>>> <function addA3 at 0x045B2108>
print(addA3.__name__)
#>>> addA3

标准库中的装饰器:

@property

廖雪峰教程 | effective python 第84页
读取或修改对象的属性时,可以执行其他操作.可用于赋值的验证.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Candies:
def __init__(self):
self._peoples = 1
self._candies = 10
self._candiesPerple = 10

@property
def peoples(self):
return self._peoples

@peoples.setter
def peoples(self,peoples):
self._peoples = peoples
self._candiesPerple = self._candies // self._peoples
print("设置peoples")

@peoples.getter
def peoples(self):
print("读取peoples")
return self._peoples

c = Candies()
c.peoples = 5
#>>> 设置peoples
p = c.peoples
#>>> 读取peoples
print(p)
#>>> 5

@staticmethod和@classmethod

Python类中有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def fn(x):
print(f"executing fn({x})")

class A(object):

# 实例方法,参数带有self
def fn(self,x):
print(f"executing fn({self},{x})")

# 类方法,参数带有cls,感觉是拿来更改类变量的
@classmethod
def class_fn(cls,x):
print(f"executing class_fn({cls},{x})")

# 静态方法
@staticmethod
def static_foo(x):
print(f"executing static_foo({x})")

a=A()

a.fn(3)
#>>> executing fn(<__main__.A object at 0x057A7AD0>,3)
a.class_fn(4)
#>>> executing class_fn(<class '__main__.A'>,4)
a.static_foo(5)
#>>> executing static_foo(5)
A.fn(6)
# 报错
A.fn(7,8)
#>>> executing fn(7,8)
# 实际是把7传给self,self作为一个普通的参数
A.class_fn(9)
#>>> executing class_fn(<class '__main__.A'>,9)
A.static_foo(10)
#>>> executing static_foo(10)

类方法可以拿来做备选的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A:
def __init__(self,a):
self.x = a

@classmethod
def from10(cls):
return cls(10)

a = A(5)
print(a.__dict__)
#>>> {'x': 5}

b = A.from10()
print(b.__dict__)
#>>> {'x': 10}

functools.lru_cache

functools.lru_cache

functools.wraps()

装饰器的问题

函数注释

python文档
就是对函数参数和返回值的注释.

1
2
3
4
def f(a : str, b : "b is a string", c : int = 5) -> str:
return str(a) + str(b) + str(c)
print(f(123,"sss"))
#>>> 123sss5

时间计算

参考

python包发布

python包发布
官方文档

pickle

python文档
自己对其的理解,限于可以将python对象存储在硬盘中,日后可读取

较实用的python内置方法

divmod(num,num)

把除法和求余结合起来,返回a // b, a % b

enumerate(list)

遍历列表,同时列出下标

all(iterable)

一个iterable全部元素为True,则返回True,否则返回False

any(iterable)

一个iterable全部元素为False,则返回False,否则返回True

repr(object)

将对象转化为供解释器读取的形式。
本人理解:repr将对象转化为供程序理解的字符串,str将对象转化为供人类理解的字符串

1
2
3
4
5
6
i = "my name is sqh\ni age 20"
print(str(i))
#>>> my name is sqh
i age 20
print(repr(i))
#>>> 'my name is sqh\ni age 20'
1
2
3
4
5
6
7
8
9
10
11
# 对于dict和list
i = {"name":"sqh","age":20}
print(i)
#>>> {'name': 'sqh', 'age': 20}
print(str(i))
#>>> {'name': 'sqh', 'age': 20}
print(repr(i))
#>>> {'name': 'sqh', 'age': 20}
print(repr(i)[0])
#>>> {
# repr后转换为字符串,打印字符串的0位置的字符

map(function,iterable)

map() 会根据提供的函数对指定序列做映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
a = [
{"id": 0, "name": "foo"},
{"id": 1, "name": "bar"}
]
b = map(lambda x: x["name"], a)
print(b)
# 返回迭代器
#>>> <map object at 0x02D3E150>
print(list(b))
#>>> ['foo', 'bar']

import math
c = [2,3,4]
d = map(math.sqrt,c)
print(d)
#>>> <map object at 0x00336B50>
print(list(d))
#>>> [1.4142135623730951, 1.7320508075688772, 2.0]

print("\n".join(map(lambda a:" | ".join(map(lambda b:f"{a} * {b} = {a * b}",range(1,a + 1))),range(1,10))))
#一行输出九九乘法表

tuple(iterable)

tuple() 函数将列表(?)转换为元组

1
2
3
4
5
6
7
8
9
10
11
12
l = ["a", "b", "c"]
print(tuple(l))
#>>> ('a', 'b', 'c')

d = {
"k1": "v1",
"k2": "v2",
"k3": "v3"
}
print(tuple(d))
#>>> ('k1', 'k2', 'k3')
#dict 返回dict的键

filter(function,list)

筛选list的元素

1
2
3
4
5
6
import math
l = filter(lambda x:not math.sqrt(x) % 1, range(1, 101))
print(l)
#>>> <filter object at 0x01B66B50>
print(list(l))
#>>> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

vars(object)

vars函数返回对象object的属性和属性值的字典对象。实现方式好像是 return obj.__dict__?(所以还是直接obj.__dict__吧…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A:
x = 1
def __init__(self):
self.y = 2

def __getattribute__(self, item):
print(f"""try to accept to '{item}'""")
return super().__getattribute__(item)

a = A()
print(vars(a))
#>>> try to accept to '__dict__'
#>>> {'y': 2}
#只返回object的属性

reversed(list)

倒序列表

1
2
3
4
5
6
7
8
9
10
11
12
a = ["a", "s", "d", "f", "g"]
print(list(reversed(a)))
#>>> ['g', 'f', 'd', 's', 'a']
#reversed返回一个迭代器

a.reverse()
print(a)
#>>> ['g', 'f', 'd', 's', 'a']
# a.reverse()直接将a倒序,而不是a = a.reverse()

#但是感觉没必要啊...切片就好了
print(a[::-1])

zip()

这玩意不知怎么描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a = "qwertyui"
b = "asdfghjk"
c = "zxcvbnm,"
print(list(zip(a,b,c)))
#>>> [('q', 'a', 'z'), ('w', 's', 'x'), ('e', 'd', 'c'), ('r', 'f', 'v'), ('t', 'g', 'b'), ('y', 'h', 'n'), ('u', 'j', 'm'), ('i', 'k', ',')]
#就是组成一个个元组

d = [
[100,200,300],
[40,50,60],
[7,8,9,1,1,1,1,1,1,1,1,1]
]
print(list(zip(*d)))
#>>> [(100, 40, 7), (200, 50, 8), (300, 60, 9)]
#将每三个数组成一个元组,可以后续传递给函数

set(iterable)

set()函数创建一个无序不重复元素集,可进行关系测试,删除重复数据,还可以计算交集,差集,并集等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
a = [1,1,2,3,4,5,1,6]

print(a)
print(set(a))
#>>> {1, 2, 3, 4, 5, 6}
#可以去重

b = [4,5,6,9,8,7,18,29,38]

# print(a|b)
#>>> TypeError: unsupported operand type(s) for |: 'list' and 'list'

print(set(a)|set(b))
#>>> {1, 2, 3, 4, 5, 6, 7, 8, 9, 38, 18, 29}

c = "qwe"
d = "asd"
# print(c|d)
#>>> TypeError: unsupported operand type(s) for |: 'str' and 'str'
print(set(c)|set(d))
#>>> {'a', 's', 'd', 'w', 'e', 'q'}

slice

创建一个切片对象,经常用到的切片参数可以用slice存给一个变量

1
2
3
4
s = slice(None, None, -1)
a = "qwer"
print(a[s])
#>>> rewq

functools

reduce(function,iterable,initial)

function接受两个参数,reduce首先把iterable的前两个元素传给函数参数,函数加工后,再把返回值和第三个元素作为两个参数传给函数参数,以此类推

1
2
3
4
5
6
7
8
9
10
from functools import reduce
l = [1, 2, 3, 4, 5, 6, 7, 8, 9]
a = reduce(lambda x,y:x*10 + y,l)
print(a)
#>>> 123456789

b = reduce(lambda x,y:x*10 + y,l,7)
print(b)
#>>> 7123456789
#如果传入了initial参数,则第一次调用function的时候,是先将initial和iterable第一个元素作为参数

partial

将函数的第一个参数冻结住

1
2
3
r = functools.partial(range,10)
print(list(r(20)))
>>> [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

operator

operator模块为多个算术运算符提供了对应的函数,从而避免lambda a, b: a * b这种平凡的匿名函数.
比如,operator.mul是乘法函数

1
2
3
4
5
6
import operator
import functools
def fact(n):
return functools.reduce(operator.mul, range(1, n + 1))
return functools.reduce(lambda a, b: a * b, range(1, n + 1))
#两行效果一样
操作 表达式 函数
加法 a + b add(a, b)
减法 a - b sub(a, b)
乘法 a * b mul(a, b)
除法 a / b div(a, b)
整除 a // b floordiv(a, b)
取余 a % b mod(a, b)
乘方 a ** b pow(a, b)
正运算 + a pos(a)
负运算 - a neg(a)
取绝对值运算 abs(a) abs(a)
按位与 a & b and_(a, b)
按位或 a | b or_(a, b)
按位异或 a ^ b xor(a, b)
按位取反 ~a invert(a)
左移位 a << b lshift(a, b)
右移位 a >> b rshift(a, b)
小于比较 a < b lt(a, b)
小于等于比较 a <= b le(a, b)
等于比较 a == b eq(a, b)
不等于比较 a != b ne(a, b)
大于等于比较 a >= b ge(a, b)
大于比较 a > b gt(a, b)

算了,不总结了,随用随查…

itemgetter()

序列,dict元素的获取

1
2
3
4
5
6
7
8
a = ["a","b","c"]
b = [2,5,7,4,8,9,6]
g = operator.itemgetter(1)

print(g(a))
#>>> b
print(g(b))
#>>> 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ls = [
[8, 5, 4, 7],
[0, 1, 2, 3],
[6, 3, 1, 5, 4],
[2, 8, 7, 4]
]

print(ls)
#>>> [[8, 5, 4, 7], [0, 1, 2, 3], [6, 3, 1, 5, 4], [2, 8, 7, 4]]

ls.sort(key=operator.itemgetter(0))
ls.sort(key=lambda l: l[0])
# 效果一样

print(ls)
#>>> [[0, 1, 2, 3], [2, 8, 7, 4], [6, 3, 1, 5, 4], [8, 5, 4, 7]]
1
2
3
4
5
6
7
8
9
ls2 = [
{"q":5},
{"q":8},
{"q":4},
{"q":7}
]
ls2.sort(key=operator.itemgetter("q"))
print(ls2)
#>>> [{'q': 4}, {'q': 5}, {'q': 7}, {'q': 8}]

attrgetter()

对象属性的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A:
def __init__(self):
self.x = int(random.random() * 100)

ls = [A() for i in range(3)]
for l in ls:
print(l.__dict__,end="")
#>>> {'x': 95}{'x': 89}{'x': 89}

ls.sort(key=operator.attrgetter("x"))
ls.sort(key=lambda a:a.x)
#效果一样

print("\n",end="")
for l in ls:
print(l.__dict__,end="")
#>>> {'x': 89}{'x': 89}{'x': 95}

lru_cache

据说是非常实用的装饰器,实现了备忘功能.这是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算.lru是"latest recently used"的缩写,表明缓存不会无限制增长,一段时间内不用的缓存条目会被扔掉.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def fibonacci(n):
t = time.perf_counter()
if n < 2:
res = n
else:
res = fibonacci(n - 2) + fibonacci(n - 1)
t1 = time.perf_counter() - t
print(f"function fibonacci({n}) has excuted in {t1}s")
return res
fibonacci(4)
#>>> function fibonacci(0) has excuted in 3.950615723213543e-07s
# function fibonacci(1) has excuted in 0.0s
# function fibonacci(2) has excuted in 3.950615723213542e-05s
# function fibonacci(1) has excuted in 0.0s
# function fibonacci(0) has excuted in 3.950615723213573e-07s
# function fibonacci(1) has excuted in 3.950615723213505e-07s
# function fibonacci(2) has excuted in 1.540740132053282e-05s
# function fibonacci(3) has excuted in 2.8444433207137508e-05s
# function fibonacci(4) has excuted in 8.414811490444843e-05s

#拿functools.lru_cache修饰后,函数调用次数减少
@functools.lru_cache()
def fibonacci(n):
t = time.perf_counter()
if n < 2:
res = n
else:
res = fibonacci(n - 2) + fibonacci(n - 1)
t1 = time.perf_counter() - t
print(f"function fibonacci({n}) has excuted in {t1}s")
return res
fibonacci(4)
#>>> function fibonacci(0) has excuted in 0.0s
# function fibonacci(1) has excuted in 3.950615723213505e-07s
# function fibonacci(2) has excuted in 5.9259235848203126e-05s
# function fibonacci(3) has excuted in 1.5802462892854156e-06s
# function fibonacci(4) has excuted in 9.362959264016094e-05s

使用lru_cache()时要加括号,因为其可以接受两个可选参数.

1
functools.lru_cache(maxsize=128,typed=false)

maxsize参数置顶存储多少个调用结果.为了得到最佳性能,maxsize应该设为2的幂.typed设为True,则把不同类型的参数得到的结果分开保存

singledispatch

单分派反函数
该装饰器可以把整体方案拆分成多个模块,使用@singledispatch装饰的普通函数会变成泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组操作(因为是单分派,所以根据第一个参数的类型,否则要是根据多个参数选择专门的函数就是多分派了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@functools.singledispatch
def str2list(obj):
pass

@str2list.register(str)
#各个专门函数用@{base_function}.register({type})装饰.
def _(aStr):
#函数名无所谓,"_"占位是个不错的选择
return list(aStr)

@str2list.register(int)
def _(anInteger):
return list(str(anInteger))

@str2list.register(tuple)
@str2list.register(list)
#指定多个类型
def _(anIterable):
return anIterable

print(str2list(123))
#>>> ['1', '2', '3']
print(str2list("abc"))
#>>> ['a', 'b', 'c']
print(str2list([",",".","/"]))
#>>> [',', '.', '/']

collections

流畅的python p65,暂不研究

itertools

上下文管理器

可以理解为,上下文管理器是一个特殊的类,包含__enter__和__exit__两个特殊方法.进入with里的语句时,执行上下文管理器的__enter__方法,离开时执行__exit__方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from datetime import datetime
class timePrint:
def __enter__(self):
global print
self.rawPrint = print
print = self.timePrint

def timePrint(self,*args,**kwargs):
timeStr = datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")
self.rawPrint(timeStr,*args,**kwargs)

def __exit__(self, exc_type, exc_val, exc_tb):
global print
print = self.rawPrint

if __name__ == '__main__':
with timePrint():
# 进入上下文管理器timePrint时执行__enter__(),print函数会被替换
print(1)
>>> [2019-02-08 02:18:07] 1

# 离开时,执行__exit__(),print函数会换回来
print(2)
>>> 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 这里是一个简陋的类似python自带的open上下文管理器的myopen上下文管理器.  
# 与上面的例子区别在于,这个上下文管理器在构建时有传入参数,且__enter__函数有返回值
class myOpen():
# 构建上下文管理器时,传入的参数还是传给__init__函数而非__enter__函数
def __init__(self, filePath):
self.filePath = filePath

def __enter__(self):
self.__F = open(self.filePath, "r", encoding="utf8")
return self.__F

def __exit__(self, exc_type, exc_val, exc_tb):
self.__F.close()


if __name__ == '__main__':
# 会将上下文管理器的__enter__的返回值传给as后面的变量
with myOpen("0.txt") as f:
print(f.read())
# >>> here's content of 0.txt

@contexmanager修饰器

@contexmanager修饰器能减少创建上下文管理器需要的样板代码量.
不用再实现一个类,定义__enter__和__exit__甚至__init__方法,只需实现一个有yield语句的生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from contextlib import contextmanager
# contextmanager在contextlib中

@contextmanager
def myOpen2(filePath):
# 定义一个函数而非一个类
# 构建上下文管理器时需要传给上下文管理器的参数直接传给本函数

__F = open(filePath, "r", encoding="utf8")
# yield前做__enter__函数需要做的事
# yield的值为__enter__函数返回的值
yield __F
# yield后做__exit__函数需要做的事
__F.close()


if __name__ == '__main__':
with myOpen2("0.txt") as f:
print(f.read())
# >>> here's content of 0.txt
# 与上面的例子一致