Python 编程规范
Auth : 张旭
Date : 2018-04-02
Email: zhangxu@1000phone.com
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
- 格式化打印