Python备忘
主要用于记录一些高级使用技巧
参考:
包管理
使用pip
使用pip+国内源安装python包
1
2
pip install <some-package> -i https://pypi.tuna.tsinghua.edu.cn/simple/ # 使用清华源
pip install <some-package> -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn # 使用清华源,同时忽略SSL证书
常用的国内源:
清华:https://pypi.tuna.tsinghua.edu.cn/simple/
阿里云:http://mirrors.aliyun.com/pypi/simple/
中国科技大学:https://pypi.mirrors.ustc.edu.cn/simple/
华中科技大学:http://pypi.hustunique.com/simple/
上海交通大学:https://mirror.sjtu.edu.cn/pypi/web/simple/
豆瓣:http://pypi.douban.com/simple/ # 可能已经无了
异步IO
1. 协程(Couroutine)
协程是一种用户态的轻量级线程,执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
协程通过async
和await
关键字实现,async
定义一个协程,await
用于挂起执行。(Python3.5引入)
参考: 协程
1
2
3
4
5
6
7
8
9
10
11
12
import asyncio
async def hello(): # 定义一个协程
print("Hello, world!")
r = await asyncio.sleep(1) # `await`用于挂起协程
print("Hello, again!")
if __name__ == "__main__":
loop = asyncio.get_event_loop() # 获取一个事件循环
loop.run_until_complete(hello())
loop.close()
一个更复杂的例子:
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
import asyncio
async def wget(host):
print(f"wget {host}...")
# 连接80端口:
reader, writer = await asyncio.open_connection(host, 80)
# 发送HTTP请求:
header = f"GET / HTTP/1.0\r\nHost: {host}\r\n\r\n"
writer.write(header.encode("utf-8"))
await writer.drain()
# 读取HTTP响应:
while True:
line = await reader.readline()
if line == b"\r\n":
break
print("%s header > %s" % (host, line.decode("utf-8").rstrip()))
# Ignore the body, close the socket
writer.close()
await writer.wait_closed()
print(f"Done {host}.")
async def main():
await asyncio.gather(wget("www.sina.com.cn"), wget("www.sohu.com"), wget("www.163.com"))
asyncio.run(main())
或者通过yield实现,yield可以返回一个值,也可以接收一个值。(Python2.5引入)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def consumer():
r = ""
while True:
n = yield r
if not n:
return
print("[CONSUMER] Consuming %s..." % n)
r = "200 OK"
def produce(c):
c.send(None)
n = 0
while n < 5:
n = n + 1
print("[PRODUCER] Producing %s..." % n)
r = c.send(n)
print("[PRODUCER] Consumer return: %s" % r)
c.close()
if __name__ == "__main__":
c = consumer()
produce(c)
高级特性/包
关于修饰器typing.overload
注意: 重载与多态的区别
由于python是动态类型语言,不支持函数重载,但是可以通过typing.overload
实现函数重载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from typing import overload
class A:
@overload
def foo(self, x: int) -> int:
pass
@overload
def foo(self, x: str) -> str:
pass
def foo(self, x):
if isinstance(x, int):
return x + 1
elif isinstance(x, str):
return x + "!"
else:
raise TypeError("x must be int or str")
关于修饰器classmethod和staticmethod
区别:
@classmethod
修饰的方法,第一个参数是类对象,通常命名为cls
,可以通过类对象调用,也可以通过实例对象调用。该方法可以访问类属性,不能访问实例属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
@classmethod
def foo(cls):
print(cls)
A.foo()
# output: >>> <class '__main__.A'>
# -------------------------
# 常用于定义工厂方法:
class B:
def __init__(self, value):
self.value = value
@classmethod
def create(cls, value):
return cls(value)
b = B.create(10)
# -------------------------
@staticmethod
修饰的方法,不需要传入类对象或实例对象,通常命名为self
,可以通过类对象调用,也可以通过实例对象调用。该方法不能访问类属性,也不能访问实例属性。
1
2
3
4
5
6
class A:
@staticmethod
def foo():
print("Hello, world!")
A.foo()
# output: >>> Hello, world!
内置全局变量
vars()
:以字典方式返回内置全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
print(vars()) # 返回当前模块的属性和属性值
>>> {
'__name__': '__main__', # 模块名
'__doc__': None, # 模块的文档字符串
'__package__': None, # 包名
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f8b1b3b3b50>, # 模块加载器
'__spec__': None, # 模块的规范
'__annotations__': {}, # 类型注解
'__builtins__': <module 'builtins' (built-in)>, # 内置函数
'__file__': '/path/to/module.py', # 模块路径
'__cached__': None # 导入模块的缓存路径
}
__name__
: 当模块被直接运行时,__name__
的值为__main__
,当模块被导入时,__name__
的值为模块名。__file__
: 当模块被导入时,__file__
的值为模块的路径,当模块被直接运行时,__file__
的值为__main__
。1 2
import os print(os.path.abspath(__file__)) # /path/to/module.py
__doc__
: 获取文件的文档字符串。1 2 3 4 5
def foo(): """This is a function""" # __doc__的值为该字符串 pass print(foo.__doc__) # This is a function
__all__
: 控制模块导入时的行为,当from module import *
时,只导入__all__
中的变量。__package__
: 获取导入文件的路径,多层目录以点分割,注意:对当前文件返回None__path__
: 模块的路径。__cached__
: 缓存路径。
反射
反射是指程序可以访问、检测和修改它本身状态或行为的一种能力。
getattr(object, name[, default])
: 获取对象的属性,如果属性不存在,返回默认值。setattr(object, name, value)
: 设置对象的属性。hasattr(object, name)
: 判断对象是否有指定的属性。delattr(object, name)
: 删除对象的属性。vars(object)
: 以字典方式返回对象的属性和属性值。dir(object)
: 返回对象的属性列表。type(object)
: 返回对象的类型。isinstance(object, class)
: 判断对象是否是指定类的实例。issubclass(class, classinfo)
: 判断类是否是另一个类的子类。object.__dict__
: 返回对象的属性字典。
包:importlib
importlib
模块提供了一些有用的函数,例如importlib.import_module
可以动态导入模块。
1
2
3
4
5
6
import importlib
# 动态导入模块中的函数
module = importlib.import_module("os") # 等价于import os
print(module.__dict__) # 返回模块的属性和属性值
包:functools
functools
模块提供了一些有用的函数,例如functools.partial
可以固定函数的部分参数,返回一个新的函数。
1
2
3
4
5
6
7
import functools
def add(a, b):
return a + b
add_1 = functools.partial(add, 1)
print(add_1(2)) # 3
包:weakref
关于深拷贝和浅拷贝: 深拷贝和浅拷贝的区别在于,浅拷贝只拷贝对象的引用,而深拷贝会拷贝对象的所有内容。 在Python赋值中,不明确区分深拷贝和浅拷贝,一般对静态数据类型(如int, str)进行赋值时,会进行深拷贝,对于动态数据类型(如list, dict)进行赋值时,会进行浅拷贝。
这个模块是我在翻torch源码时发现的,torch中的
torch.utils.hooks
模块中创建hook
的handle
对象时使用
weakref
模块可以创建弱引用,弱引用不会增加对象的引用计数,当对象的引用计数为0时,对象会被销毁。
该模块可以用于解决循环引用问题,例如一个对象A引用了对象B,对象B又引用了对象A,这样两个对象的引用计数永远不会为0,导致内存泄漏。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import weakref
class A:
def __init__(self, value):
self.value = value
def __repr__(self):
return str(self.value)
a = A(10)
d = weakref.WeakValueDictionary() # 创建一个弱引用字典
d["a"] = a # 将对象a加入字典
print(d["a"]) # 10
del a # 删除对象a
print(d["a"]) # None
包: dataclasses
极为方便的数据类,用于创建不可变的数据类,可以自动实现__init__
、__repr__
、__eq__
等方法。
1
2
3
4
5
6
7
8
9
10
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
z: int = 0
p = Point(1, 2)
print(p) # Point(x=1, y=2,z=0)
杂记
str.encode()和bytes.decode()是互逆操作,str.encode()将字符串编码为字节,bytes.decode()将字节解码为字符串。
1
2
3
4
5
6
s = "Hello, world!"
b = s.encode("utf-8")
print(b) # b'Hello, world!'
print(b.decode("utf-8")) # Hello, world!
python的复杂列表推导式:
1
2
3
4
5
6
7
8
9
10
11
li = [i for i in range(5) for j in range(10) for k in range(20)]
# 等价于
li = []
for i in range(5):
for j in range(10):
for k in range(20):
li.append(i)
关于运算符@: Python3.5新增的矩阵乘法运算符, torch.tensive @ torch.tensive
等价于torch.matmul(torch.tensive, torch.tensive)
关于torch的多维张量的乘法: 前几个维度可以当作batch维度,最后两个维度可以当作矩阵维度,这样可以实现batch矩阵乘法
1
2
3
4
5
6
7
8
9
10
11
12
13
import torch
a = torch.randn(3, 4, 5)
b = torch.randn(3, 5, 4)
c = a @ b
print(c.size()) # torch.Size([3, 4, 4])
#-------------------------
a = torch.randn(3, 4, 5, 6)
b = torch.randn(3, 5, 6, 4)
c = a @ b
print(c.size()) # torch.Size([3, 4, 5, 4])
nn.Linear(in_features, out_features)可以处理多维的tensor,只要最后一个维度是in_features,前面的维度可以当作batch维度
1
2
3
4
5
6
import torch
a = torch.randn(3, 4, 5)
linear = torch.nn.Linear(5, 6)
b = linear(a)
print(b.size()) # torch.Size([3, 4, 6])