第2章 Python整洁之道
2.1 用断言加一层保险
断言用于程序内部自检,如声明一些代码中不可能出现的条件。如果触发了某个条件,即意味着程序中存在相应的bug。
Python的断言语句是一种调试辅助功能,不是用来处理运行时错误的机制。
不要使用断言来验证数据,断言是可以通过命令行参数(-O和-OO)或者环境变量(PYTHONOPTIMIZE)进行禁用的。
断言不要用括号,比如assert (1==2, '出错了'),这种写法实际判断的是一个非空元组,这样断言永远都不会失败,也就不会触发。
正确的做法应该总是先对单元测试用例做一个快速的冒烟测试,保证断言能在错误发生时被触发。
2.2 巧妙地放置逗号
在列表、字典、集合的每一项后面都放置逗号,避免逗号缺失导致的bug。且每一项独占一行,可以在代码diff时方便比较。
2.3 上下文管理器和with语句
如果想把一个对象作为上下文管理器,就需要对其增加__enter__和__exit__方法。
with语法:
with 表达式 [as 变量]:
业务操作
其中:
表达式返回的是一个上下文管理器,即返回的对象需要有__enter__和__exit__方法。
而 [as 变量]这部分是可选的,注意:变量接收的不是表达式返回的对象而是表达式返回对象的__enter__方法返回的对象
表达式执行完之后就对调用返回对象的__enter__方法,业务操作执行完之后,离开with代码块就会执行表达式返回对象的__exit__方法
一个缩进示例:
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):
print(' ' * self.level + text)
with Indenter() as indent:
indent.print('hi')
with indent:
indent.print('hello')
with indent:
indent.print('greeting')
indent.print('bye...')
""" 输出结果
hi
hello
greeting
bye...
"""
View Code
还可是使用标准库中contextlib模块的装饰器contextmanager把一个函数变为上下文管理器
以下示例代码展示了两种方式的上下文管理器
from contextlib import contextmanager
# class NoExit:
# def __init__(self):
# print('NoExit init')
#
# def write(self, content):
# print(content)
#
# def __enter__(self):
# print('NoExit enter')
#
# def __exit__(self, exc_type, exc_val, exc_tb):
# print('NoExit exit')
class ManagedFile:
def __init__(self, name):
self.name = name
def __enter__(self):
print('enter')
self.file = open(self.name, 'w')
return self.file
# return NoExit()
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
if self.file:
self.file.close()
# with ManagedFile('hello.text') as f:
# f.write('hello')
# f.write('hi')
# with ManagedFile('hello.text') as f:
# f.write('hello')
# with f:
# f.write('hello 2')
# with f:
# f.write('hello 3')
@contextmanager
def managed_file(name):
try:
f = open(name, 'w')
yield f
finally:
print('finally')
f.close()
with managed_file('hello_2.text') as f:
f.write('hello2')
f.write('hi2')
View Code
with语句关键要点:
一般用于管理资源的安全获取和释放。资源首先由with语句获取,并在执行离开with上下文时自动释放。
有效地使用with有助于避免资源泄露问题,让代码更加易于阅读
2.4 下划线、双下划线及其他
前置单下划线:_var:只是约定,告诉其他人:以单下划线开头的变量或方法只能在内部使用。只是一个警告标志:注意,这并不是这个类的公开接口,最好不要使用它。
后置单下划线:var_:只是约定,当变量名称与关键字冲突时,可用来关键字后面命名变量,比如:class_等
前置双下划线:__var:不是约定,解释器会重写双下划线开头的属性名称,以避免子类中的命名冲突。
class Test:
def __init__(self):
self.foo = 11
self._bar = 22
self.__baz = 33
class ExtendedTest(Test):
def __init__(self):
super().__init__()
# self.foo = 'overridden'
self._bar = 'overridden'
self.__baz = 'overridden'
t = Test()
print(dir(t))
et = ExtendedTest()
print(dir(et))
# print(t.__baz) # AttributeError: 'Test' object has no attribute '__baz'
print(t._Test__baz)
print(et._Test__baz)
print(et._ExtendedTest__baz)
print(et.foo)
""" 输出结果
['_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']
['_ExtendedTest__baz', '_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']
33
33
overridden
11
"""
View Code
前后双下划线:__var__:前后都是用了双下划线,名称不会被改写。Python中有许多前后带双下划线的方法,它们通常被称为魔法方法。避免在自己的程序中使用前后都有双下划线的名称,避免与Python语言未来变更发生冲突。
单下划线:_:通常用来表示临时的或无关紧要的变量名称。在Python REPL会话中上一个表达式的结果。
dunder:双下划线double underscore的简称
2.5 字符串格式化中令人震惊的真相
4种字符串格式化方式:%,format,f,Template
如果格式字符串是用户提供的,使用模板字符串Template来避免安全问题。如果不是,再考虑Python版本,Python3.6+使用字符串字面插值f,老版本是用format。