文章

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)

协程是一种用户态的轻量级线程,执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。

协程通过asyncawait关键字实现,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

区别:

  1. @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)
    # -------------------------
  1. @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 # 导入模块的缓存路径
    }
  1. __name__: 当模块被直接运行时,__name__的值为__main__,当模块被导入时,__name__的值为模块名。
  2. __file__: 当模块被导入时,__file__的值为模块的路径,当模块被直接运行时,__file__的值为__main__
    1
    2
    
     import os
     print(os.path.abspath(__file__)) # /path/to/module.py
    
  3. __doc__: 获取文件的文档字符串。
    1
    2
    3
    4
    5
    
     def foo():
         """This is a function""" # __doc__的值为该字符串
         pass
        
     print(foo.__doc__) # This is a function
    
  4. __all__: 控制模块导入时的行为,当from module import *时,只导入__all__中的变量。
  5. __package__: 获取导入文件的路径,多层目录以点分割,注意:对当前文件返回None
  6. __path__: 模块的路径。
  7. __cached__: 缓存路径。

反射

反射是指程序可以访问、检测和修改它本身状态或行为的一种能力。

  1. getattr(object, name[, default]): 获取对象的属性,如果属性不存在,返回默认值。
  2. setattr(object, name, value): 设置对象的属性。
  3. hasattr(object, name): 判断对象是否有指定的属性。
  4. delattr(object, name): 删除对象的属性。
  5. vars(object): 以字典方式返回对象的属性和属性值。
  6. dir(object): 返回对象的属性列表。
  7. type(object): 返回对象的类型。
  8. isinstance(object, class): 判断对象是否是指定类的实例。
  9. issubclass(class, classinfo): 判断类是否是另一个类的子类。
  10. 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模块中创建hookhandle对象时使用

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])
本文由作者按照 CC BY 4.0 进行授权