培养pythonic思维(1-10条)

2022-05-05 16:11:26 浏览数 (1)

1.python版本选择

python目前的版本分为python2和python3,并且这两个版本并不兼容。笔者写这篇文章的时候是2022-05-03,此时python2早已停止了维护(2020年1月1日,python2停止更新维护)。建议新入手的python使用者选择python3。如果你的项目深度依赖于python2代码库,那么可以考虑2to3与six工具来过渡到python3。

2.PEP8风格指南

PEP8风格指南的是python代码风格的指南,如果大家的python代码都 遵照PEP8风格,那么就让代码的易读性大大提高。PEP8随着python的 发展也在更新。大家可以查看PEP8指南。

与空白有关的建议

  1. 用空格表示缩进,不要用制表符;
  2. 每一层代码都应该缩进4个空格;

因此,建议大家设置tab键为4个空格。

  1. 对于占据多行的长表达式而言,除了首行之外的其余各行都应该在通常的缩进级别上在加上4个空格;
  2. 在同一份代码文件中,函数与类之间用两行空行隔开;
  3. 在同一个类中的方法与方法之间,使用一行空行隔开;
  4. 使用字典时,键与冒号之间不加空格,值与冒号之间加一个空格;
  5. 给变量赋值的时候,赋值符号左右各加一个空格即可;
  6. 给变量做类型注解的时,变量名和冒号之间不加空格,在类型和冒号之间加一个空格。

与命名有关的建议

  1. 函数,变量以及属性用小写字母,各个单词之间用下划线相连;
  2. 受保护的实例属性,用一个下划线开头;
  3. 私有的实例属性,用两个下划线开头;
  4. 类和异常命名时,每个单词的首字母需要大写;
  5. 模块基本的常量,每个字母都大写,单词之间使用下划线相连;
  6. 类的实例方法的第一个参数应该被命名为self,用来表示对象本身;
  7. 类方法的第一个参数应该命名为cls,用来表示类本身。

与表达式和语句有关的建议

  1. 采用行内否定,把否定词直接写在需要否定的内容前面。例如:if a is not b,而不是if not a is b
  2. 不要通过长度来判断容器或者序列是不是空,而是应该使用if not xxx来判断不为空,python会自动判断空值为False,非空值为True
  3. 除了在列表推导之外,不要讲if while execpt等写在一行;
  4. 如果表达式一行写不下,可以使用圆括号包括起来,然后在适当的地方换行,不建议使用从C/C 处借鉴的符号续行。

与引入有关的建议

  1. import语句总是应该放在文件开头;
  2. 引入模块的时候,应该使用绝对名称,而不是使用相对名称。例如,当前路径有名为bar的模块,应该使用from bar import foo,而不是使用import bar
  3. 如果一定要使用相对名称,那么应该明确的写为from . import foo
  4. 文件中的import语句应该按三个顺序来引入。首先引入标准库中的模块,其次引入第三方模块,最后引入自己的模块。

pythonic

pythonic这个词用来形容python开发界的特定风格,这种风格是大家在写代码的过程中逐渐形成的习惯。

python之禅中提到的python代码设计哲学“每件事都应该有简单的做法,而且最好只有一种”。python开发者不喜欢写复杂的代码,而是喜欢直观,简单且易懂的方式编写代码。

3.bytes和str的区别

bytes

bytes实例包含的是原始数据,即8位无符号值。

在网络传输,读写二进制文件的时候,通常使用bytes类型。

str

str实例包含的是Unicode码点。

在python3中,默认的编码方案是utf-8.

在编写python代码的时候,一定要把解码和编码操作放在最外层来做,让程序的核心部分可以使用Unicode数据来操作,这种办法叫做Unicode三明治。程序核心应该使用str类型的Unicode数据,并且最好使用utf-8编码。

bytes和str的区别

python3是强类型语言,bytes和str是两种不同的数据类型,它们之间不能直接运算。而是必须转换为统一的数据类型。通常是通过下面这两个辅助函数来完成。

代码语言:javascript复制
def to_str(bytes_or_str):
    """bytes转str"""
    if isinstance(bytes_or_str, bytes):
        value = bytes_or_str.decode('utf-8')
    else:
        value = bytes_or_str
    return value


def to_bytes(bytes_or_str):
    """str转bytes"""
    if isinstance(bytes_or_str, bytes):
        value = bytes_or_str
    else:
        value = bytes_or_str.encode('utf-8')
    return value

操作文件

在操作文件的时候,python3内置的open函数返回的文件句柄默认使用Unicode字符串操作,而不是bytes实例,除非在open函数中指定以二进制方式打开文件。例如:

代码语言:javascript复制
open('data.bin', 'rb')    # 以二进制只读方式打开
open('data.txt', 'r')     # 以文本只读方式打开

4.使用f-string取代C风格的格式字符串和str.format方法

在python3.6中引入了f-string来进行格式字符串,它解决了C风格的格式字符串和str.format带来的缺点。

C风格的格式字符串

对于大多数人而言,C风格的格式字符串都是熟悉的。因为很多语言的格式字符串写法都来自于C语言的printf函数。python也支持C风格的格式字符串,例如:

代码语言:javascript复制
name = "Zhao Si"
age = 18
print("name is %s, age is %d" % (name, age))    # C风格的格式字符串

Effective Python中作者提到的几个缺点确实在经常使用python做命令行应用程序开发或者小脚本的时候,深有体会。下面是4个缺点。

如果%号右侧的元组值在类型或顺序上有变化,那么将可能导致错误。这个错误在C这种静态类型语言中在编译的时候会被指出,但是python是解释性的动态类型语言,不容易发现这个错误。(因此,我们对python这种动态类型语言做静态类型检查是必要的)

在填充模板之前,经常需要对值做一些处理,但是这样就会导致表达式会很长,可读性下降。

如果想用同一个值来填充格式字符串里的多个位置,那么就必须在%右侧的元组中多次重复该值。

python的%允许我们用dict来取代tuple,这样就可以避免1,3两个缺点。但是会将第2个缺点放大。并且带来了大量的废话书写。每个键至少要书写两次,不够简单。例如:

str.format

后来python3引入了str.format的高级字符串格式化机制,它比C风格的格式字符串要更加强大。但是依旧没有解决上面提到的几个问题,因此在python3.6的时候引入了f-string,一个str.format例子如下所示:

代码语言:javascript复制
name = "Zhao Si"
age = 18
gender = "man"
template = "name is {name}, age is {age}, gender is {gender}"
formated = template.format(name=name, age=age, gender=gender)
print(formated)

这种写法比C风格的格式字符串见到了一些,但是并没有完全解决2,4两个缺点。str.format引入了一套迷你规则来控制format,可以通过交互式终端输入help('FORMATTING')来查看这套规则。

f-string

f-string可以很好的解决上面提到的所有问题。新的语法要求在字符串之前加上F(f)来作为前缀形成f-string.

f-string把格式字符串的表达能力发挥到极致,它彻底解决了上面的第4个缺点,也就是键名重复导致的冗余问题。例如:

代码语言:javascript复制
key = 'my_var'
value = 1.234

formatted = f'{key} = {value}'
print(formatted)

并且,f-string也支持str.format方法所支持的那套迷你语言。同一个问题,使用f-string方式比C风格的格式字符串和str.format风格都要简单,优雅的多。这也和python之禅所追求的目标一致。

5.用辅助函数取代复杂的表达式

由于python的语法非常简明(不是简单),所以有时候只用一条表达式就能实现许多逻辑。例如,求一句英文中最后一个单词的长度。

代码语言:javascript复制
s = "I am a developer"
print(len(s.split(' ')[-1]))    # 求最后一个单词长度并输出

一行代码即可求解,但是这样的表达式是复杂的,因此应该考虑把它拆分开来,形成多个部分,并且把这套逻辑写道辅助函数中,这样虽然多了几行代码,但是可以让程序更加清晰,更加容易修改,所以总体而言还是非常值得使用辅助函数来代替复杂表达式。

6.把数据结构拆分到多个变量里,不用使用下标访问

这个问题,实际上可以通过namedtuple来代替tuple从而得到解决。当然,我们也可以通过类中的属性来解决。但是这里介绍的是使用多个变量来替代。也就是主要介绍解包unpacking,例如:

代码语言:javascript复制
people = ("Zhao Si", 18, "man")
name, age, gender = people

通过unpacking,可以让代码更加清晰,通过使用变量来操作相应的值,而且只需要一行代码即可把people中的多个值赋给相应的变量。

7.尽量用enumerate取代range

在我们需要通过下标操作的时候,通常会使用range函数。例如:

代码语言:javascript复制
num_list = [1, 3, 5, 11, 23, 58, 98, 23]
for i in range(len(num_list)):
    print(f"{i}: {num_list[i]}")

这种写法需要我们先求取列表的长度,然后根据长度来缺点循环次数,最后使用每次循环的i作为下标。这样写就很C语言,因此python提供了enumerate函数来解决刚才的问题,enumerate能够把任何一种迭代器封装为惰性生成器。每次循环的时候,enumerate能够给出下一个值以及本次循环的次数。另外,还可以通过enumerate的第二个参数来指定起始序号(默认为0),注意不要超出范围。下面通过next来展示enumerate的作用。

代码语言:javascript复制
num_list = [1, 3, 5, 11, 23, 58, 98, 23]
it = enumerate(num_list)    # it是个迭代器
print(next(it))
print(next(it))
print(next(it))

输出结果如下所示:

代码语言:javascript复制
(0, 1)
(1, 3)
(2, 5)

现在,我们改写上面的for循环如下:

代码语言:javascript复制
num_list = [1, 3, 5, 11, 23, 58, 98, 23]
for i, num in enumerate(num_list):
    print(f"{i}: {num_list[i]}")

0 人点赞