龙空技术网

「Python开发」EffectivePython:编写高质量Python代码

独一无二的Python君 295

前言:

如今小伙伴们对“python语言规范”大体比较重视,兄弟们都想要了解一些“python语言规范”的相关内容。那么小编也在网络上汇集了一些有关“python语言规范””的相关内容,希望姐妹们能喜欢,兄弟们快快来了解一下吧!

使用f'{var}实现格式化字符串输出

采用%操作符把值填充到C风格的格式字符串时会遇到许多问题,而且这种写法比较烦琐。str.format方法专门用一套迷你语言来定义它的格式说明符,这套语言给我们提供了一些有用的概念,但是在其他方面,这个方法还是存在与C风格的格式字符串一样的多种缺点,所以我们也应该避免使用它。f-string采用新的写法,将值填充到字符串之中,解决了C风格的格式字符串所带来的最大问题。f-string是个简洁而强大的机制,可以直接在格式说明符里嵌入任意Python表达式。

详见:weread.qq.com/web/reader/…

scores = {'Andy': 100, 'Bob': 90, 'Cici': 80}name = 'Andy'# 使用%格式化操作符print('name=%s, score=%d' % (name, scores[name]))# 使用format函数: 不设置指定位置,按默认顺序print("name={}, score={}".format(name, scores[name]))# 使用format函数: 设置指定位置print("name={0}, score={1}".format(name, scores[name]))# 使用format函数: 通过字典设置参数print("name={name}, score={score}".format(**{'name': name, 'score': scores[name]}))# 【推荐】使用插值格式字符串print(f"name={name}, score={scores[name]}")# name=Andy, score=100# name=Andy, score=100# name=Andy, score=100# name=Andy, score=100# name=Andy, score=100复制代码
数据拆分

unpacking是一种特殊的Python语法,只需要一行代码,就能把数据结构里面的多个值分别赋给相应的变量。unpacking在Python中应用广泛,凡是可迭代的对象都能拆分,无论它里面还有多少层迭代结构。尽量通过unpacking来拆解序列之中的数据,而不要通过下标访问,这样可以让代码更简洁、更清晰。

详见:weread.qq.com/web/reader/…

拆分数据结构并把其中的数据赋给变量时,可以用带星号的表达式,将结构中无法与普通变量相匹配的内容捕获到一份列表里。这种带星号的表达式可以出现在赋值符号左侧的任意位置,它总是会形成一份含有零个或多个值的列表。在把列表拆解成互相不重叠的多个部分时,这种带星号的unpacking方式比较清晰,而通过下标与切片来实现的方式则很容易出错。

详见:weread.qq.com/web/reader/…

在返回多个值的时候,可以用带星号的表达式接收那些没有被普通变量捕获到的值函数可以把多个值合起来通过一个元组返回给调用者,以便利用Python的unpacking机制去拆分。对于函数返回的多个值,可以把普通变量没有捕获到的那些值全都捕获到一个带星号的变量里。把返回的值拆分到四个或四个以上的变量是很容易出错的,所以最好不要那么写,而是应该通过小类或namedtuple实例完成。

详见:weread.qq.com/web/reader/…

names = ('Andy', 'Bob', 'Cici', 'Daniel')name1, name2, *others = namesprint(f'names = {names}')print(f'|- name1 = {name1}')print(f'|- name2 = {name2}')print(f'|- others = {others}')# names = ('Andy', 'Bob', 'Cici', 'Daniel')# |- name1 = Andy# |- name2 = Bob# |- others = ['Cici', 'Daniel']复制代码

在Python代码里,可以用下划线 来表示用不到的变量

数据拆分 - 实现交换

a = 111; b = 222print(f'交换前:a={a}, b={b}')a, b = b, aprint(f'交换后:a={a}, b={b}')# 交换前:a=111, b=222# 交换后:a=222, b=111复制代码
尽量用enumerate

enumerate函数可以用简洁的代码迭代iterator,而且可以指出当前这轮循环的序号。不要先通过range指定下标的取值范围,然后用下标去访问序列,而是应该直接用enumerate函数迭代。可以通过enumerate的第二个参数指定起始序号(默认为0)。

详见:weread.qq.com/web/reader/…

列表:

tuple_list = [('AAA', '111'), ('BBB', '222'), ('CCC', '333')]for index, (key, value) in enumerate(tuple_list):    print(f'#{index}: {key} = {value}')# #0: AAA = 111# #1: BBB = 222# #2: CCC = 333复制代码

字典:

dict = {'AAA': '111', 'BBB': '222', 'CCC': '333'}print('\nenumerate(dict):')for index, key in enumerate(dict):    print(f'#{index}: {key} = {dict.get(key)}')print('\nenumerate(dict.items()):')for index, (key, value) in enumerate(dict.items()):    print(f'#{index}: {key} = {value}')# enumerate(dict):# #0: AAA = 111# #1: BBB = 222# #2: CCC = 333# # enumerate(dict.items()):# #0: AAA = 111# #1: BBB = 222# #2: CCC = 333复制代码
用zip同时遍历多个集合

内置的zip函数可以同时遍历多个迭代器。zip会创建惰性生成器,让它每次只生成一个元组,所以无论输入的数据有多长,它都是一个一个处理的。如果提供的迭代器的长度不一致,那么只要其中任何一个迭代完毕,zip就会停止。如果想按最长的那个迭代器来遍历,那就改用内置的itertools模块中的zip_longest函数。

详见:weread.qq.com/web/reader/…

names = ['Andy', 'Bob', 'Cici', 'Daniel']scores = [100, 90, 80]print('# 若2个集合 长度不一样,则当较短的集合遍历完,就会结束整个遍历:')for name, score in zip(names, scores):    print(f'{name}: {score}')import itertoolsprint('\n# 或是使用 itertools.zip_longest() 实现较长集合的遍历:')for name, score in itertools.zip_longest(names, scores):    print(f'{name}: {score}')# # 若2个集合 长度不一样,则当较短的集合遍历完,就会结束整个遍历:# Andy: 100# Bob: 90# Cici: 80## # 或是使用 itertools.zip_longest() 实现较长集合的遍历:# Andy: 100# Bob: 90# Cici: 80# Daniel: None复制代码
自定义排序

列表的sort方法可以根据自然顺序给其中的字符串、整数、元组等内置类型的元素进行排序。普通对象如果通过特殊方法定义了自然顺序,那么也可以用sort方法来排列,但这样的对象并不多见。可以把辅助函数传给sort方法的key参数,让sort根据这个函数所返回的值来排列元素顺序,而不是根据元素本身来排列。如果排序时要依据的指标有很多项,可以把它们放在一个元组中,让key函数返回这样的元组。对于支持一元减操作符的类型来说,可以单独给这项指标取反,让排序算法在这项指标上按照相反的方向处理。如果这些指标不支持一元减操作符,可以多次调用sort方法,并在每次调用时分别指定key函数与reverse参数。最次要的指标放在第一轮处理,然后逐步处理更为重要的指标,首要指标放在最后一轮处理。

详见:weread.qq.com/web/reader/…

class Student:    name = ''    score = 0    def __init__(self, name, score):        self.name = name        self.score = score    def __repr__(self):        return f'Student({self.name}, {self.score})'print(f'排序前:')students = [Student('Bob', 60), Student('Andy', 100), Student('Cici', 80), Student('Bob', 80)]for stu in students: print(f'|- {stu.name}: {stu.score}')print(f'\n以名称升序排序后:')students.sort(key=lambda stu: stu.name)for stu in students: print(f'|- {stu.name}: {stu.score}')print(f'\n以名称到序排序后:')students.sort(key=lambda stu: stu.name, reverse=True)for stu in students: print(f'|- {stu.name}: {stu.score}')print(f'\n以名称倒序排序,且 名称相同时 分数高的排在前面:')students.sort(key=lambda stu: (stu.name, stu.score), reverse=True)for stu in students: print(f'|- {stu.name}: {stu.score}')print(f'\n以分数降序排序后:')students.sort(key=lambda stu: stu.score, reverse=True)for stu in students: print(f'|- {stu.name}: {stu.score}')# 排序前:# |- Bob: 60# |- Andy: 100# |- Cici: 80# |- Bob: 80# # 以名称升序排序后:# |- Andy: 100# |- Bob: 60# |- Bob: 80# |- Cici: 80# # 以名称到序排序后:# |- Cici: 80# |- Bob: 60# |- Bob: 80# |- Andy: 100# # 以名称倒序排序,且 名称相同时 分数高的排在前面:# |- Cici: 80# |- Bob: 80# |- Bob: 60# |- Andy: 100# # 以分数降序排序后:# |- Andy: 100# |- Cici: 80# |- Bob: 80# |- Bob: 60复制代码
使用字典 获取类的属性键值对在Python 3.5 与之前的版本中,迭代字典(dict)时所看到的顺序好像是任意的从Python 3.6开始,字典会保留这些键值对在添加时所用的顺序,而且Python 3.7版的语言规范正式确立了这条规则。于是,在新版的Python里,总是能够按照当初创建字典时的那套顺序来遍历这些键值对。类也会利用字典来保存这个类的实例所具备的一些数据,在新版的Python中,我们就可以认为这些字段在__dict__中出现的顺序应该与当初赋值时的顺序一样。现在的Python语言规范已经要求,字典必须保留添加键值对时所依照的顺序。所以,我们可以利用这样的特征来实现一些功能,而且可以把它融入自己给类和函数所设计的API中。
class Student:    name = ''    score = 0    id = '001'    _sex = None    __id_num = None    def __init__(self, name, score):        self._sex = 'M'        self.name = name        self.score = score    def __repr__(self):        return f'Student({self.name}, {self.score})'students = [Student('Bob', 60), Student('Andy', 100), Student('Cici', 80), Student('Bob', 80)]print('# 遍历 students')for stu in students: print(f'|- {stu.name}: {stu.score}')print('\n# 遍历 students 属性键值对')for stu in students: print(f'|- {stu.__dict__}')# # 遍历 students# |- Bob: 60# |- Andy: 100# |- Cici: 80# |- Bob: 80# # # 遍历 students 属性键值对# |- {'_sex': 'M', 'name': 'Bob', 'score': 60}# |- {'_sex': 'M', 'name': 'Andy', 'score': 100}# |- {'_sex': 'M', 'name': 'Cici', 'score': 80}# |- {'_sex': 'M', 'name': 'Bob', 'score': 80}复制代码
无序字典 & 有序字典

从Python 3.7版开始,我们就可以确信迭代标准的字典时所看到的顺序跟这些键值对插入字典时的顺序一致。

在Python代码中,我们很容易就能定义跟标准的字典很像但本身并不是dict实例的对象。

对于这种类型的对象,不能假设迭代时看到的顺序必定与插入时的顺序相同。

如果不想把这种跟标准字典很相似的类型也当成标准字典来处理,那么可以考虑这样三种办法。

第一,不要依赖插入时的顺序编写代码;第二,在程序运行时明确判断它是不是标准的字典;第三,给代码添加类型注解并做静态分析。

其实,内置的collections模块早就提供了这种能够保留插入顺序的字典,叫作OrderedDict。

它的行为跟(Python 3.7以来的)标准dict类型很像,但性能上有很大区别。

如果要频繁插入或弹出键值对(例如要实现least-recently-used缓存),那么OrderedDict可能比标准的Pythondict类型更合适

详见:weread.qq.com/web/reader/…

import collectionsimport osprint(f'# python env:', end='')os.system("python3 --version")scores = dict()scores['Bob'] = 60scores['Andy'] = 100scores['Cici'] = 80print('\n# 遍历 scores')for name, score in scores.items(): print(f'|- {name}: {score}')ordered_scores = collections.OrderedDict()ordered_scores['Bob'] = 60ordered_scores['Andy'] = 100ordered_scores['Cici'] = 80print('\n# 遍历 ordered_scores')for name, score in ordered_scores.items(): print(f'|- {name}: {score}')# # python env:Python 3.8.9# # # 遍历 scores# |- Bob: 60# |- Andy: 100# |- Cici: 80# # # 遍历 ordered_scores# |- Bob: 60# |- Andy: 100# |- Cici: 80复制代码
字典的 取值、赋值、默认值

详见:weread.qq.com/web/reader/…

另外 defaultdict可见:weread.qq.com/web/reader/…

scores = {'Andy': 100, 'Bob': 90, 'Cici': 80}name = 'Daniel'score = None# 传统方法if name in scores:    score = scores[name]print(f"{name}'s score is {score}")# 推荐方法score2 = scores.get(name, 0)print(f"{name}'s score is {score2}")# 设置默认值:# 这个方法会查询字典里有没有这个键,如果有 就返回对应的值;如果没有 就先把用户提供的默认值跟这个键关联起来并插入字典,然后返回这个值。scores.setdefault(name, 60)print(f"{name}'s score is {scores.get(name)}")# Daniel's score is None# Daniel's score is 0# Daniel's score is 60复制代码
字典取值 - 自定义__missing__

如果要构造的默认值必须根据键名来确定,那么可以定义自己的dict子类并实现__missing__方法。

详见:weread.qq.com/web/reader/…

class Scores(dict):    def __missing__(self, key):        self[key] = None# 传统方法scores = dict()try:    print(f"xxx's score is {scores['xxx']}")  # KeyError: 'xxx'except KeyError as err:    print(f'KeyError: {err}')# 通过继承dict类型并实现__missing__特殊方法来解决这个问题scores2 = Scores()print(f"xxx's score is {scores2['xxx']}")# KeyError: 'xxx'# xxx's score is None复制代码

一个支持自定义__missing__的字典:

class safedict(dict):    missing = None    def __missing__(self, key):        if self.missing:            return self.missing(key)        return Noneempty_dict = safedict()empty_dict.missing = lambda key: f'key:{key} not define.'print(f'{empty_dict["qqq"]}')# key:qqq not define.复制代码
使用作用域外的变量

闭包函数可以引用定义它们的那个外围作用域之中的变量。按照默认的写法,在闭包里面给变量赋值并不会改变外围作用域中的同名变量。先用nonlocal语句说明,然后赋值,可以修改外围作用域中的变量。除特别简单的函数外,尽量少用nonlocal语句。

详见:weread.qq.com/web/reader/…

先来看一个示例:

def fun_outer():    outer_var = None    def func_inner():        outer_var = 'new value'        return outer_var    func_inner()    return outer_varprint(f'value = {fun_outer()}')# value = None复制代码

如上代码,最后 value = None 而不是 value = new value

使用 nonlocal 可以实现该效果,即「把闭包里面的数据赋给闭包外面的变量」:

def fun_outer():    outer_var = None    def func_inner():        nonlocal outer_var  # 重点        outer_var = 'new value'        return outer_var    func_inner()    return outer_varprint(f'value = {fun_outer()}')# value = new value

标签: #python语言规范 #pythondict函数