目录

军规1:遵循PEP 8样式指南

军规2:了解字节序列(bytes)和字符串(str)的差异

用编程语言写代码是自由的,编译器不会强制你使用特定的格式编写程序(只要符合语法,编译器才不管你呢!)。所以很多程序员就会将Python当做自己熟悉的Java、C++等语言来用。 不过这些编码方式真的是最好的选择吗?本系列文章将为你揭秘68种在编写Python代码中的规则,这些规则将会让你Python程序更加健壮,运行效率更高。

军规1:遵循PEP 8样式指南

Python的PEP 8是Python官方提供了关于如何格式化Python代码的样式指南。尽管可以用任何有效的方式编写Python代码,但是,使用一致的样式会使你的代码更易于访问和阅读,以及与其他Python程序员使用同一种样式有助于项目上的分工协作。即使你是唯一会阅读代码的人,遵循样式指南也可以让你的代码更容易维护,并可以避免许多常见错误。

关于PEP 8的详细内容,读者可以查看下面的页面:

https://www.python.org/dev/peps/pep-0008/

下面挑出PEP 8中一些常见的应该注意的地方:

空格

在Python语言中,空格是有实际意义的。Python程序员应该更关注空格的用法,下面是与空格相关的一些建议(并不一定要遵守,但按照这个规范,会让你的Python程序看着更舒服):

(1)使用空格代替Tab进行缩进;

(2)尽管缩进可以使用任意多个空格,但建议统一使用4个空格进行缩进;

(3)每行不应该有过多的字符,建议最多不要超过79的字符;

(4)如果每行的字符过多(超过79个),应该折到下一行,而且应该在当前缩进的基础上再使用4个空格进行缩进,如下图所示:

(5)在文件中,如果函数和类相邻,建议使用两个空行将他们分开,这样会让代码一目了然;

(6)在类中,相邻的方法之间应该用一个空行分隔;

(7)在字典中,不要在key和冒号(:)之间放置空格,如果对应的值与key和冒号在同一行,应该在值前面放置一个空格;

(8)在变量赋值时,等号(=)前面和后面应该有一个空格;

(9)对于类型注释(type annotations),要确保变量和冒号直接没有空格,而且要在类型信息前面使用一个空格,如下图所示:

命名规则:

PEP 8程序的不同部分采用统一的命名风格。因为拥有统一风格的命名,会让代码更容易阅读,下面是一些推荐的命名规则:

(1)函数、变量和属性应该使用小写字母加下划线(_)的风格,例如,get_name,product_id等;

(2)被保护的(protected)的实例属性应该在名字前面加一个下划线,例如,_name,_product_id等;

(3)私有(private)实例属性应该在名字前面加两个下划线(__),例如,__name,__product_id等;

(4)类名应该使用大驼峰格式,也就是每一个单词首字母都要大写,例如,MyClass,Test,Product等;

(5)模块层常量的名字所有的字母都应该大写,如果包含多个单词,中间用下划线分隔,例如,PRODUCT_ID,OS_PATH等;

(6)类中的实例方法的第1个参数应该使用self(尽管可以使用任意参数名,但推荐使用self),该参数引用了对象本身;

(7)类方法的第1个参数应该使用cls(尽管可以使用任意参数名,但推荐使用cls),该参数引用了类的本身;

表达式和语句:

Python禅宗指出:“应该有一种(最好只有一种)明显的方式来完成你的工作。”。PEP 8正常尝试按这个规则确定表达式和语句的编写风格。

(1)使用内联求反(if a is not b)代替对正表达式的求反(if not a is b);

(2)如果要判断序列(字符串、列表、字典等)是否为空(是否有元素),并不建议通过序列长度是否为0来判断(if len(somelist) == 0),而要直接使用not进行判断,例如,if not somelist。如果somelist是空串或空序列,那么not somelist就为True,当然,如果somelist不为空,那么somelist就被认为是True;

(3)尽量避免单行的if、for和while语句,除非是复合语句,否则为了清晰起见,应该将它们分布在多行;

(4)如果表达式过长,建议将这样的表达式分布在多行,这样更容易阅读。但注意要在每行结尾加连接符,并且从第2行开始在第1行的基础上再往后缩进4个空格;

导入模块:

下面是PEP8关于导入模块的一些建议:

(1)将import语句(包括from x import y和import x语句)放在文件的最顶端;

(2)如果在import语句中涉及到模块名,应该使用绝对的模块名,而不要使用相对的模块名。例如,为了从bar包导入foo模块,应该使用from bar import foo,而不要使用Import foo;

(3)如果必须要使用相对的模块名,应该显式使用from . import foo形式;

(4)导入模块应该按下面的顺序:

a. 标准的模块

b. 第三方的模块

c. 自己编写的模块

而且每一个子部分在导入时应该按字母顺序排列;

军规2:了解字节序列(bytes)和字符串(str)的差异

在Python语言中,有两个数据类型可以表示字符序列:字节序列和字符串。其中字节序列中包含了原始的,8位无符号的值,通常以ASCII编码形式显示:

如果用字节序列表示字符序列,应该以b开头,代码如下:

a = b'h\x65llo'
print(list(a))
print(a)

执行这段代码,会输出如下的结果:

[104, 101, 108, 108, 111]
b'hello'

字符串的实例包含了Unicode编码,这些编码表示人类语言的文本字符:

a = 'a\u0300 propos'
print(list(a))
print(a)

执行这段代码,会输出如下结果:

['a', '̀', ' ', 'p', 'r', 'o', 'p', 'o', 's']
à propos

值得注意的是,字符串并不包含与之关联的二进制编码,而字节序列也不包含与之关联的文本编码。为了将文本编码数据转换为二进制数据,必须调用字符串的encode方法。为了将二进制数据转换为文本编码数据,必须调用字节序列的decode方法。我们可以显式地指定这些方法的编码格式,或者接受这些方法的默认编码格式。默认编码格式通常是UTF-8,不过也并不是所有方法的默认编码格式都是UTF-8,具体情况请看下面的内容。

在编写Python程序时,在例接口最远的边界(也就是最初接触Unicode数据的地方)进行Unicode数据的编码和解码非常重要。 这种方法通常被称为Unicode三明治。 程序的核心应使用包含Unicode数据的str类型,并且不应对字符编码做任何假设。 这种方法使你可以非常容易接受其他文本编码(例如Latin-1,Shift JIS和Big5),同时严格限制输出文本编码(理想情况下为UTF-8)。

字符类型之间的分拆将导致Python代码中出现两种常见情况:

(1)操作的是包含UTF-8编码(或其他编码)的8位字节序列;

(2)操作的是没有特定编码的Unicode字符串;

下面给出两个函数来完成这些情形下的转换:

第1个颜色将字节序列或字符串转换一个字符串:

def to_str(bytes_or_str):if isinstance(bytes_or_str, bytes):# 将使用utf-8编码的字节序列转换为字符串value = bytes_or_str.decode('utf-8')else:# 将不含编码格式的字符串转换为字符串(其实就是该字符串本身)value = bytes_or_strreturn value  # 返回字符串print(repr(to_str(b'hello')))
print(repr(to_str('world')))

运行这段代码,会输出如下的结果:

'hello'
'world'

第2个函数用于将字节序列或字符串转换为字节序列:

def to_bytes(bytes_or_str):if isinstance(bytes_or_str, str):value = bytes_or_str.encode('utf-8')else:value = bytes_or_strreturn value  # 返回字节序列
print(repr(to_bytes(b'hello')))
print(repr(to_bytes('world')))

运行这段代码,会输出如下的结果:

b'hello'
b'world'

在Python中处理原始8位值和Unicode字符串时,有两个大陷阱。

第一个问题是字节和字符串的工作方式看似相同,但是它们的实例彼此并不兼容,因此你必须仔细考虑要传递的字符序列的类型。

字节序列与字符串都支持加号(+)运算,也就是说,可以用加号分别将字节序列和字符串连接起来,看下面的代码:

print(b'hello ' + b' world')
print('hello ' + 'world')

运行代码,会输出下面的内容:

b'hello  world'
hello world

但是不能将字节序列和字符串相加,例如,下面的代码会抛出异常:

print(b'hello ' + 'world')

抛出的异常如下:

Traceback (most recent call last):File "/python/bytes_str.py", line 36, in <module>print(b'hello ' + 'world')
TypeError: can't concat str to bytes

如果将字符串与字符序列相加,同样会抛出异常:

print('hello ' + b'world')

抛出的异常如下:

Traceback (most recent call last):File "/python/bytes_str.py", line 37, in <module>print('hello ' + b'world')  # 抛出异常
TypeError: can only concatenate str (not "bytes") to str

如果两侧的操作数都是字节序列或字符串,那么也可以用于逻辑比较(<、<=、>、>=等运算符)。

print('hello' > 'world')
print(b'hello' < b'world')

执行代码,会输出如下的结果:

False
True

与加号类似,字符串与字节序列不能直接比较,如下面的代码会抛出异常:

print(b'hello' > 'world')

抛出的异常:

Traceback (most recent call last):File "/python/bytes_str.py", line 41, in <module>print(b'hello' > 'world')
TypeError: '>' not supported between instances of 'bytes' and 'str'

与=、<、<=、>=、>=这些运算符不同,字节序列和字符串可以直接使用“==”判定是否相等。不过这是个陷阱,就算字节序列和字符串表面上每一个字符都是相同的,返回的结果仍然是False。

print(b'hello' == 'hello')

执行这行代码,会返回如下的结果:

False

百分号(%)用于分别格式化字符串和字节序列,

print(b'hello %s' % b'world')
print('hello %s' % 'world')

执行代码,会输出如下结果:

b'hello world'
hello world

但是不能传递字符串到字节序列中(反过来可以),因为Python并不清楚使用何种编码格式将字符串转换为字节序列:

print('hello %s' % b'world')  # 正常格式化
print(b'hello %s' % 'world')  # 抛出异常

执行代码,会抛出下面的异常:

Traceback (most recent call last):File "/python/bytes_str.py", line 50, in <module>print(b'hello %s' % 'world') # 抛出异常
TypeError: %b requires a bytes-like object, or an object that implements __bytes__, not 'str'

第2个问题是涉及文件句柄的操作(由打开的内置函数返回),写文件时默认Unicode字符串而不是字节序列。 这可能会导致抛出异常,尤其是对于习惯了Python 2的程序员而言。例如,假设我要向文件中写入一些二进制数据,下面的代码会抛出异常:

with open('data.bin', 'w') as f:f.write(b'\xf1\xf2\xf3\xf4\xf5')

执行代码,会抛出如下异常:

Traceback (most recent call last):File "/python/bytes_str.py", line 53, in <module>f.write(b'\xf1\xf2\xf3\xf4\xf5')
TypeError: write() argument must be str, not bytes

抛出异常的原因是该文件是以写文本模式('w')而不是写二进制模式('wb')打开的。 当文件处于文本模式时,写操作期望字符串包含Unicode数据,而不是字节序列。 所以为了避免抛出异常,应该用“wb”模式打开data.bin文件。

with open('data.bin', 'wb') as f:f.write(b'\xf1\xf2\xf3\xf4\xf5')

从文件读取数据也存在类似的问题。 例如,下面的代码尝试读取data.bin文件的内容:

with open('data.bin', 'r') as f:data = f.read()

执行代码,会抛出如下的异常:

Traceback (most recent call last):File "/python/bytes_str.py", line 61, in <module>data = f.read()File "/Users/lining/opt/anaconda3/lib/python3.7/codecs.py", line 322, in decode(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 0: invalid continuation byte

失败是因为文件是在读取文本模式('r')而非读取二进制模式('rb')中打开的。 当句柄处于文本模式时,它将使用系统的默认文本编码来使用bytes.encode(用于写入)和str.decode(用于读取)方法来解释二进制数据。 在大多数系统上,默认编码为UTF-8,该编码不能接受二进制数据b'\ xf1 \ xf2 \ xf3 \ xf4 \ xf5',因此会抛出异常。所以应该使用“rb”模式来打开二进制文件。

with open('data.bin', 'rb') as f:data = f.read()assert data == b'\xf1\xf2\xf3\xf4\xf5'  # 验证读取的数据是否与写入的数据一致

另外,还可以为open函数明确指定encoding参数(编码格式),以确保Python可以正确处理二进制的编码格式。 例如,下面的代码明确指定了使用cp1252编码格式以只读方式打开data.bin文件。

with open('data.bin', 'r', encoding='cp1252') as f:data = f.read()print(data)

执行代码,会输出如下内容:

ñòóôõ

现在来总结一下:

(1)字节序列(bytes)包含8位的二进制数据,字符串(str)包含Unicode编码的值;

(2)为了让程序更健壮,需要使用专门的函数来校验输入的是字节序列,还是字符串。如前面的to_bytes函数和to_str函数;

(3)字节序列和字符串不能混合在一起进行运算(如+、>、<、%等);

(4)如果你想读写二进制格式的文件,应该使用二进制模式打开文件(例如,"rb"或"wb");

(5)如果你想读写文本格式的文件,需要考虑文本的编码格式。需要显式通过encoding参数传入正确的编码格式;

Python高效编程之88条军规(1):编码规范、字节序列与字符串相关推荐

  1. Python高效编程之88条军规(2):你真的会格式化字符串吗?

    目录 1.  C风格的字符串格式化方式 2. 内建format函数与str.format方法 3. f-字符串 总结: 在微信公众号「极客起源」中输入595586,可学习全部的<Python高效 ...

  2. python 多线程编程之_thread模块

    python 多线程编程之_thread模块 参考书籍:python核心编程 _thread模块除了可以派生线程外,还提供了基本的同步数据结构,又称为锁对象(lock object,也叫原语锁.简单锁 ...

  3. Python网络编程之二:网络编程基础

    Python网络编程之二:网络编程基础 一.基础概念 1.两个地址 1.1.mac地址 mac地址:直译为媒体存取控制位址,也称为局域网地址.MAC位址.以太网地址或物理地址,它是一个用来确认网络设备 ...

  4. Python网络编程之day01-网络编程基础

    Python网络编程之day01-网络编程基础 文章目录 Python网络编程之day01-网络编程基础 一.网络通信概述 二.IP地址 三.ping,ifconfig,ipconfig 四.端口 代 ...

  5. Python并发编程之threading模块

    Python并发编程之threading模块 threading 模块 1. Timer对象 2. Lock对象 3. RLock 4. 信号量和有边界的信号量 5. 事件 6. 条件变量 7. 使用 ...

  6. python编码规范手册-Python官方竟然给出了一种编码规范PEP 8

    写在之前 每个人都有自己的代码风格,随着写的行数增加,自己对于代码的审美也会变的不一样,这就像是一个逐渐蜕变的过程,每过一段时间回头再去看看自己之前写的代码就会生出一种「这么丑的玩意儿竟然是我写的」这 ...

  7. python itertools模块位置_Python高效编程之itertools模块详解

    0 前言 说到处理循环,我们习惯使用for, while等,比如依次打印每个列表中的字符:lis = [ I , love , python ] for i in lis: print(i) I lo ...

  8. batch print pro_研之有效 | 高效编程之Batch批处理程序

    前言 常言道:"工欲善其事,必先利其器".要有效率地开展科研工作,不仅需要持续投入时间和热情,还有必要掌握好文献管理.数据处理与分析以及团队合作等各项技能.一款好的软件能大幅提高科 ...

  9. python函数式编程之functools、itertools、operator详解

    文章目录 写在篇前 itertools 无穷迭代器 最短停止迭代器 排列组合迭代器 operator 基本运算符函数 属性查询 functools partial & partialmetho ...

最新文章

  1. 别瞎操心了!机器人根本不会抢你的饭碗
  2. (0023)iOS 开发之Mac上MySQL服务的卸载
  3. linux 云主机 卡顿 排查过程
  4. Android中自定义View的研究 -- 在XML中引用自定义View
  5. CTFshow 命令执行 web31
  6. RMAN CONFIGURE
  7. VDP文件级恢复需要在用VDP备份的机器上浏览
  8. SAP CRM Business transaction save mode
  9. XML数据岛(XML Data Island)(只适用于ie)
  10. 675. Cut Off Trees for Golf Event
  11. javascript 西瓜一期 03 机器语言与高级语言
  12. java替换最后一个字符_Excel公式技巧23: 同时定位字符串中的第一个和最后一个数字...
  13. ASP基础教程之ASP AdRotator 组件的使用
  14. 工具----10、Backtrack的安装
  15. CCC认证和CQC认证的区别有哪些
  16. Report Categories修改LDB选择屏幕
  17. WDM和OTN技术简介
  18. python调包师_为“Python调包侠” 画像
  19. Simulink仿真WiFi信号
  20. 从Nginx到Pandownload,程序员如何避免面向监狱编程

热门文章

  1. C++中的bool类型
  2. 谈谈我对 iPhone5 全景照相机 的理解
  3. vs2017编译iconv
  4. BZOJ 4239 巴士走读
  5. 怎么用stata打开dta文件_第三十二章、用c语言打开文件
  6. C语言打开中文路径文件
  7. 注册服务号、订阅号流程
  8. board oracle tarot_Thoth and the Tarot dpedtech(透特和塔罗牌dpedtech).pdf
  9. BiLSTM+CRF实现AI诗人与长短记忆网络
  10. 装几只“加密狗”都无妨:活用打印机共享器