Python 编程规范
3326 字约 11 分钟
2024-12-01
Python 编程规范
Auth : 张旭
Date : 2018-04-02
Email: [email protected]
PEP8 编码规范, 以及开发中的一些惯例和建议
代码编排:
- 缩进 4 个空格, 禁止空格与 Tab 混用
- 行长 80: 防止单行逻辑过于复杂
import
- 不要使用
from xxx import *
- 顺序
- 标准库
- 第三方库
- 自定义库
- 单行不要 import 多个库
- 模块内用不到的不要去 import
- 不要使用
空格
: , ;
后面跟一个空格, 前面无空格 (行尾分号后无空格)- 二元操作符前后各一个空格, 包括以下几类:
- 数学运算符:
+ - * / // = & |
- 比较运算符:
== != > < >= <= is not in
- 逻辑运算符:
and or not
- 位运算符:
& | ^ << >>
- 数学运算符:
- 当
=
用于指示关键字参数或默认参数值时, 不要在其两侧使用空格
适当添加空行
- 函数间: 顶级函数间空 2 行, 类的方法之间空 1 行
- 函数内: 同一函数内的逻辑块之间, 空 1 行
- 文件结尾: 留一个空行 (Unix 中 \n 是文件的结束符)
注释
- 忌: 逐行添加注释,没有一个注释
- 行内注释: 单行逻辑过于复杂时添加
- 块注释: 一段逻辑开始时添加
- 引入外来算法或者配置时须在注释中添加源连接, 标明出处
- 函数和类尽可能添加
docstring
命名
- 除非在 lambda 函数中, 否则不要用 单字母 的变量名 (即使是 lambda 函数中的变量名也应该尽可能的有意义)
- 包名、模块名、函数名、方法名全部使用小写, 单词间用下划线连接
- 类名、异常名使用 CapWords (首字母大写) 的方式, 异常名结尾加
Error
或Wraning
后缀 - 全局变量尽量使用大写, 一组同类型的全局变量要加上统一前缀, 单词用下划线连接
字符串拼接尽量使用
join
方式: 速度快, 内存消耗小语意明确、直白
not xx in yy
VSxx not in yy
not a is b
VSa is not b
程序的构建
- 一个函数只做一件事情, 并把这件事做好
- 大的功能用小函数之间灵活组合来完成
- 避免编写庞大的程序, “大” 意味着体积庞大, 逻辑复杂甚至混乱
函数名必须有动词, 最好是 do_something 的句式, 或者 somebody_do_something 句式
自定义的变量名、函数名不要与标准库中的名字冲突
pip install pep8 pylint flake8
练习: 规范化这段代码
from django.conf import settings import sys, os mod=0xffffffff def foo (a , b =123 ): c= { 'x' : 111 , 'y' : 222}#定义一个字典 d =[1, 3 ,5 ] return a ,b, c def bar(x): if x%2== 0: return True
*
和**
的用法函数定义
def foo(*args, **kwargs): pass
参数传递
def foo(x, y, z, a, b): print(x) print(y) print(z) print(a) print(b) lst = [1, 2, 3] dic = {'a': 22, 'b': 77} foo(*lst, **dic)
import * 语法
from xyz import * __all__ = ('a', 'e', '_d') a = 123 b = 456 c = 'asdfghjkl' _d = [1,2,3,4,5,6] e = (9,8,7,6,5,4)
强制命名参数
def foo(a, *, b, c=123): pass
解包语法:
a, b, *ignored, c = [1, 2, 3, 4, 5, 6, 7]
Python 的赋值和引用
==, is
:==
判断的是值,is
判断的是内存地址 (即对象的id)小整数对象: [-5, 256]
copy, deepcopy
的区别copy
: 只拷贝表层元素deepcopy
: 在内存中重新创建所有子元素
练习1: 说出执行结果
def extendList(val, lst=[]): lst.append(val) return lst list1 = extendList(10) list2 = extendList(123, []) list3 = extendList('a')
练习2: 说出下面执行结果
from copy import copy, deepcopy from pickle import dumps, loads a = ['x', 'y', 'z'] b = [a] * 3 c = copy(b) d = deepcopy(b) e = loads(dumps(b, 4)) b[1].append(999) c[1].append(999) d[1].append(999) e[1].append(999)
自定义 deepcopy:
my_deepcopy = lambda item: loads(dumps(item, 4))
迭代器, 生成器
class Range: def __init__(self, start, end, step): self.start = start - step self.end = end self.step = step def __iter__(self): return self def __next__(self): current = self.start + self.step if current < self.end: self.start = current return current else: raise StopIteration()
iterator: 任何实现了
__iter__
和__next__
(python2中是next()
) 方法的对象都是迭代器.__iter__
返回迭代器自身__next__
返回容器中的下一个值- 如果容器中没有更多元素, 则抛出StopIteration异常
generator: 生成器其实是一种特殊的迭代器, 不需要自定义
__iter__
和__next__
- 生成器函数 (yield)
- 生成器表达式
练习1: 定义一个随机数迭代器, 随机范围为 [1, 50], 最大迭代次数 30
import random class RandomIter: def __init__(self, start, end, times): self.start = start self.end = end self.max_times = times self.count = 0 def __iter__(self): return self def __next__(self): self.count += 1 if self.count <= self.max_times: return random.randint(self.start, self.end) else: raise StopIteration()
练习2: 自定义一个迭代器, 实现斐波那契数列
class Fib: def __init__(self, max_value): self.prev = 0 self.curr = 1 self.max_value = max_value def __iter__(self): return self def __next__(self): if self.curr < self.max_value: res = self.curr self.prev, self.curr = self.curr, self.prev + self.curr return res else: raise StopIteration()
练习3: 自定义一个生成器函数, 实现斐波那契数列
def fib(max_value): prev = 0 curr = 1 while curr < max_value: yield curr prev, curr = curr, curr + prev
迭代器、生成器有什么好处?
- 节省内存
- 惰性求值
itertools
- 无限迭代
count(start=0, step=1)
cycle(iterable)
repeat(object [,times])
- 有限迭代
chain(*iterables)
- 排列组合
product(*iterables, repeat=1)
笛卡尔积permutations(iterable[, r-length])
全排列combinations(iterable, r-length)
组合
- 无限迭代
各种推导式
- 列表:
[i for i in range(5)]
- 字典:
{i: i + 3 for i in range(5)}
- 集合:
{i for i in range(5)}
- 列表:
装饰器
最简装饰器
def deco(func): def wrap(*args, **kwargs): return func(*args, **kwargs) return wrap @deco def foo(a, b): return a ** b
原理
对比被装饰前后的
foo.__name__
和foo.__doc__
from functools import wraps def deco(func): '''i am deco''' @wraps(func) def wrap(*args, **kwargs): '''i am wrap''' return func(*args, **kwargs) return wrap
简单过程
fn = deco(func) foo = fn foo(*args, **kwargs)
多个装饰器调用过程
@deco1 @deco2 @deco3 def foo(x, y): return x ** y # 过程拆解 1 fn3 = deco3(foo) fn2 = deco2(fn3) fn1 = deco1(fn2) foo = fn1 foo(3, 4) # 过程拆解 2 deco1( deco2( deco3(foo) ) )(3, 4)
带参数的装饰器
def deco(n): def wrap1(func): def wrap2(*args, **kwargs): return func(*args, **kwargs) return wrap2 return wrap1
装饰器类和
__call__
class Deco: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) @Deco def foo(x, y): return x ** y # 过程拆解 fn = Deco(foo) foo = fn foo(12, 34)
使用场景
- 参数、结果检查
- 缓存、计数
- 日志、统计
- 权限管理
- 重试
- 其他
练习1: 写一个 timer 装饰器, 计算出被装饰函数调用一次花多长时间, 并把时间打印出来
import time from functools import wraps def timer(func): @wraps(func) # 修正 docstring def wrap(*args, **kwargs): time0 = time.time() result = func(*args, **kwargs) time1 = time.time() print(time1 - time0) return result return wrap
练习2: 写一个权限管理装饰器, 权限分为
admin / member / guest
三级练习3: 写一个 Retry 装饰器
import time class retry(object): def __init__(self, max_retries=3, wait=0, exceptions=(Exception,)): self.max_retries = max_retries self.exceptions = exceptions self.wait = wait def __call__(self, f): def wrapper(*args, **kwargs): for i in range(self.max_retries + 1): try: result = f(*args, **kwargs) except self.exceptions: time.sleep(self.wait) continue else: return result return wrapper
method
,classmethod
和staticmethod
method
: 通过实例调用时, 可以引用类内部的任何属性和方法classmethod
: 无需实例化, 可以调用类属性和类方法, 无法取到普通的成员属性和方法staticmethod
: 无论用类调用还是用实例调用, 都无法取到类内部的属性和方法, 完全独立的一个方法练习: 说出下面代码的运行结果
class Test(object): x = 123 def __init__(self): self.y = 456 def bar1(self): print('i am a method') @classmethod def bar2(cls): print('i am a classmethod') @staticmethod def bar3(): print('i am a staticmethod') def foo1(self): print(self.x) print(self.y) self.bar1() self.bar2() self.bar3() @classmethod def foo2(cls): print(cls.x) # print(cls.y) # cls.bar1() Test.bar2() Test.bar3() @staticmethod def foo3(obj): print(obj.x) print(obj.y) obj.bar1() obj.bar2() obj.bar3() t = Test() t.foo1() t.foo2() t.foo3()
Python 魔术方法
__str__
,__repr__
__init__
和__new__
__new__
返回一个对象的实例,__init__
无返回值__new__
是一个类方法单例模式
class A(object): '''单例模式''' obj = None def __new__(cls, *args, **kwargs): if cls.obj is None: cls.obj = object.__new__(cls) return cls.obj
比较运算、数学运算
运算符重载
+
:__add__(value)
-
:__sub__(value)
*
:__mul__(value)
/
:__truediv__(value)
(Python 3.x),__div__(value)
(Python 2.x)//
:__floordiv__(value)
%
:__mod__(value)
&
:__and__(value)
|
:__or__(value)
练习: 实现字典的
__add__
方法, 作用相当于 d.update(other)class Dict(dict): def __add__(self, other): if isinstance(other, dict): new_dict = {} new_dict.update(self) new_dict.update(other) return new_dict else: raise TypeError('not a dict')
比较运算符的重载
==
:__eq__(value)
!=
:__ne__(value)
>
:__gt__(value)
>=
:__ge__(value)
<
:__lt__(value)
<=
:__le__(value)
练习: 完成一个类, 实现数学上无穷大的概念
class Inf: def __lt__(self, other): return False def __le__(self, other): return False def __ge__(self, other): return True def __gt__(self, other): return True def __eq__(self, other): return False def __ne__(self, other): return True
容器方法
__len__
-> len__iter__
-> for__contains__
-> in__getitem__
对string, list, tuple, dict
有效__setitem__
对list, dict
有效__missing__
对dict
有效, 字典的预留接口, dict 自身并没有实现class Dict(dict): def __missing__(self, key): self[key] = None # 当检查到 Key 缺失时, 可以做任何默认行为
可执行对象:
__call__
with:
__enter__
进入with
代码块前的准备操作__exit__
退出时的善后操作
__setattr__, __getattribute__, __getattr__, __dict__
常用来做属性监听
class A: '''TestClass''' z = [7,8,9] def __init__(self): self.x = 123 self.y = 'abc' def __setattr__(self, name, value): print('set %s to %s' % (name, value)) object.__setattr__(self, name, value) def __getattribute__(self, name): print('get %s' % name) return object.__getattribute__(self, name) def __getattr__(self, name): print('not has %s' % name) return -1 def foo(self, x, y): return x ** y # 对比 a = A() print(A.__dict__) print(a.__dict__)
槽:
__slots__
固定类所具有的属性
实例不会分配
__dict__
实例无法动态添加属性
优化内存分配
class A: __slots__ = ('x', 'y')
闭包
说出下面函数返回值
def foo(): l = [] def bar(i): l.append(i) return l return bar f1 = foo() f2 = foo() # 说出下列语句执行结果 f1(1) f1(2) f2(3)
作用域
global
nonlocal
globals()
locals()
vars()
local namespace | V global namespace | V builtin namespace
更深入一点:
__closure__
Python 性能之困
计算密集型
- CPU 长时间满负荷运行, 如图像处理、大数据运算、圆周率计算等
- 计算密集型: 用 C 语言补充
- Profile, timeit
I/O 密集型: 网络 IO, 文件 IO, 设备 IO 等
- 多任务处理: 进程 / 线程 / 协程
- 阻塞 -> 非阻塞
- 同步 -> 异步
GIL 全局解释器锁
- 它确保任何时候都只有一个Python线程执行。
什么是进程、线程、协程?
- 进程: 资源消耗大, 系统整体开销大, 数据通信不方便
- 线程: 资源消耗小, 可共享数据。上下文开销大。按时间片强制切换, 不够灵活
- 协程: 内存开销更小, 上下文切换开销更小。可根据事件切换, 更加有效的利用 CPU
- 进程、线程、协程调度的过程叫做上下文切换
什么是同步、异步、阻塞、非阻塞?
- 同步, 异步: 客户端调用服务器接口时
- 阻塞, 非阻塞: 服务端发生等待
事件驱动 + 多路复用
- 轮询: select, poll
- 事件驱动: epoll 有效轮询
greenlet / gevent | tornado / asyncio
import asyncio async def foo(n): for i in range(10): print('wait %s s' % n) await asyncio.sleep(n) return i task1 = foo(1) task2 = foo(1.5) tasks = [asyncio.ensure_future(task1), asyncio.ensure_future(task2)] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
线程安全, 锁
- 获得锁之后, 一定要释放, 避免死锁
- 获得锁之后, 执行的语句, 只跟被锁资源有关
- 区分普通锁 Lock, 可重入锁 RLock
- 线程之间的数据交互尽量使用 Queue
gevent
- monkey.patch
- gevent.sleep 非阻塞式等待
- Queue 协程间数据交互, 避免竞争
Garbage Collection (GC)
- 引用计数
优点: 简单、实时性高
缺点: 消耗资源、循环引用
lst1 = [3, 4] # lst1->ref_count 1 lst2 = [8, 9] # lst2->ref_count 1 # lst1 -> [3, 4, lst2] lst1.append(lst2) # lst2->ref_count 2 # lst2 -> [8, 9, lst1] lst2.append(lst1) # lst1->ref_count 2 del lst1 # lst1->ref_count 1 del lst2 # lst2->ref_count 1
- 引用计数
- 标记-清除, 分代收集
继承、多继承、多态、Mixin、super
继承
多态
class Animal: pass class Cat(Animal): pass class Tom(Cat): pass tom = Tom() isinstance(tom, Cat) isinstance(tom, Animal)
多继承
Cls.mro()
菱形继承问题
# 继承关系示意 # # A.foo() # / \ # B C.foo() # \ / # D class A: def foo(self): print('I am A') class B(A): pass class C(A): def foo(self): print('I am C') class D(B, C): pass d = D() d.foo() print(D.mro())
Mixin
super
class A: def __init__(self): print('enter A') self.x = 111 print('exit A') class B(A): def __init__(self): print('enter B') A.__init__(self) # super().__init__() print('exit B') class C(A): def __init__(self): print('enter C') A.__init__(self) # super().__init__() print('exit C') class D(B, C): def __init__(self): print('enter D') B.__init__(self) C.__init__(self) # super().__init__() print('exit D') d = D()
一些技巧和误区
格式化打印
- json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
- json 压缩:
json.dumps(obj, separators=[',',':'])
- pprint
确保能取到有效值
d.get(k, default)
d.setdefault
defaultdict
a or b
x = a if foo() else b
try...except... 的滥用
- 不要把所有东西全都包住, 程序错误需要报出来
- 使用
try...except
要指明具体错误,try
结构不是用来隐藏错误的, 而是用来有方向的处理错误的
利用 dict 做模式匹配
def do1(): print('i am do1') def do2(): print('i am do2') def do3(): print('i am do3') def do4(): print('i am do4') mapping = {1: do1, 2: do2, 3: do3, 4: do4} mod = random.randint(1, 10) func = mapping.get(mod, do4) func()
inf, -inf, nan
venv, pyenv, 命名空间
- venv: 创建虚拟环境, 做环境隔离, venv 目录直接放到项目的目录里
- pyenv: 管理 Python 版本
property: 把一个方法属性化
class C(object): @property def x(self): "I am the 'x' property." return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
else 子句:
if, for, while, try
collections 模块
- defaultdict
- OrderedDict
- Counter
- namedtuple