跳转至

PEP 8 - 风格代码例子

pep8-examples.md# 编码样式指南

变量交换

# Bad
tmp = a
a = b
b = tmp

# Pythonic
a,b = b,a

列表推导

# Bad
my_list = []
for i in range(10):
    my_list.append(i*2)

# Pythonic
my_list = [i*2 for i in range(10)]

单行表达式

虽然列表推导式由于其简洁性及表达性,被广受推崇。

但是有许多可以写成单行的表达式,并不是好的做法。

# Bad
print 'one'; print 'two'

if x == 1: print 'one'

if <complex comparison> and <other complex comparison>:
    # do something

# Pythonic

print 'one'
print 'two'

if x == 1:
    print 'one'

cond1 = <complex comparison>
cond2 = <other complex comparison>
if cond1 and cond2:
    # do something

带索引遍历

# Bad
for i in range(len(my_list)):
    print(i, "-->", my_list[i])

# Pythonic
for i,item in enumerate(my_list):
    print(i, "-->",item)

字符串拼接

# Bad
letters = ['s', 'p', 'a', 'm']
s=""
for let in letters:
    s += let

# Pythonic
letters = ['s', 'p', 'a', 'm']
word = ''.join(letters)

真假判断

# Bad
if attr == True:
    print 'True!'

if attr == None:
    print 'attr is None!'

# Pythonic
if attr:
    print 'attr is truthy!'

if not attr:
    print 'attr is falsey!'

if attr is None:
    print 'attr is None!'

真假值对照表:

类型 False True
布尔 False(与0等价) True(与1等价)
字符串 ""(空字符串) 非空字符串,例如 " ", "blog"
数值 0, 0.0 非0的数值,例如:1, 0.1, -1, 2
容器 [], (), 至少有一个元素的容器对象,例如:[0],(None,),['']
None None 非None对象

访问字典元素

# Bad

d = {'hello': 'world'}
if d.has_key('hello'):
    print d['hello']    # prints 'world'
else:
    print 'default_value'

# Pythonic

d = {'hello': 'world'}

print d.get('hello', 'default_value') # prints 'world'
print d.get('thingy', 'default_value') # prints 'default_value'

# Or:
if 'hello' in d:
    print d['hello']

操作列表

# Bad

a = [3, 4, 5]
b = []
for i in a:
    if i > 4:
        b.append(i)

# Pythonic

a = [3, 4, 5]
b = [i for i in a if i > 4]
# Or:
b = filter(lambda x: x > 4, a)
Bad

a = [3, 4, 5]
for i in range(len(a)):
    a[i] += 3

# Pythonic

a = [3, 4, 5]
a = [i + 3 for i in a]
# Or:
a = map(lambda i: i + 3, a)

文件读取

# Bad

f = open('file.txt')
a = f.read()
print a
f.close()

# Pythonic

with open('file.txt') as f:
    for line in f:
        print line

代码续行

# Bad
my_very_big_string = """For a long time I used to go to bed early. Sometimes, \
    when I had put out my candle, my eyes would close so quickly that I had not even \
    time to say “I’m going to sleep.”"""

from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \
    yet_another_nice_function

# Pythonic
my_very_big_string = (
    "For a long time I used to go to bed early. Sometimes, "
    "when I had put out my candle, my eyes would close so quickly "
    "that I had not even time to say “I’m going to sleep.”"
)

from some.deep.module.inside.a.module import (
    a_nice_function, another_nice_function, yet_another_nice_function)

显式代码

# Bad
def make_complex(*args):
    x, y = args
    return dict(**locals())

# Pythonic
def make_complex(x, y):
    return {'x': x, 'y': y}

使用占位符

# Pythonic
filename = 'foobar.txt'
basename, _, ext = filename.rpartition('.')

链式比较

# Bad
if age > 18 and age < 60:
    print("young man")

# Pythonic

if 18 < age < 60:
    print("young man")

# 理解了链式比较操作,那么你应该知道为什么下面这行代码输出的结果是 False
>>> False == False == True 
False

三目运算

这个保留意见。随使用习惯就好。

# Bad
if a > 2:
    b = 2
else:
    b = 1
#b = 2

# Pythonic

a = 3   

b = 2 if a > 2 else 1
#b = 2

or赋值

某些情况下,or可以替换if else 达到代码简化的作用,比如在变量赋值时, 并且支持链式调用

# 基本用法
v = p1 or p2
# v = p1 if p1 else p2

# 链式用法, 直到找到为真的值来赋值,否则赋值最后的值
v = p1 or p2 or p3 or default

# if p1:
#     v = p1
# elif p2:
#     v = p2
# elif p3:
#     v = p3
# else:
#     v = defualt

Python3 新特性

*和**解构

# Pythonic

a, *rest = [1, 2, 3]
# a = 1, rest = [2, 3]

a, *middle, c = [1, 2, 3, 4]
# a = 1, middle = [2, 3], c = 4

# 解构字典和列表
print(*[1], *[2], 3, *[4, 5])

def fn(a, b, c, d):
    print(a, b, c, d)

fn(**{'a': 1, 'c': 3}, **{'b': 2, 'd': 4})

# 也可以解构元祖,集合set等
*range(4), 4

[*range(4), 4]

{*range(4), 4, *(5, 6, 7)}

{'x': 1, **{'y': 2}}

字符串格式化

# 早期 - python 2

print("name:%s, age:%s, sex: %s," %('张三', 18, '男'))

# 后来 - python 2

print("name:{}, age:{}, sex:{}".format('张三', 18, '男'))
print("name:{name}, age:{age}, sex:{sex}".format(name='张三', age=18, sex='男'))

# 现在 - python3
name, age, sex = ('张三', 18, '男') # 自动解包
print(f"name:{name}, age:{age}, sex:{sex}")

f字符串支持用=

自动记录表达式和调试文档

增加 = 说明符用于 f-string。 形式为 f'{expr=}' 的 f-字符串将扩展表示为表达式文本,加一个等于号,再加表达式的求值结果。

# 基本用法
>>> import datetime
>>> user = 'eric_idle'
>>> member_since = datetime.date(1975, 7, 31)
>>> f'{user=} {member_since=}'
"user='eric_idle' member_since=datetime.date(1975, 7, 31)"

# 通常的 f-字符串格式说明符 允许更细致地控制所要显示的表达式结果:
>>> delta = datetime.date.today() - member_since
>>> f'{user=!s}  {delta.days=:,d}'
'user=eric_idle  delta.days=17,436'

# = 说明符将输出整个表达式,以便详细演示计算过程:
>>> from math import cos, radians
>>> theta = 30
>>> print(f'{theta=}  {cos(radians(theta))=:.3f}')
theta=30  cos(radians(theta))=0.866

字典新增合并运算

python 3.9新增特性

合并 (|) 与更新 (|=) 运算符已被加入内置的 dict 类。 它们为现有的 dict.update{**d1, **d2} 字典合并方法提供了补充。

>>> x = {"name": "张三", "sex": "男"}
>>> y = {"name": "张小三", "sex": "男"}
>>> print(x | y)  # 取并集并覆盖
{'name': '张小三', 'sex': '男'}
>>> print(y | x)  # 取并集并覆盖
{'name': '张三', 'sex': '男'}

数字加下划线

数字中可以使用下划线增强可读性

print(1_000_000_000_000_00)
print(0x_FF_FF_FF_FF)

# 字符串格式化也支持下划线_选项

类型标注

参考官网: https://docs.python.org/zh-cn/3/library/typing.html

变量标注

# 需要一部分IDE支持
primes: list[int] = []

captain: str  # Note: no initial value!

class Starship:
    stats: dict[str, int] = {}

primes.append('sda')  # 这里会类型检查不通过
print(primes)

类型别名

把类型赋给别名,就可以定义类型别名。

# Vector 和 list[float] 相同,可互换
Vector = list[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

# 通过类型检查; 一个浮点数的列表组合 类型为 Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])

类型别名主要适用于简化复杂的类型签名

from collections.abc import Sequence  # 序列泛型

ConnectionOptions = dict[str, str]
Address = tuple[str, int]
Server = tuple[Address, ConnectionOptions]

def broadcast_message(message: str, servers: Sequence[Server]) -> None:
    ...

# 静态类型检查器会将前一个类型签名视为与当前类型签名完全相同。
def broadcast_message(
        message: str,
        servers: Sequence[tuple[tuple[str, int], dict[str, str]]]) -> None:
    ...

标准库

csv

参考标准库: https://docs.python.org/zh-cn/3/library/csv.html

CSV (Comma Separated Values) 格式是电子表格和数据库中最常见的输入、输出文件格式。

csv 模块实现了 CSV 格式表单数据的读写

# 基本读写
import csv

# csv.reader 读取
with open('eggs.csv', newline='') as csvfile:
    spamreader = csv.reader(csvfile, delimiter=' ', quotechar='|')
    for row in spamreader:
        print(', '.join(row))

# csv.writer 写入
with open('eggs.csv', 'w', newline='') as csvfile:
    spamwriter = csv.writer(csvfile, delimiter=' ',
                            quotechar='|', quoting=csv.QUOTE_MINIMAL)
    spamwriter.writerow(['Spam'] * 5 + ['Baked Beans'])
    spamwriter.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam'])

# 字典读写
import csv

# csv.DictReader 读取字典格式
with open('names.csv', newline='') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        print(row['first_name'], row['last_name'])

# csv.DictWriter 写入字典格式
with open('names.csv', 'w', newline='') as csvfile:
    fieldnames = ['first_name', 'last_name']
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerow({'first_name': 'Baked', 'last_name': 'Beans'})
    writer.writerow({'first_name': 'Lovely', 'last_name': 'Spam'})
    writer.writerow({'first_name': 'Wonderful', 'last_name': 'Spam'})

io

参考官网: https://docs.python.org/zh-cn/3/library/io.html

io 模块提供了 Python 用于处理各种 I/O 类型的主要工具。三种主要的 I/O类型分别为: 文本 I/O, 二进制 I/O 和 原始 I/O。

文本IO

内存中文本流作为 StringIO 对象使用:

# 文本I/O预期并生成 str 对象。
f = io.StringIO("some initial text data")

# 主要用于生成小的文本文件(例如:csv),便于处理,而非创建一个临时文件(磁盘IO)

二进制 I/O

二进制I/O(也称为缓冲I/O)预期 bytes-like objects 并生成 bytes 对象。

# 存储二进制数据
f = io.BytesIO(b"some initial binary data: \x00\x01")

# 多常用于图片、验证码等。

collections 常用容器

ChainMap

一个 ChainMap 将多个字典或者其他映射组合在一起,创建一个单独的可更新的视图。 如果没有 maps 被指定,就提供一个默认的空字典,这样一个新链至少有一个映射。

>>> baseline = {'music': 'bach', 'art': 'rembrandt'}
>>> adjustments = {'art': 'van gogh', 'opera': 'carmen'}
>>> list(ChainMap(adjustments, baseline))
['music', 'art', 'opera']

# python3 新语法 针对字典 的 | 运算符也可以

Counter

一个计数器工具提供快速和方便的计数

>>> from collections import Counter
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
...     cnt[word] += 1
... 
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})

defaultdict

返回一个新的类似字典的对象。 defaultdict 是内置 dict 类的子类。 它重载了一个方法并添加了一个可写的实例变量。 其余的功能与 dict 类相同因而不在此文档中写明。

>>> from collections import defaultdict
>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
... 
>>> sorted(d.items())
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

namedtuple

命名元组赋予每个位置一个含义,提供可读性和自文档性。它们可以用于任何普通元组,并添加了通过名字获取值的能力,通过索引值也是可以的。

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])  # 使用位置或关键字参数实例化
>>> p = Point(11, y=22)                      # 像普通元组 (11, 22) 一样可索引
>>> p[0] + p[1]
33
>>> x, y = p                                 # 像普通元组一样解构
>>> x, y
(11, 22)
>>> p.x + p.y                                # 也可按名称访问字段
33
>>> p
Point(x=11, y=22)                            # 以可读的name=value形式作为__repr__的输出

内置函数

map

返回一个迭代器,它将函数应用于可迭代的每个项目,产生结果。 如果传递了额外的可迭代参数,则函数必须采用那么多参数,并并行应用于所有可迭代的项目。

# 有时候需要批量需要将pk的集合转化为str,然后进行join操作
>>> pk_list = [1,2,3,4,5,6]
>>> pk_list_str = ','.join(map(lambda x: str(x), pk_list))
>>> pk_list_dtr
>>> pk_list_str
'1,2,3,4,5,6'

filter

iterable 的那些元素构造一个迭代器,其中 function 为真。 iterable 可以是序列、支持迭代的容器或迭代器。 如果 functionNone,则假设为恒等函数,即 iterable 中所有为 false 的元素都将被移除。

# 保留执行函数为真的值
>>> pk_list = [1,2,3,4,5,6, 7, 8]
>>> pk_list = filter(lambda x: x%2==0, pk_list)  # 保留能被2整除的PK
>>> pk_list_str = ','.join(map(lambda x: str(x), pk_list))
>>> pk_list_str
'2,4,6,8'

# 过滤值bool判断为false的值
>>> pk_list = [0, 1,3,0,2, -1, 0, 6]
>>> pk_list = filter(None, pk_list) 
>>> pk_list_str = ','.join(map(lambda x: str(x), pk_list))
>>> pk_list_str
'1,3,2,-1,6'

any

如果 iterable 的任一元素为真值则返回 True。 如果可迭代对象为空,返回 False。 等价于:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

all

如果 iterable 的所有元素均为真值(或可迭代对象为空)则返回 True 。 等价于:

def all(iterable):
    for element in iterable:
        if not element:
            return False
    return True

zip

在多个迭代器上并行迭代,从每个迭代器返回一个数据项组成元组

# 有时候经常遇到sql执行之后返回这样的值
>>> keys = ('a', 'b', 'c')
>>> values = [[1,2,3], [4,5,6], [7,8,9]]
>>> list(map(dict, map(lambda items: zip(keys, items), values)))
[{'a': 1, 'b': 2, 'c': 3}, {'a': 4, 'b': 5, 'c': 6}, {'a': 7, 'b': 8, 'c': 9}]

sorted

定义: sorted(iterable, /, *, key=None, reverse=False)

根据 iterable 中的项返回一个新的已排序列表。

具有两个可选参数,它们都必须指定为关键字参数。

key 指定带有单个参数的函数,用于从 iterable 的每个元素中提取用于比较的键 (例如 key=str.lower)。 默认值为 None (直接比较元素)。

reverse 为一个布尔值。 如果设为 True,则每个列表元素将按反向顺序比较进行排序。

sum

定义: sum(iterable, /, start=0)

start 开始自左向右对 iterable 的项求和并返回总计值。 iterable 的项通常为数字,而 start 值则不允许为字符串。

对某些用例来说,存在 sum() 的更好替代。 拼接字符串序列的更好更快方式是调用 ''.join(sequence)。 要以扩展精度对浮点值求和,参考 math.fsum()。 要拼接一系列可迭代对象,请参考使用 itertools.chain()

range

生成指定区间的整数数列

>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(range(0, 30, 5))
[0, 5, 10, 15, 20, 25]
>>> list(range(0, 10, 3))
[0, 3, 6, 9]

getattr

返回对象命名属性的值。 名称必须是字符串。

setattr

赋值指定属性以及值给对象。

setattr(x, 'foobar', 123) 等价于 x.foobar = 123

hasattr

判断一个对象是否拥有某个属性, 如果拥有返回True、否则返回False

enumerate

返回一个枚举对象。iterable 必须是一个序列,或 iterator,或其他支持迭代的对象。 enumerate() 返回的迭代器的 __next__() 方法返回一个元组,里面包含一个计数值(从 start 开始,默认为 0)和通过迭代 iterable 获得的值。

>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

等价于:

def enumerate(iterable, start=0):
    n = start
    for elem in iterable:
        yield n, elem
        n += 1

项目实战

代码优化1

优化前

def get_access_token(cls, data):
    param = data.get('param')
    code = param.get('code')
    home_url = param.get('home_url')
    if not code:
        raise Exception('缺少参数code')
    url = 'https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code&appid={}&secret={}&' \
            'code={}'.format(
            public_account_config.get('app_id'), public_account_config.get('secret'), code)
    # 获取到微信的access_token、openid
    res = requests.get(url, params=data).json()

    # 存在跳转链接
    if home_url:
        return redirect_link(home_url, res)
    return {
        'code': UserStatusCode.OK,
        'message': 'ok',
        'data': res
    }

优化后:

def get_access_token(cls, data: dict):
    param = data.get('param')
    code = param.get('code')
    home_url = param.get('home_url')
    app_id = public_account_config.get('app_id')
    secret = public_account_config.get('secret')

    if not code:
        raise Exception('缺少参数code')

    parameters = (
        ('grant_type', 'authorization_code'),
        ('appid', app_id),
        ('secret', secret),
        ('code', code),
    )

    param_url = '&'.join([f'{key}={val}' for (key, val) in parameters])

    url = f'https://api.weixin.qq.com/sns/oauth2/access_token?{param_url}'
    # 获取到微信的access_token、openid
    res = requests.get(url).json()

    # 存在跳转链接
    if home_url:
        return redirect_link(home_url, res)

    return {
        'code': UserStatusCode.OK,
        'message': 'ok',
        'data': res
    }

最后更新: 2024年9月4日
创建日期: 2024年9月4日