本系列文章为《编写高质量代码——改善Python程序的91个建议》的精华汇总。

关于导入模块

Python的3种引入外部模块的方式:import语句、from ... import ... 和 __import__函数。其中前两种比较常见。

在使用 import 时,应注意:

  • 优先使用 import A 或 import A as a
  • 有节制的使用 from A import B
  • 尽量避免使用 from A import *

对于 from a import ...,如果无节制的使用,会带来的问题:

  • 命名空间的冲突
  • 循环嵌套导入的问题(两个文件相互导入对方的变量或函数或类)

i += 1 不等于 ++i

Python 解释器会将 ++i 解释为 +(+i),其中 + 表示正数符号。对于 --i 也是类似。

因此,要明白 ++i 在 Python 的语法层面上是合法的,但并不是通常意义上的自增操作。

使用 with 自动关闭资源

对文件操作完成后,应该立即关闭它们,因为打开的文件不仅会占用系统资源,而且可能影响其他程序或者进程的操作,甚至会导致用户期望与实际操作结果不一致。

Python 提供了 with 语句,语法为:

with 表达式 [as 目标]:
代码块

with 语句支持嵌套,支持多个 with 子句,它们两者可以相互转换。with expr1 as e1, expr2 as e2 与下面的嵌套形式等价:

with expr1 as e1:
with expr2 as e2:

使用 else 子句简化循环(异常处理)

在循环中, else 子句提供了隐含的对循环是否由 break 语句引发循环结束的判断。例子:

# 以下两段代码等价
# 借助了一个标志量 found 来判断循环结束是不是由 break 语句引起的。
def print_prime(n):
for i in range(2, n):
found = True
for j in range(2, i):
if i % j == 0:
found = False
break
if found:
print("{} is a prime number".format(i)) def print_prime2(n):
for i in range(2, n):
for j in range(2, i):
if i % j == 0:
break
else:
print("{} is a prime number".format(i))

当循环“自然”终结(循环条件为假)时 else 从句会被执行一次,而当循环是由 break 语句中断时,else 子句就不被执行。

与 for 语句相似,while 语句中的 else 子句的语意是一样的: else 块在循环正常结束和循环条件不成立时被执行。

遵循异常处理的几点基本原则

Python中常用的异常处理语法是try、except、else、finally,它们可以有多种组合。语法形式如下:

# Run this main action first
try:
<statements> # 当 try 中发生 name1 的异常时,进行处理
except <name1>:
<statements> # 当 try 中发生 name2 或 name3 中的某一个异常时
except (name2, name3):
<statements> # 当 try 中发生 name4 的异常时处理,并获取对应实例
except <name4> as <data>:
<statements> # 其他异常时,进行处理
except:
<statements> # 没有异常时,执行
else:
<statements> # 无论有没有异常,都执行
finally:
<statements>

异常处理,通常需要遵循以下几点基本原则:

  • 不推荐在 try 中放入过多的代码。在 try 中放入过多的代码带来的问题是如果程序中抛出异常,将会较难定位,给 debug 和修复带来不便,因此应尽量只在可能抛出异常的语句块前面放入 try 语句。
  • 谨慎使用单独的 except 语句处理所有异常,最好能定位具体的异常。同样也不推荐使用 except Exception 或者 except StandardError 来捕获异常。如果必须使用,最好能够使用 raise 语句将异常抛出向上层传递。
  • 注意异常捕获的顺序,在合适的层次处理异常。
    • 用户也可以继承自内建异常构建自己的异常类,从而在内建类的继承结构上进一步延伸。在这种情况下捕获异常的顺序显得非常重要。为了更精确地定位错误发生的原因,推荐的方法是将继承结构中子类异常在前面的 except 语句中抛出,而父类异常在后面的 except 语句抛出。这样做的原因是当 try 块中有异常发生的时候,解释器根据 except 声明的顺序进行匹配,在第一个匹配的地方便立即处理该异常。
    • 异常捕获的顺序非常重要,同时异常应该在适当的位置被处理,一个原则就是如果异常能够在被捕获的位置被处理,那么应该及时处理,不能处理也应该以合适的方式向上层抛出。向上层传递的时候需要警惕异常被丢失的情况,可以使用不带参数的 raise 来传递。
  • 使用更为友好的异常信息,遵守异常参数的规范。通常来说有两类异常阅读者:使用软件的人和开发软件的人。

避免 finally 中可能发生的陷阱

无论 try 语句中是否有异常抛出,finally 语句总会被执行。由于这个特性,finally 语句经常被用来做一些清理工作。

但使用 finally 时,也要特别小心一些陷阱。

  • 当 try 块中发生异常的时候,如果在 except 语句中找不到对应的异常处理,异常将会被临时保存起来,当 finally 执行完毕的时候,临时保存的异常将会再次被抛出,但如果 finally 语句中产生了新的异常或者执行了 return 或者 break 语句,那么临时保存的异常将会被丢失,从而导致异常屏蔽。
  • 在实际应用程序开发过程中,并不推荐在 finally 中使用 return 语句进行返回,这种处理方式不仅会带来误解而且可能会引起非常严重的错误。

深入理解 None,正确判断对象是否为空

Python 中以下数据会当作空来处理:

  • 常量 None
  • 常量 False
  • 任何形式的数值类型零,如 0、0L、0.0、0j
  • 空的序列,如 ''、()、[]
  • 空的字典,如 {}
  • 当用户定义的类中定义了 __nonzero__() 和 __len__() 方法,并且该方法返回整数 0 或 False 的时候。
if list1 # value is not empty
Do something
else: # value is empty
Do some other thing
  • 执行过程中会调用内部方法 __nonzero__() 来判断变量 list1 是否为空并返回其结果。

注: __nonzero__() 方法 —— 该内部方法用于对自身对象进行空值测试,返回 0/1 或 True/False。

  • 如果一个对象没有定义该方法,Python 将获取 __len__() 方法调用的结果来进行判断。__len__() 返回值为 0 则表示为空。如果一个类中既没有定义 __len__() 方法也没有定义 __nonzero__() 方法,该类的实例用 if 判断的结果都为 True。

格式化字符串时尽量使用 .format 方式而不是 %

推荐尽量使用 format 方式而不是 % 操作符来格式化字符串,理由:

  • format 方式在使用上较 % 操作符更为灵活。使用 format 方式时,参数的顺序与格式化的顺序不必完全相同

  • format 方式可以方便的作为参数传递

    weather = [("Monday", "rain"), ("Tuesday", "sunny"), ("Wednesday", "sunny"), ("Thursday", "rain"), ("Friday", "cloudy")]
    formatter = "Weather of '{0[0]}' is '{0[1]}'".format
    for item in map(formatter, weather):
    print(item)
  • % 最终会被 .format 方式所代替。根据 Python 的官方文档,之所以仍然保留 % 操作符是为了保持向后兼容

  • % 方法在某些特殊情况下使用时需要特别小心,对于 % 直接格式化字符的这种形式,如果字符本身为元组,则需要使用在 % 使用 (itemname,) 这种形式才能避免错误,注意逗号。

区别对待可变对象和不可变对象

Python 中一切皆对象,对象根据其值能否修改分为可变对象和不可变对象。

  • 不可变对象

    • 数字
    • 字符串
    • 元组
  • 可变对象

    • 字典
    • 列表
    • 字节数组

在将可变对象作为函数默认参数的时候要特别紧惕,对可变对象的更改会直接影响原对象。

最好的方法是传入 None 作为默认参数,在创建对象的时候动态生成可变对象。

  • 对于一个可变对象,切片操作相当于浅拷贝。

  • 对于不可变对象,当我们对其进行相关操作的时候,Python 实际上仍然保持原来的值而且重新创建一个新的对象,所以字符串对象不允许以索引的方式进行赋值,当有两个对象同时指向一个字符串对象的时候,对其中一个对象的操作并不会影响另一个对象。

函数传参既不是传值也不是传引用

对于Python中函数的传参方法,既不是传值,也不是传引用。

正确的叫法应该是传对象(call by object)或者说传对象的引用(call-by-object-reference)。

函数参数在传递的过程中将整个对象传入,

  • 对于可变对象:它的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象
  • 对于不可变对象:由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值来实现的

慎用变长参数

慎用可变长度参数*args, **kwargs,原因如下:

  • 使用过于灵活。变长参数意味着这个函数的签名不够清晰,存在多种调用方式。另外变长参数可能会破坏程序的健壮性。
  • 如果一个函数的参数列表很长,虽然可以通过使用 *args 和 **kwargs 来简化函数的定义,但通常这个函数可以有更好的实现方式,应该被重构。例如可以直接传入元组和字典。

可变长参数适合在下列情况下使用:

  • 为函数添加一个装饰器
  • 如果参数的数目不确定,可以考虑使用变长参数
  • 用来实现函数的多态,或者在继承情况下子类需要调用父类的某些方法的时候

深入理解 str() 和 repr() 的区别

函数 str() 和 repr() 都可以将 Python 中的对象转换为字符串,两者的使用以及输出都非常相似。有以下几点区别:

  • 两者的目标不同:

    • str() 主要面向用户,其目的是可读性,返回形式为用户友好性和可读性都较强的字符串类型
    • 而 repr() 面向开发人员,其目的是准确性,其返回值表示 Python 解释器内部的含义,常用作 debug
  • 在解释器中直接输入时默认调用 repr() 函数,而 print 则调用 str() 函数

  • repr() 的返回值一般可以用 eval() 函数来还原对象。通常有如下等式:obj == eval(repr(obj))

  • 一般,类中都应该定义 __repr__() 方法,而 __str__() 方法则为可选,当可读性比准确性更为重要的时候应该考虑定义 __str__() 方法。如果类中没有定义 __str__() 方法,则默认会使用 __repr__() 方法的结果来返回对象的字符串表示形式。用户实现 __repr__() 方法的时,最好保证其返回值可以用 eval() 方法使对象重新还原。

分清静态方法和类方法的适用场景

静态方法:

class C(object):
@staticmethod
def f(arg1, arg2, ...):

类方法:

class C(object):
@classmethod
def f(cls, arg1, arg2, ...):

都可以通过类名.方法名或者实例.方法名的形式来访问。

其中,静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则,而类方法的调用使用类本身作为其隐含参数,但调用本身并不需要显示提供该参数。

类方法

  • 在调用的时候没有显式声明 cls,但实际上类本身是作为隐藏参数传入的
  • 类方法可以判断出自己是通过基类被调用,还是通过某个子类被调用
  • 类方法通过子类调用时,可以返回子类的属性而非基类的属性
  • 类方法通过子类调用时,可以调用子类的其他类方法

静态方法

  • 既不跟特定的实例相关也不跟特定的类相关
  • 静态方法定义在类中的原因是,能够更加有效地将代码组织起来,从而使相关代码的垂直距离更近,提高代码的可维护性

文章首发于公众号【Python与算法之路】

编写高质量Python程序(三)基础语法的更多相关文章

  1. 编写高质量Python程序(四)库

    本系列文章为<编写高质量代码--改善Python程序的91个建议>的精华汇总. 按需选择 sort() 或者 sorted() Python 中常用的排序函数有 sort() 和 sort ...

  2. Effective Python之编写高质量Python代码的59个有效方法

                                                         这个周末断断续续的阅读完了<Effective Python之编写高质量Python代码 ...

  3. Effective Python 编写高质量Python代码的59个有效方法

    Effective Python 编写高质量Python代码的59个有效方法

  4. 编写高质量Python代码的59个有效方法

    Python学习资料或者需要代码.视频加Python学习群:960410445 1. 用Pythonic方式思考 第一条:确认自己使用的Python版本 (1)有两个版本的python处于活跃状态,p ...

  5. 《Effective Python:编写高质量Python代码的59个有效方法》读书笔记(完结)

    Effective Python 第1章 用Pythonic方式来思考 be pythonic 遵守pep8 python3有两种字符序列类型:bytes(原始的字节)和str(Unicode字符). ...

  6. Python -- Effective Python:编写高质量Python代码的59个有效方法

    第 1 章 用 Pythonic 方式来思考 第 1 条:确认自己所用的 Python 版本 python --version import sys print(sys.version_info) p ...

  7. 编写高质量Python代码总结:待完成

    1:字符串格式化 #避免%过多影响阅读 print('hello %(name)s'%{'name':'tom'}) #format方法print('{name} is very {emmition} ...

  8. 编写高质量代码--改善python程序的建议(三)

    原文发表在我的博客主页,转载请注明出处! 建议十三:警惕eval()的安全漏洞 相信经常处理文本数据的同学对eval()一定是欲罢不能,他的使用非常简单: eval("1+1==2" ...

  9. 编写高质量代码--改善python程序的建议(四)

    原文发表在我的博客主页,转载请注明出处! 建议十八:有节制的使用from...import语句 python提供了三种方式引入外部模块: import语句 from...import... __imp ...

随机推荐

  1. 你不一定知道的UrlPrefix路由规则

    引言 接上文,容器内web程序一般会绑定到http://0.0.0.0:{某监听端口}或http://+:{某监听端口},以确保使用容器IP可以访问到web应用. 正如我们在ASP.NET Core官 ...

  2. fastjson JSONObject.toJSONString 出现 $ref: "$."的解决办法(重复引用)

    首先,fastjson作为一款序列化引擎,不可避免的会遇到循环引用的问题,为了避免StackOverflowError异常,fastjson会对引用进行检测. 如果检测到存在重复/循环引用的情况,fa ...

  3. Journal of Proteome Research | Single-Shot Capillary Zone Electrophoresis−Tandem Mass Spectrometry Produces over 4400 Phosphopeptide Identifications from a 220 ng Sample (分享人:赵伟宁)

    Title: Single-Shot Capillary Zone Electrophoresis−Tandem Mass Spectrometry Produces over 4400 Phosph ...

  4. [C++]HelloWorld背后的故事!

    人物介绍 姓名 HelloWorld 性别 .cpp 住址 D:\ 身份证号(SHA1) 25106D2879A9EA300BB264F8155A71D7C44DA2E8 故事简介 编写源文件 预编译 ...

  5. zabbix笔记_008 zabbix监控交换机路由器

    zabbix监控交换机路由器 要监控路由器交换机,需要使用到SNMP协议 SNMP是一个简单网络管理协议,他基于C/S模型实现的监控和管理. 服务器安装SNMP: yum -y install net ...

  6. DRF 序列化组件 模型层中参数补充

    一. DRF序列化 django自带有序列化组件,但是相比rest_framework的序列化较差,所以这就不提django自带的序列化组件了. 首先rest_framework的序列化组件使用同fr ...

  7. xmake v2.3.2 发布, 带来和ninja一样快的构建速度

    这个版本重点重构优化了下内部并行构建机制,实现多个target间源文件的并行编译,以及并行link的支持,同时优化了xmake的一些内部损耗,修复影响编译速度的一些bug. 通过测试对比,目前的整体构 ...

  8. laravel中间件的创建思路分析

    网上有很多解析laravel中间件的实现原理,但是不知道有没有读者在读的时候不明白,作者是怎么想到要用array_reduce函数的? 本文从自己的角度出发,模拟了如果我是作者,我是怎么实现这个中间件 ...

  9. C# 录音和播放录音-NAudio

    在使用C#进行录音和播放录音功能上,使用NAudio是个不错的选择. NAudio是个开源,相对功能比较全面的类库,它包含录音.播放录音.格式转换.混音调整等操作,具体可以去Github上看看介绍和源 ...

  10. java后台调用文件上传接口

    借鉴:https://blog.csdn.net/yjclsx/article/details/70675057 /** * 调用流程上传文件接口上传文件 * @param url * @param ...