注:本文为学习朱雷老师的《Python工匠:案例、技巧与工程实践》一书的总结与心得。
好的变量和注释:
# 用户输入可能有空格,使用strip()去掉空格
username = extract_username(input_string.strip())
变量:见名知意,符合规范
注释:介绍使用场景、目的,及具体的作用
把一个可迭代对象(比如列表)的所有成员,一次性赋值给多个变量
情况1:(常用)
usernames = ['piglei', 'raymond'] # 注意:左侧变量的个数必须和待展开的列表长度相等,否则会报错 author, reader = usernames >>> author 'piglei'
情况2:假如在赋值语句左侧添加小括号(...),甚至可以一次展开多层嵌套数据
attrs = [1, ['piglei', 100]] user_id, (username, score) = attrs
情况3:动态解包,用*variables(星号表达式),贪婪捕获)
data = ['piglei', 'apple', 'orange', 'banana', 100] username, *fruits, score = data # 结果 >>> username 'piglei' >>> fruits ['apple', 'orange', 'banana'] >>> score 100
等同于切片赋值:
#1. 动态解包 username, *fruits, score = data # 2. 切片赋值 username, fruits, score = data[0], data[1:-1], data[-1]
2)单下划线变量名_
因Python是动态类型语言,使用变量时不需要做任何类型声明。造成了一些阅读上的不清楚。
方法1:在函数文档(docstring)---用:type items:注明了items是个整型列表
def remove_invalid(items): """剔除 items 里面无效的元素 :param items: 待剔除对象 :type items: 包含整数的列表,[int, ...] """
方法2:类型注解,Python的内置功能 ---在变量后添加类型,并用冒号隔开
from typing import List def remove_invalid(items: List[int]): ➊ """剔除 items 里面无效的元素""" ... ...
1-遵循PEP 8原则(驼峰命名-CamelCase;下划线连接的蛇形命名-snake_case)
2-命名规范:
· 对于普通变量,使用蛇形命名法,比如max_value;
· 对于常量,采用全大写字母,使用下划线连接,比如MAX_VALUE;
· 如果变量标记为“仅内部使用”,为其增加下划线前缀,比如_local_var;
· 当名字与Python关键字冲突时,在变量末尾追加下划线,比如class_。
除变量名以外,PEP 8中还有许多其他命名规范,比如类名应该使用驼峰风格(FooClass)、函数应该使用蛇形风格(bar_function)
3-描述性强:可接受的长度范围内,变量名所指向的内容描述得越精确越好
4-要尽量短:为变量命名要结合代码情境和上下文
5-匹配类型:
匹配布尔值类型的变量名:让读到变量的人觉得它只有“肯定”和“否定”两种可能(is_superuhas_error)
匹配int/float类型的变量名:别拿一个名词的复数形式来作为int类型的变量名,比如apples、trips,建议用number_of_apples或trips_count这类复合词来作为int类型的名字。
6-
7-其他技巧
· 在同一段代码内,不要出现多个相似的变量名,比如同时使用users、users1、users3这种序列;
· 可以尝试换词来简化复合变量名,比如用is_special来代替is_not_normal;
注释方式:
1-代码内注释:如#。。。
2-接口注释:如:函数(类)文档
常见错误
1-用注释屏蔽代码:对于不再需要的代码,我们应该直接把它们删掉
2-用注释复述代码:应该尽量提供那些读者无法从代码里读出来的信息。描述代码为什么要这么做,而不是简单复述代码本身。
3-指引性代码?--
4-弄错接口注释的受众:接口文档主要是给函数(或类)的使用者看的,它最主要的存在价值,是让人们不用逐行阅读函数代码,也能很快通过文档知道该如何使用这个函数,以及在使用时有什么注意事项
def resize_image(image, size): """将图片缩放到指定尺寸,并返回新的图片。 注意:当文件超过 5MB 时,请使用resize_big_image() :param image: 图片文件对象 :param size: 包含宽高的元组:(width, height) :return: 新图片对象 """ # 无需介绍具体设计
前:
def magic_bubble_sort(numbers): j = len(numbers) - 1 while j > 0: for i in range(j): if numbers[i] % 2 == 0 and numbers[i + 1] % 2 == 1: numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i] continue elif (numbers[i + 1] % 2 == numbers[i] % 2) and numbers[i] > numbers[i + 1]: numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i] continue j -= 1 return numbers
后:
def magic_bubble_sort(numbers: List[int]): """有魔力的冒泡排序算法,默认所有的偶数都比奇数大 :param numbers: 需要排序的列表,函数会直接修改原始列表 """ stop_position = len(numbers) - 1 while stop_position > 0: for i in range(stop_position): current, next_ = numbers[i], numbers[i + 1] ➊ current_is_even, next_is_even = current % 2 == 0, next_ % 2 == 0 should_swap = False # 交换位置的两个条件: # - 前面是偶数,后面是奇数 # - 前面和后面同为奇数或者偶数,但是前面比后面大 if current_is_even and not next_is_even: should_swap = True elif current_is_even == next_is_even and current > next_: should_swap = True if should_swap: numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i] stop_position -= 1 return numbers
1-保持变量一致性
在使用变量时,你需要保证它在两个方面的一致性:名字一致性与类型一致性
2-变量定义尽量靠近使用
3-定义临时变量提升可读性
5-同一作用域内不要有太多变量
要减少函数里的变量数量,最直接的方式是给这些变量分组,建立新的模型。如:添加类
class ImportedSummary: """保存导入结果摘要的数据类""" def __init__(self): self.succeeded_count = 0 self.failed_count = 0 class ImportingUserGroup: """用于暂存用户导入处理的数据类""" def __init__(self): self.duplicated = [] self.banned = [] self.normal = [] def import_users_from_file(fp): """尝试从文件对象读取用户,然后导入数据库 :param fp: 可读文件对象 :return: 成功与失败的数量 """ importing_user_group = ImportingUserGroup() for line in fp: parsed_user = parse_user(line) # …… 进行判断处理,修改上面定义的importing_user_group 变量 summary = ImportedSummary() # …… 读取 importing_user_group,写入数据库并修改成功与失败的数量 return summary.succeeded_count, summary.failed_count
6-能不定义变量就别定义
如:return ...
7- 空行也是一种“注释”
在写代码时,我们可以适当地在代码中插入空行,把代码按不同的逻辑块分隔开,这样能有效提升代码的可读性。
8-先写注释,后写代码。
每个函数的名称与接口注释(也就是docstring),其实是一种比函数内部代码更为抽象的东西。
在定义数值字面量时,如果数字特别长,可以通过插入_分隔符来让它变得更易读:
#以"千"为单位分隔数字 i = 1_000_000 i + 10 1000010
浮点数精度问题:需要精确的浮点数计算,请考虑使用decimal.Decimal对象来替代普通浮点数
from decimal import Decimal # 注意:这里的'0.1'和'0.2' 必须是字符串,必须使用字符串来表示数字 Decimal('0.1') + Decimal('0.2') Decimal('0.3')
布尔值其实也是数字:绝大多数情况下,True和False这两个布尔值可以直接当作1和0来使用
# 此处的表达式i % 2 == 0会返回一个布尔值结果,该结果随后会被当成数字0或1由sum()函数累加求和 count = sum(i % 2 == 0 for i in numbers)
字符串是一种序列类型,可进行遍历、切片等操作,就像访问一个列表对象一样
反转一个字符串,可以使用切片操作或者reversed内置方法:
# 切片最后一个字段使用-1,表示从后往前反序 s[::-1] ➊ '!dlrow ,olleH' # reversed会返回一个可迭代对象,通过字符串的.join方法可以将它转换为字符串 ''.join(reversed(s)) ➋ '!dlrow ,olleH'
字符串格式化:
username, score = 'piglei', 100 # 1. C 语言风格格式化-----不再常用 print('Welcome %s, your score is %d' % (username, score)) # 2. str.format----可重复多个变量 print('Welcome {}, your score is {:d}'.format(username, score)) # 3. f-string,最短最直观----常用 print(f'Welcome {username}, your score is {score:d}') # 输出: # Welcome piglei, your score is 100
拼接多个字符串:
1-一些好用,但不常用的方法:
# 判断是否只包含数字 '123'.isdigit(), 'foo'.isdigit() >>>(True, False) # 按照分隔符sep切分字符串,返回一个包含三个成员的元组:(part_before, sep, part_after) def extract_value_v2(s): # 当 s 包含分隔符 : 时,元组最后一个成员刚好是value # 若是没有分隔符,最后一个成员默认是空字符串 '' return s.partition(':')[-1] # 按规则一次性替换多个字符,比调用多次replace方法更快也更简单 s = '明明是中文,却使用了英文标点.' # 创建替换规则表:',' -> ',', '.' -> '。' table = s.maketrans(',.', ',。') s.translate(table) >>>'明明是中文,却使用了英文标点。'
原:
def add_daily_points(user): """用户每天完成第一次登录后,为其增加积分""" if user.type == 13: return if user.type == 3: user.points += 120 return user.points += 100 return
新:
#用户每日奖励积分数量 DAILY_POINTS_REWARDS = 100 # VIP 用户每日额外奖励积分数量 VIP_EXTRA_POINTS = 20 from enum import Enum # 在定义枚举类型时,如果同时继承一些基础类型,比如int、str, # 枚举类型就能同时充当该基础类型使用。比如在这里,UserType 就可以当作int 使用 class UserType(int, Enum): # VIP 用户 VIP = 3 # 小黑屋用户 BANNED = 13 def add_daily_points(user): """用户每天完成第一次登录后,为其增加积分""" if user.type == UserType.BANNED: return if user.type == UserType.VIP: user.points += DAILY_POINTS_REWARDS + VIP_EXTRA_POINTS return user.points += DAILY_POINTS_REWARDS return
涉及知识点:
枚举类型?
使用模块改写代码:
当你的代码里出现复杂的裸字符串处理逻辑时,请试着问自己一个问题:“目标/源字符串是结构化的且遵循某种格式吗?”
如果答案是肯定的,那么请先寻找是否有对应的开源专有模块,比如处理SQL语句的SQLAlchemy、处理XML的lxml模块等。如果你要拼接非结构化字符串,也请先考虑使用Jinja2等模板引擎
1-不必预计算字面量表达式
2-使用特殊数字:“无穷大”:
float("inf") ,float("-inf") -----正负无穷大; float("-inf")<任意数值< float("inf")
def sort_users_inf(users): def key_func(username): age = users[username] # 当年龄为空时,返回正无穷大作为 key,因此就会被排到最后 return age if age is not None else float('inf') return sorted(users.keys(), key=key_func) users = {"tom": 19, "jenny": 13, "jack": None, "andrew": 43} print(sort_users_inf(users)) # 输出: # ['jenny', 'tom', 'andrew', 'jack']
3-改善超长字符串的可读性
三、容器类型