目录
用编程语言写代码是自由的,编译器不会强制你使用特定的格式编写程序(只要符合语法,编译器才不管你呢!)。所以很多程序员就会将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_str
-
return 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_str
-
return 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参数传入正确的编码格式;
转载:https://blog.csdn.net/nokiaguy/article/details/108446446