Effective Python note(1)

Effective Python note(1)

Pythonic:

PEP8 的一些重要规范:

  • 每行字符数不超过79
  • 长表达式换行时,首行后每行额外缩进4个空格
  • 文件中函数与类间用两个空行分隔
  • 同一类内各方法间用一个空行隔开
  • 函数、变量及属性命名使用小写字母并以下划线相连,如 lowercase_underscore
  • 受保护实例属性以单下划线开头,例如 _leading_underscore
  • 私有实例属性以双下划线开头,例如 __double_leading_underscore
  • 类与异常名采用每个单词首字母大写的格式,例如 CapitalizedWord
  • 模块级别常量全用大写字母拼写,单词间以下划线连接,如 ALL_CAPS
  • 类方法首个参数应命名为 cls 表示该类自身
  • 如必须相对导入,则明确书写:from . import foo
  • 文件中的import语句按顺序分为三部分:标准库模块、第三方模块和自定义模块。各部分内部按照模块名称字母顺序排列import语句
  • 检查somelist是否为非空值(如[1]或’hi’)时,遵循此原则;非空值在条件判断中默认视为True
  • zip函数同时遍历两个迭代器

更多规范查询官方手册,最好全部都看一遍

bytes 和 str区别与操作

  1. 类型本质
    • str(字符串):表示文本数据,存储的是Unicode编码的字符序列。Python 3中所有文本字符串默认都是Unicode编码的
    • bytes(字节串):表示二进制数据,存储的是一个8位字节的序列。它通常用于网络传输、磁盘文件读写等场景,可能包含图像、音频等原始非文本数据
  2. 内容形式
    • str 类型的数据由一系列Unicode码点组成,每个码点对应一个字符
    • bytes 类型的数据是由一连串的整数(0-255)组成的序列,这些整数代表了原始字节值
  3. 操作方式
    • str 字符串可以进行各种文本操作,如连接、查找、替换等,并支持多语言字符
    • bytes 字节串不能直接进行文本处理操作,但可以执行二进制操作,如按字节切片、比较等
  4. 转换关系
    • 从文本到二进制:通过编码(encoding),将 str 转换为 bytes,例如使用 my_str.encode('utf-8') 将文本转换成UTF-8编码的字节串
    • 从二进制到文本:通过解码(decoding),将 bytes 转换为 str,例如使用 my_bytes.decode('utf-8') 将UTF-8编码的字节串转换回文本

Python 3 不允许隐式地混合 strbytes 数据,比如你不能拼接字符串和字节流,也不能在字节串里搜索字符串,必须显式地进行类型转换后才能进行相应的操作。编写Python程序时,一定要先把编码和解码操作放在界面外围,程序的核心部分使用str数据类型,且不要对编码做任何假设

例如我们需要编写接受strbytes,并总是返回str 的 方法

1
2
3
4
5
6
def to_str(Cbytes_or_str):
if isinstance(bytes_or_str,bytes) :
Value = bytes_or_str.decode("utf-8")
else:
Value = bytes_or_str
return value

合理利用try except else finally结构中的每个代码块

  • try 块用于测试一段代码是否存在错误

  • except 块用于处理错误

  • else 块用于在没有错误时执行代码

  • finally 块用于无论 tryexcept 块的结果如何都要执行的代码

​ 不论try块是否发生异常,均可通过try/finally复合语句中的finally块执行清理工作。若在成功执行try块后,希望在finally块的清理代码之前执行某些操作,则可将这些操作置于else块中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def divide_json(Cpath):
handle = open(Cpath, "r+")
# 可能引发 IOError
try:
data = handle.read() # 可能引发 UnicodeDecodeError
op = json.loads(data) # 可能引发 ValueError
value = op['numerator'] / op['denominator'] # 可能引发 ZeroDivisionError
except ZeroDivisionError as e:
return UNDEFINED
else:
op['result'] = value
result = json.dumps(op)
handle.seek(0)
handle.write(result) # 可能引发 IOError
finally:
handle.close() # 总会执行
return value

函数:

  • 尽量使用异常来表示特殊情况,而不是返回None
  • 慎用nonlocal语句,因为它像global那样容易被滥用。建议仅在非常简单的函数中使用这种机制。nonlocal的副作用很难追踪
  • 考虑使用生成器来改写直接返回列表的函数
  • 使用只能以关键字形式指定的参数,以确保代码明晰

*args**kargs参数使用:

  • 令函数接受可选的位置参数(由于这种参数习惯上写作*args,所以又被称为star args或星号参数),能够使代码更加清晰,并减少 visual noise
  • 尽量只在函数参数个数少的情况下使用这两个可变长参数
  • 在已经接受*args参数的函数上面继续添加位置参数,可能会产生难以排查的bug

例如,要定义一个log函数以便打印一些调试信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def log(message, *values):
# 如果没有values,直接打印message
if not values:
print(message)
else:
# 将values中的元素转换为字符串并用逗号连接
values_str = ', '.join(str(x) for x in values)
# 打印message和values_str
print(f'{message}: {values_str}')

# 使用示例
log('My numbers are', 1, 2)
log('Hi there')
>>>
My numbers are: 1, 2
Hi there

在这个例子中,log函数通过*values参数可以接受任意数量的额外参数,这些参数在打印时会被格式化为一个列表。这种使用*args的方式使得函数调用更加灵活,同时也提高了代码的可读性

​ 但是这种参数会带来两个问题:

  • 变长参数在传给函数时,总是要先转化成元组(tuple)。这意味着,如果用带有 * 操作符的生成器作为参数来调用这种函数,那么 Python 就必须要先把该生成器完整地迭代一轮,并将生成器所生成的每一个值都存储起来。这可能会消耗大量内存,并导致程序崩溃
  • 如果以后要给函数添加新的可选位置参数,那就必须修改原来调用该函数的那些旧代码。若是只给参数列表前方添加新的可选位置参数,而不更新现有的调用代码,那么可能会产生难以调试的错误

​ 对于第二个问题,我们可以使用**kargs参数或者*符号解决:

​ 在Python3中,定义的参数之间加上*,它表示在该位置之前的所有参数必须是位置参数,而*之后的所有参数必须是关键字参数

1
2
3
4
5
6
def my_function(positional_arg1, positional_arg2, *, keyword_arg1, keyword_arg2):
print(positional_arg1, positional_arg2, keyword_arg1, keyword_arg2)

# 调用函数时,positional_arg1 和 positional_arg2 是位置参数,
# keyword_arg1 和 keyword_arg2 是关键字参数。
my_function('a', 'b', keyword_arg1='c', keyword_arg2='d')