最近正在阅读《Python Tricks: The Book》这本书,想要通过掌握书中提及的知识点提高自己的Python编程能力。本文主要记录在学习该书第二章“Patterns for Cleaner Python”过程中的一些心得体会。
断言是编程语言提供的一种调试工具,是让程序在运行时进行自检的代码,可以辅助程序员进行交流和调试。程序员可以通过断言了解代码正确运行所依赖的假设,此外,当断言为假时,程序员可以快速排查出由于输入(输入参数的取值范围不符合预期)输出(返回的结果是没有意义的)不符合接口假设而导致的错误。
断言常用于验证以下两类条件是否满足:
在Python中通过“assert expression ["," expression]”的方式声明断言,例如:
def apply_discount(product, discount): price = int(product['price'] * (1.0 - discount)) # 验证后置条件:打折后的价格应该介于0到原价之间 assert 0 <= price <= product['price'] return price注意:断言主要用于处理代码中不应发生的错误,而那些在预期中的可能发生的错误建议使用异常进行处理。
在定义列表、元组、字典以及集合时,在最后一个元素后面追加一个额外的逗号非常有用:
# 新增元素'Jane',由于忘了在'Dilbert'后面加逗号,names变成了['Alice', 'Bob', 'DilbertJane']names = [ 'Alice', 'Bob', 'Dilbert' 'Jane']with语句解构了try/finally语句的标准用法:with语句开始执行时,会调用上下文管理器对象的“__enter__”方法,这对应try/finally语句之前申请系统资源的过程(比如打开文件);with语句执行结束后,会调用上下文管理器对象的“__exit__”方法,这对应finally子句中释放系统资源的过程(比如关闭文件句柄)。
可以通过两种方式定义上下文管理器:
class Indenter: """缩进管理器""" def __init__(self): self.level = 0 def __enter__(self): self.level += 1 return self def __exit__(self, exc_type, exc_val, exc_tb): self.level -= 1 def print(self, text: str): print(' ' * self.level + text)with Indenter() as indent: indent.print('风在吼') indent.print('马在叫') with indent: indent.print('黄河在咆哮') indent.print('黄河在咆哮') with indent: indent.print('河西山冈万丈高') indent.print('河东河北高粱熟了') indent.print('...') indent.print('保卫家乡!保卫黄河!') indent.print('保卫华北!保卫全中国!')# 风在吼# 马在叫# 黄河在咆哮# 黄河在咆哮# 河西山冈万丈高# 河东河北高粱熟了# ...# 保卫家乡!保卫黄河!# 保卫华北!保卫全中国!(有没有更好的写法,感觉使用全局变量不太优雅?)
import contextliblevel = 0@contextlib.contextmanagerdef indenter(): global level level += 1 yield level -= 1def cprint(text: str): global level print(' ' * level + text)with indenter(): cprint('风在吼') cprint('马在叫') with indenter(): cprint('黄河在咆哮') cprint('黄河在咆哮') with indenter(): cprint('河西山冈万丈高') cprint('河东河北高粱熟了') cprint('...') cprint('保卫家乡!保卫黄河!') cprint('保卫华北!保卫全中国!')在Python的变量名或方法名的前面或后面使用单个下划线或两个下划线,有着不同的含义:
单个下划线作为前缀(_var)表示类或模块的私有成员,尝试通过通配符导入模块的所有函数和变量时(from module import *),私有成员不会被导入;
单个下划线作为后缀(var_)用于与Python关键字进行区分,比如“def make_object(name, class_)”中的形参”class_”;
两个下划线作为前缀(__var)用于避免与子类相同名称的类变量之间的冲突,变量名会被Python解释器重写为“_类名__var”;
class Test: """父类""" def __init__(self): self.foo = 0 self.__bar = 1test = Test()dir(test) # ['_Test__bar', 'foo', ...]class ExtendedTest(Test): """子类""" def __init__(self): super().__init__() self.foo = 3 self.__bar = 4test = ExtendedTest()dir(test) # ['_ExtendedTest__bar', '_Test__bar', 'foo', ...]两个下划线同时作为前缀和后缀(__var__)表示特殊方法;
单独的下划线表示临时变量的名称(主要用于拆包时进行占位),也表示REPL最近一个表达式的结果。
在Python中有四种常用的格式化字符串的方法:
printf风格的格式化,比如print('逐梦演艺%s' % '圈圈圈圈圈');
str.format,比如print('逐梦演艺{0}{0}{0}{0}{0}'.format('圈'));
f-string字符串插值:
echoes = '圈'print(f'逐梦演艺{echoes * 5}')string.Template:
import stringtemplate = string.Template('逐梦演艺${echoes}')print(template.substitute(echoes='圈圈圈圈圈'))当需要对用户输入的字符串进行格式化时,推荐使用string.Template而非其他方案,因为恶意用户可能通过类似SQL注入的方式获取系统的敏感信息:
import stringSECRET = '这是私钥'class Error: def __init__(self): passerr = Error()user_input = '{error.__init__.__globals__[SECRET]}'user_input.format(error=err) # '这是私钥',糟糕,私钥被泄露了!user_input = '${error.__init__.__globals__[SECRET]}'string.Template(user_input).substitute(error=err) # ValueErrorBeautiful is better than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than complicated.Flat is better than nested.Sparse is better than dense.Readability counts.Special cases aren’t special enough to break the rules.Although practicality beats purity.Errors should never pass silently.Unless explicitly silenced.In the face of ambiguity, refuse the temptation to guess.There should be one—and preferably only one—obvious way to do it.Although that way may not be obvious at first unless you’re Dutch.Now is better than never.Although never is often better than right now.If the implementation is hard to explain, it’s a bad idea.If the implementation is easy to explain, it may be a good idea.Namespaces are one honking great idea—let’s do more of those!