Python编程规范修炼

Guido van Rossum(吉多·范罗苏姆,Python 创始人 )说过,代码的阅读频率远高于编写代码的频率。毕竟,即使是在编写代码的时候,你也需要对代码进行反复阅读和调试,来确认代码能够按照期望运行。

接下来我们今天就为大家分享《Python编程规范:让你的代码脚下生风》第三弹《Google开源项目风格指南》

Python是Google主要的脚本语言。这本风格指南主要包含的是针对python的编程规范。

Google开源项目风格指南-Python风格指南包含以下两个主要内容

  1. Python风格规范

  2. Python语言规范

文档地址: Google开源项目风格指南

一、Python风格规范

在Google的Python风格规范中主要涉及了像如何使用缩进、空格和代码行的长度、注释、文档以及老生常谈的命名规范、分号的使用、导入格式等,这些基本上在前面的分享中都提及过,这里就不在过多解释。

但是在Google风格规范中提及了Main的使用,我认为这个还是比较重要的内容。

首先我们知道Python语音的一个特点就是模块与包,我们可以在程序中导入第三方模块或者包,当然也可以在项目中自定义模块与包,这样的做法会让我们把一些功能的实现进行封装,剩下的只需要关注我们当前的业务代码即可。

因此我们在模块文件或者自定义模块时中总是能够看到或使用下面这样的语句。

1 def main(): 2 3 4 5 6 7     print('run') 8 9 10 11 12 13 14 15 16 17 18 19 if __name__ == '__main__': 20 21 22 23 24 25    main() 26 27 28 29 30 31 32 33 34 35 36 37 ‘’‘ 38 39 40 41 42 43 __name__ 是当前模块名,当模块被直接运行时模块名为 ‘__main__’ 44 45 46 47 48 49 一般我们会在模块测试时,把相关的调用或者执行放在`if __name__ == '__main__'`当中。 50 51 52 53 54 55 这样可以保证我们的测试代码不会在模块被导入时执行,而只是在模块被作为主程序时执行。 56 57 58 59 60 61 ’‘’ 62 63 64 65 66 67

这是一种很好的特性,而我们在进行模块开发时也变的更加方便,那么接下来我们看一下在Google编码规范中时如何对Main的使用进行规范化的。

Main

即使是一个打算被用作脚本的文件, 也应该是可导入的. 并且简单的导入不应该导致这个脚本的主功能(main functionality)被执行, 这是一种副作用. 主功能应该放在一个main()函数中.

在Python中, pydoc以及单元测试要求模块必须是可导入的.

你的代码应该在执行主程序前总是检查 if __name__ == '__main__' ,

这样当模块被导入时主程序就不会被执行.

1 def main(): 2 3 4 5 6 7     print('run') 8 9 10 11 12 13 14 15 16 17 18 19 if __name__ == '__main__': 20 21 22 23 24 25    main() 26 27 28 29 30 31

所有的顶级代码在模块导入时都会被执行.

要小心不要去调用函数, 创建对象, 或者执行那些不应该在使用pydoc时执行的操作.

二、 Python语言规范

接下来我们一起看下《Google开源项目风格指南》中对Python语言的一些规范吧

1.Lint

对你的代码运行pylint,pylint是一个在Python源代码中查找bug的工具.

可以捕获容易忽视的错误, 例如输入错误, 使用未赋值的变量等.

2.导入

仅对包和模块使用导入.并且必要时使用as

3.包

使用模块的全路径名来导入每个模块

4.异常

允许使用异常, 但必须小心。

总结几点如下:

  • 永远不要使用 except: 语句来捕获所有异常, 也不要捕获 Exception 或者 StandardError , 除非你打算重新触发该异常, 或者你已经在当前线程的最外层(记得还是要打印一条错误消息). 在异常这方面, Python非常宽容, except: 真的会捕获包括Python语法错误在内的任何错误. 使用 except: 很容易隐藏真正的bug.

  • 尽量减少try/except块中的代码量. try块的体积越大, 期望之外的异常就越容易被触发. 这种情况下, try/except块将隐藏真正的错误.

  • 使用finally子句来执行那些无论try块中有没有异常都应该被执行的代码. 这对于清理资源常常很有用, 例如关闭文件.

5.全局变量

避免全局变量,导入时可能改变模块行为, 因为导入模块时会对模块级变量赋值.

避免使用全局变量, 用类变量来代替. 但也有一些例外:

  1. 脚本的默认选项.

  2. 模块级常量. 例如: PI = 3.14159. 常量应该全大写, 用下划线连接.

  3. 有时候用全局变量来缓存值或者作为函数返回值很有用.

  4. 如果需要, 全局变量应该仅在模块内部可用, 并通过模块级的公共函数来访问.

6.嵌套/局部/内部类或函数

鼓励使用嵌套/本地/内部类或函数

定义:

  • 类可以定义在方法, 函数或者类中.

  • 函数可以定义在方法或函数中.

  • 封闭区间中定义的变量对嵌套函数是只读的.

优点: 允许定义仅用于有效范围的工具类和函数.

缺点: 嵌套类或局部类的实例不能序列化(pickled).

结论: 推荐使用.

7.列表推导(List Comprehensions)

可以在简单情况下使用,复杂的列表推导或者生成器表达式可能难以阅读.

先看结论

简单的列表推导可以比其它的列表创建方法更加清晰简单.

但是复杂的列表推导或者生成器表达式可能难以阅读.

因此

列表推导适用于简单情况. 每个部分应该单独置于一行: 映射表达式, for语句, 过滤器表达式.

禁止多重for语句或过滤器表达式. 复杂情况下还是使用循环.

下面是一些代码示例,可以感受一下

1 # 适用于简单情况. 2 3 4 5 6 7 # 每个部分应该单独置于一行: 映射表达式, for语句, 过滤器表达式. 8 9 10 11 12 13 # 禁止多重for语句或过滤器表达式. 复杂情况下还是使用循环. 14 15 16 17 18 19 20 21 22 23 24 25 # Yes: 26 27 28 29 30 31 result = [] 32 33 34 35 36 37 for x in range(10): 38 39 40 41 42 43    for y in range(5): 44 45 46 47 48 49        if x * y > 10: 50 51 52 53 54 55            result.append((x, y)) 56 57 58 59 60 61 62 63 64 65 66 67 for x in xrange(5): 68 69 70 71 72 73    for y in xrange(5): 74 75 76 77 78 79        if x != y: 80 81 82 83 84 85            for z in xrange(5): 86 87 88 89 90 91                if y != z: 92 93 94 95 96 97                    yield (x, y, z) 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 # No: 116 117 118 119 120 121 result = [(x, y) for x in range(10) for y in range(5) if x * y > 10] 122 123 124 125 126 127 128 129 130 131 132 133 return ((x, y, z) 134 135 136 137 138 139        for x in xrange(5) 140 141 142 143 144 145        for y in xrange(5) 146 147 148 149 150 151        if x != y 152 153 154 155 156 157        for z in xrange(5) 158 159 160 161 162 163        if y != z) 164 165 166 167 168 169

8.默认迭代器和操作符

如果类型支持, 就使用默认迭代器和操作符. 比如列表, 字典及文件等.

优点:

  • 默认操作符和迭代器简单高效, 它们直接表达了操作, 没有额外的方法调用.

  • 使用默认操作符的函数是通用的. 它可以用于支持该操作的任何类型.

缺点:

  • 你没法通过阅读方法名来区分对象的类型(例如, has_key()意味着字典). 不过这也是优点.

    1# 内建类型也定义了迭代器方法. 优先考虑这些方法, 而不是那些返回列表的方法. 2 3 4 5 6 7# 当然,这样遍历容器时,你将不能修改容器. 8 9 10 11 12 13 14 15 16 17 18 19# Yes:   20 21 22 23 24 25for key in adict: ... 26 27 28 29 30 31if key not in adict: ... 32 33 34 35 36 37if obj in alist: ... 38 39 40 41 42 43for line in afile: ... 44 45 46 47 48 49for k, v in dict.iteritems(): ... 50 51 52 53 54 55 56 57 58 59 60 61# No: 62 63 64 65 66 67for key in adict.keys(): ... 68 69 70 71 72 73if not adict.has_key(key): ... 74 75 76 77 78 79for line in afile.readlines(): ... 80

9.生成器

按需使用生成器.

生成器的定义:

所谓生成器函数, 就是每当它执行一次生成(yield)语句, 它就返回一个迭代器, 这个迭代器生成一个值.

生成值后, 生成器函数的运行状态将被挂起, 直到下一次生成.

优点:简化代码, 因为每次调用时, 局部变量和控制流的状态都会被保存. 比起一次创建一系列值的函数, 生成器使用的内存更少.

鼓励使用. 注意在生成器函数的文档字符串中使用”Yields:”而不是”Returns:”.

10.Lambda函数

适用于单行函数.

优点:方便。

缺点:比本地函数更难阅读和调试. 没有函数名意味着堆栈跟踪更难理解. 由于lambda函数通常只包含一个表达式, 因此其表达能力有限.

结论:适用于单行函数. 如果代码超过60-80个字符, 最好还是定义成常规(嵌套)函数.

11.条件表达式

条件表达式是对于if语句的一种更为简短的句法规则. 例如: x = 1 if cond else 2 .

优点:比if语句更加简短和方便.

缺点:比if语句难于阅读. 如果表达式很长, 难于定位条件.

结论:适用于单行函数. 在其他情况下,推荐使用完整的if语句.

12.默认参数值

适用于大部分情况。

1 # 不要在函数或方法定义中使用可变对象作为默认值. 2 3 4 5 6 7 Yes: def foo(a, b=None): 8 9 10 11 12 13         if b is None: 14 15 16 17 18 19             b = [] 20 21 22 23 24 25 No:  def foo(a, b=[]): 26 27 28 29 30 31         ... 32 33 34 35 36 37 No:  def foo(a, b=time.time()):  # The time the module was loaded??? 38 39 40 41 42 43         ... 44 45 46 47 48 49 No:  def foo(a, b=FLAGS.my_thing):  # sys.argv has not yet been parsed... 50 51 52 53 54 55         ... 56 57 58 59 60 61

结论:鼓励使用,但需要注意:不要在函数或方法定义中使用可变对象作为默认值.

13.True/False的求值

尽可能使用隐式false,

Python在布尔上下文中会将某些值求值为false. 按简单的直觉来讲, 就是所有的”空”值都被认为是false.

因此0, None, [], {}, “” 都被认为是false.

优点:使用Python布尔值的条件语句更易读也更不易犯错. 大部分情况下, 也更快.

结论: 尽可能使用隐式的false, 例如: 使用 if foo:而不是 if foo != []:

不过还是有一些注意事项需要你铭记在心:

1 # 1. 永远不要用==或者!=来比较单件, 比如None. 使用is或者is not. 2 3 4 5 6 7 8 9 10 11 12 13 # 2. 注意: 当你写下 `if x:`, 你其实表示的是 `if x is not None` . 14 15 16 17 18 19 20 21 22 23 24 25 # 3. 永远不要用==将一个布尔量与false相比较. 使用 `if not x:` 代替. 26 27 28 29 30 31 #   如果你需要区分false和None, 你应该用像 `if not x and x is not None:` 这样的语句. 32 33 34 35 36 37 38 39 40 41 42 43 # 4. 对于序列(字符串, 列表, 元组), 要注意空序列是false. 44 45 46 47 48 49 #   因此 `if not seq:` 或者 `if seq:``if len(seq):``if not len(seq):` 要更好. 50 51 52 53 54 55 56 57 58 59 60 61 # 5. 处理整数时, 使用隐式false可能会得不偿失(即不小心将None当做0来处理). 62 63 64 65 66 67 #   你可以将一个已知是整型(且不是len()的返回结果)的值与0比较. 68 69 70 71 72 73 74 75 76 77 78 79 # Yes: 80 81 82 83 84 85 if not users: 86 87 88 89 90 91         print 'no users' 92 93 94 95 96 97 98 99 100 101 102 103     if foo == 0: 104 105 106 107 108 109         self.handle_zero() 110 111 112 113 114 115 116 117 118 119 120 121     if i % 10 == 0: 122 123 124 125 126 127         self.handle_multiple_of_ten() 128 129 130 131 132 133         134 135 136 137 138 139 #No:   140 141 142 143 144 145 if len(users) == 0: 146 147 148 149 150 151         print 'no users' 152 153 154 155 156 157 158 159 160 161 162 163     if foo is not None and not foo: 164 165 166 167 168 169         self.handle_zero() 170 171 172 173 174 175 176 177 178 179 180 181     if not i % 10: 182 183 184 185 186 187         self.handle_multiple_of_ten() 188 189 190 191 192 193         194 195 196 197 198 199 # 注意‘0’(字符串)会被当做true. 200 201 202 203 204 205

14.函数与方法装饰器

如果好处很显然, 就明智而谨慎的使用装饰器

优点:优雅的在函数上指定一些转换. 该转换可能减少一些重复代码, 保持已有函数不变(enforce invariants), 等.

缺点:

  • 装饰器可以在函数的参数或返回值上执行任何操作, 这可能导致让人惊异的隐藏行为.

  • 而且, 装饰器在导入时执行.

  • 从装饰器代码的失败中恢复更加不可能.

结论: 如果好处很显然, 就明智而谨慎的使用装饰器,但注意使用

  • 装饰器应该遵守和函数一样的导入和命名规则.

  • 装饰器的python文档应该清晰的说明该函数是一个装饰器.

  • 请为装饰器编写单元测试.

  • 避免装饰器自身对外界的依赖(即不要依赖于文件, socket, 数据库连接等), 因为装饰器运行时这些资源可能不可用(由 pydoc 或其它工具导入).

  • 应该保证一个用有效参数调用的装饰器在所有情况下都是成功的.

  • 装饰器是一种特殊形式的”顶级代码”.

15.线程

优先使用Queue模块的 Queue 数据类型作为线程间的数据通信方式.

Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。

这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

16.威力过大的特性

避免使用这些威力过大的特性

定义:

Python是一种异常灵活的语言, 它为你提供了很多花哨的特性, 诸如元类(metaclasses), 字节码访问, 任意编译(on-the-fly compilation), 动态继承, 对象父类重定义(object reparenting), 导入黑客(import hacks), 反射, 系统内修改(modification of system internals), 等等.

优点:

  • 强大的语言特性, 能让你的代码更紧凑.

缺点:

  • 使用这些很”酷”的特性十分诱人, 但不是绝对必要. 使用它们将更加难以阅读和调试.

  • 开始可能还好, 但当你回顾代码, 它们可能会比那些稍长一点但是很直接的代码更加难以理解.

结论:莫装B

Google-Python编码规范 总结

在上面的内容中我们对Python中的Main的使用以及针对Python语言特性的语法规范进行了解读,那么希望小伙伴们通过本期以及上一期《Python编程规范修炼-PEP8规范解读》的文章对Python编程的规范有了一个很深的认知,如果对本期关于编码规范的内容进行一个总结的话

请务必保持代码的一致性

如果你正在编辑代码, 花几分钟看一下周边代码, 然后决定风格. 比如如果它们在所有的算术操作符两边都使用空格, 那么你也应该这样做. 如果它们的注释都用标记包围起来, 那么你的注释也要这样.总之保持风格的统一才是王道

以上就关于分期的分享,在后面我们还会为大家分享关于Python代码安全、精简语句、调试和性能分析以及代码分解和单元测试等Python编程规范的系列内容,感兴趣的小伙伴欢迎关注我的公众号后厂程序员。

如果喜欢或者对你有帮助的小伙伴,欢迎大家关注我的公众号:后厂程序员,并分享、点赞、在看 三连

点我留言

本文分享自微信公众号 - 后厂程序员(houchang_work)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )