编写高质量代码:Python中的常见语法
Rome was not built in one day, coding will not advance vigorously with one effort.
有节制地使用from… import语句
- 尽量优先使用import a形式,如访问B需要使用a.B的形式
- 有节制的使用from a import B, 可以直接访问B
- 无节制使用可能会导致:命名空间的冲突、循环嵌套导入
- 最好不使用from a import * ,这会导致污染命名空间,且无法清楚得知导入了哪些对象
Python的import机制:在初始化环境的时候会预先加载一批内建模块到内存中,这些模块相关信息被存放在sys.modules中,可以通过sys.modules.items()查看。
加载一个模块时,解释器实际要做以下操作:
- 在sys.modules中进行搜索看该模块是否已经存在, 如果存在则将其导入到当前局部命名空间,加载结束;
- 如果在sys.modules中没有找到对应模块的名称,则为需要导入的模块创建一个字典对象,并将该对象信息插入到sys.modules中;
- 加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译;
- 执行动态加载,在当前模块的命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中;
1 | # test_module.py |
从以上结果看,对于用户定义的模块,import机制会创建一个新的module将其加入到当前的局部命名空间中,与此同时,sys.modules也会加入了该模块的相关信息。
脚本文件所在的目录中的__pycache__中多了一个test_module.cpython-36.pyc文件,该文件为解释器生成的模块相对应的字节码,从import之后的输出“this is test_module.py“可以看出模块同时被执行,而a和b被写入test_module所对应的字典信息中。
优先使用absolute import
absolute import 可读性和出现问题后可跟踪性更好,而使用relative import可能会导致命名冲突、语义含糊。
连接字符串优先使用join
try…except…finally…
无论try中是否有异常抛出,finally语句总会被执行。
try块中发生异常的时候,如果在except语句中找不到对应的异常处理,异常将会被临时保存起来,当finally语句中产生了新的异常、或执行了return、或break语句那么临时保存的异常将被丢失,从而导致异常被屏蔽。
另外不推荐在finally中使用return语句进行返回,这种处理方式不仅会带来误解,可能还会导致严重的错误。
1 | def test_finally(n): |
优先使用列表生成式
- 使用列表生成式更为直观清晰、代码更加简洁。
- 列表生成式的效率更高
举例:
1 | words = [' a', 'awk', 'backup', 'Advance', 'Street'] |
函数传参既不是传值也不是传引用
Python函数参数传对象,或者说是传对象的引用。
- 函数参数在传递的过程中将整个对象传入,对可变对象的修改在函数内外都可见,调用者和被调用者之间共享这个对象;
- 对于是不可变对象的参数,并不能真正被改变,是通过生成一个新对象后赋值来实现改变。我的理解是:一个是实参,一个是形参;
1 | # 两个例子: |
小结:id到底变不变,一看参数是否为可变对象,二看是否重新开辟内存地址
1 | def function_param(origin_list): |
Python中的赋值 VS C/C++中的赋值:背后的内存地址分配
1 | a = 1 |
C/C++:
- 执行
b=a时,在内存中申请一块内存并将a的值复制到该内存中 - 执行
b=2时,将b对应的值从原来的1修改为2
Python:
- Python中的赋值不是复制,
b=a使b与a引用同一个对象,而b=2是将b指向对象2

警惕默认参数潜在的问题
可变对象不能作为默认参数传递
1 | def test_default(paramlist=[]): |
def 在Python中是一个可执行的语句,当解释器执行 def 时,默认参数也会被计算,并存在函数的__defaults__属性中。
如果 不想让默认参数所指向的对象在所有的函数调用中被共享,而是在函数调用的过程中动态生成,可以在定义时使用None对象作为占位符。
1 | def test_default(paramlist=None): |
一个问题:假设某个函数需要传入当时系统的时间并做一些处理,下面两种哪种正确?
1 | import time |
1 | import time |
不定长参数的使用
*args 实现可变列表,**kwargs接受字典的关键字参数列表
可变长参数的使用场景:
- 为函数添加一个装饰器
1 | def decorate(func): |
- 参数的数量不确定,可以考虑使用可变长参数
- 实现函数的多态或者在继承情况下子类需要调用父类的某些方法
str() 和 repr()的区别
对于不同类型的输入,对比两者的差异:
- 两者的面向的对象不同:str()主要是面向用户,其目的是可读性,返回形式为用户友好性和可读性都较强的字符串类型。而repr()面向的是python解释器、程序员,返回值为Python解释器内部的含义,常作为debug用。
- 在解释器中直接输入a时默认调用repr()函数,而print(a)是调用str()函数
- repr()返回值一般可以用eval()函数来还原对象:obj == eval(repr(obj))
- 两个方法分别调用内建的
__str__、__repr__方法,一般来说在类中都应该定义__repr__方法,而__str__方法是可选的,若没有定义__str__方法,默认使用__repr__
