Python 编程中经验型的一些规范
持续更新
使用li[:]代替copy.copy(li)
li[:]等价于copy.copy(li)li[:]不等价于copy.deepcopy(li)li[:]可以简化代码
从后开始访问列表
- list作为stack用时访问栈顶元素list[-1]
1
2
3
4
5
6
7
8
9list1 = [1, 5, 2, 3]
print list1[-1]
print list1[-2]
print list1[1:-1]
# # output:
# 3
# 2
# [5, 2]
函数内函数
- Python可以在函数内部定义函数,但是要注意不能与外部变量混淆
1
2
3
4
5
6
7
8
9
10
11
12
13
14def sum_all(alist):
def sum_two(a, b):
return a+b
result = 0
for i in alist:
result = sum_two(result, i)
return result
print sum_all([1, 2, 3, 4, 5])
# # output:
# 15
不要随意调用特殊方法
(比如:__len__)
- 特殊方法不应该被开发者调用,而应该被开发者定义新类时实现
- 一般使用
len(object)即可 - 原因: 内置的方法(比如:len())可能会直接返回对象中的
ob_size属性,而不用调用__len__()(这个函数往往会用迭代或者其他比较复杂的方法实现)
多使用列表推导
(list comprehension, 简写listcomps)和生成器表达式(generator expression, 简写genexps)
listcomps
1
[str(i) for i in range(1,10)]
genexps
1
(str(i) for i in range(1,10))
考虑使用reduce而不是循环语句
- reduce
1
2
3def add(x, y):
return x + y
reduce(add, [1,2,3,4,5])
1 | from operator import mul |
多使用pprint
- (Pretty Print)而不是print
1
2
3
4
5
6
7
8
9
10
11import pprint
l1 = (1, {2: 3}, "first", ("second", 5, 6, [7, 8, 9]), [1, 3, 5], "this is a pprint")
pprint.pprint(l1)
# Output:
(1,
{2: 3},
'first',
('second', 5, 6, [7, 8, 9]),
[1, 3, 5],
'this is a pprint')
多使用三目运算符
Python不像Java和C++一样,有x?y:z这样的三目运算符号,但是可以有自己的特殊使用方法,等价于三目运算符且更容易理解
1
2
3# same as x?y:z
result = y if x is True else z
return y if x is True else z- 注意: 在使用return语句中的三目运算时必须有else语句,否则编译不通过,因为返回值可能会缺失
匿名变量的使用
- 初始化一个列表时
1
list1 = [_ for _ in list2 if _.val > 10]
Python异常处理
异常处理的正确姿势, 注意如果不是必要的话不要使用Exception, 可以考虑列出来需要捕获的所有异常, 然后在函数内部判断异常类型
1
2
3
4
5try:
read some thing
except IOError, ValueError, e:
exception_type = type(e)
print("%s" % e)当然, 我们一般为了方便也会直接使用下面的方法
1
2
3
4
5try:
read some thing
except Exception, e:
exception_type = type(e)
print("%s" % e)
位运算
- 在Python中, 位运算与C++中有所不同
- 参见Python——位运算与逻辑运算和C++有什么不同
函数
- 内部函数访问全局变量时使用
global关键字声明(与外部函数一样) - 内部函数访问外部函数的变量使用
nonlocal(仅限Python 3)关键字声明 - 如果没有声明
- 变量变为只读的
- 可以写出
a.append()这样的语句 - 但不可以写出
a = b这样的语句
- 可以写出
- 变量变为只读的
- 函数内部变量与函数外同名时:
- 若写出赋值操作,则认为当前变量为局部变量
- 否则认为是函数外的全局变量
- 若先访问变量(一般访问,不是对变量赋值,此时视为全局变量),然后对变量赋值(此时视为局部变量),则产生矛盾,Python解释器报错
三元表达式的使用
如果在三元表达式使用在加法中,需要加上括号,不然整体意思会变成错误的
比如两个结点的加法操作,带有进位
carry,我们可以简单的写一行:下面是错误示例:
1
val = l1.val if l1 else 0 + l2.val if l2 else 0 + carry
上面的表达式可以理解为如果
l1不为空,返回l1.val,否则返回0 + l2.val if l2 else 0 + carry当
l1为空时又可以理解为如果l2不为空,返回0+l2.val否则返回0+carry
正确的写法
1
val = (l1.val if l1 else 0) + (l2.val if l2 else 0) + carry
迭代dict的键时用dict.iterkeys()
迭代键值时使用
iterkeys()而不是keys()iterkeys()返回一个迭代器而不是所有键值列表keys()返回所有键值列表1
2
3
4# 迭代效率高
for _ in dict.iterkeys()
# 迭代效率低
for _ in dict.keys()迭代值时也同理
1
2
3
4# 迭代效率高
for _ in dict.itervalues()
# 迭代效率低
for _ in dict.values()
zip实现二维列表的行列变换
只适用于Python3,因为涉及到
*操作1
2
3
4
5
6
7
8
9
10a = [[1,2,3],
[4,5,6]]
print([e for e in zip(*a)]) # 等价于 print([a for a in zip(a[0],a[1])])
print(*a) # 等价于 print(a[0],a[1])
# b = *a # 这行会报错: SyntaxError: can't use starred expression here
# Output
# [(1,4), (2,5), (3,6)]
# [1, 2, 3] [4, 5, 6]Python3中才能使用
*作为列表解包符Python3中
zip函数返回的是一个生成器而不是列表,所以需要迭代成列表注意:在代码中,
*a的作用是将列表a中的子列表解包(unpack)为独立的参数。具体来说:- 当
a是一个列表时,*a可以作为参数传递,不能直接赋值给其他变量 - 使用
print(a)时,它会直接打印整个嵌套列表,输出为:[[1, 2, 3], [4, 5, 6]] - 使用
zip(*a)时,*a会将a解包为两个独立的列表[1, 2, 3]和[4, 5, 6],等价于zip(a[0], a[1]) - 使用
print(*a)时,*a会将a解包为两个独立的列表[1, 2, 3]和[4, 5, 6],然后print函数会分别打印这两个列表,输出为:[1, 2, 3] [4, 5, 6]
- 当
Python中逻辑运算符的巧用
不建议使用
1 | print 0 or 1 |
理解
1
2
3
4
5
6
7print a or b
<==>
print a if a else b
print a and b
<==>
print b if a else a记忆:
- 把
a,b当做逻辑表达式,如果访问到b则返回b,否则返回a
- 把
reversed函数的使用
接受参数为可迭代对象,返回一个反向访问迭代对象的迭代器
1
2
3
4
5for i in reversed(range(n)):
<==>
for i in range(n-1, -1, -1):reversed的使用似乎更优雅,也更容易理解容易遗忘的点,需要注意: reversed的参数必须是可迭代的对象,而不是两个数字
将简单的句子优雅的写到一行
返回值
1
2
3
4if i < 0: return Flase
<==>
if i < 0:
return False其他简单的执行语句直接合并
1
i = 10; j = 10
判断语句之后使用
1
if bool: i = 10;
判断语句后使用多条
1
if bool: i = 10; j = 20
while语句后使用
1
while(bool): print 10; print 20
当句子较长时不建议使用
一般为了美观,平时的项目也不建议使用
刷题时为了让代码看起来简便,是可以使用的,但是这样会使得代码不易调试
总之:慎用
用同一个值初始化两个变量
1 | x = y = value |
- 在初始化链表头部时最常用
1
head, curr = ListNode(None)
使用collections.Counter
1 | import collections |
括号用作分组表达式
- 在 Python 中,括号用于分组表达式,不会改变表达式的值(除非用于元组、函数调用等特殊情况)
- 所以,
(1)只是1,不会变成元组或其他类型
- 所以,
a = (1)和a = 1等价a = (1,),则是元组类型,和a = 1不等价
for 和 while 语句内部作用域
Python 和 C++ 不一样
- C++ 中,通常变量在函数、类、或代码块(如循环、条件语句)内部定义时,只能在该作用域内访问
- Python 中,在函数、类中定义的是局部变量,但是循环、条件语句内部定义的不是
C++ 中,for 和 while 语句中定义的变量仅在语句内可访问(虽然 for 和 while 语句本身并不产生新的作用域,但是 for 和 while 语句需要接
{},这个会产生一个作用域块)1
2
3
4
5
6// C++ 中
for (int i = 0; i < 3; ++i) {
int x = i;
// 可以使用 i 和 x
}
// 这里不能访问 i 和 xPython 中,for 和 while 语句不会产生新的定义域,在 for 和 while 语句内部定义的变量,可以直接在外面访问
1
2
3
4
5
6
7
8
9
10
11
12for i in range(3):
x = i
print(x) # 输出 2,最后一次循环的值
print(i) # 输出 2,i 也不会被视作局部变量
i = 1
while True:
y = i
i += 1
if y >= 20:
break
print(y) # 输出 20注:当 for 循环迭代没有生效时,外面无法访问
1
2
3
4
5
6
7
8
9for x in range(11, 11):
print(x)
print("finally, x is", x) # NameError: name 'x' is not defined
for x in range(0):
print(x)
print("finally, x is", x) # NameError: name 'x' is not defined- 这是一个常见的 bug,所以建议在循环语句前加一个默认赋值操作,确保变量可访问
list 切片索引可以超过 list 长度而不会报错
- 示例
1
2
3
4a = [1,2,3,4,5,6]
print(a[:100]) # [1, 2, 3, 4, 5, 6]
print(a[99:100]) # []
递归函数中定义函数会影响递归函数的运行效率吗?
在 Python 中,在递归函数内部定义函数(嵌套函数)可能会对运行效率产生一定影响,主要体现在以下几个方面:
函数定义开销:
递归函数每次调用时,内部嵌套的函数都会被重新定义一次(生成新的函数对象),例如:
1
2
3
4
5
6
7def recursive(n):
# 每次递归调用时,都会重新定义 inner()
def inner(x):
return x * 2
if n == 0:
return 0
return inner(n) + recursive(n-1)每次递归调用
recursive(n)时,inner函数都会被重新创建,这会产生额外的内存分配和函数对象初始化开销如果将
inner定义在递归函数外部,只会初始化一次,避免了重复定义的成本
作用域查找的开销:
- 嵌套函数需要访问外部递归函数的变量时,会涉及闭包作用域的查找 ,比全局作用域或局部作用域的查找更耗时
- 例如,若
inner引用了recursive中的局部变量,Python 解释器需要在闭包环境中逐级查找,增加了每次调用的开销
对解释器优化的限制
- Python 解释器(如 CPython)对函数的优化(如局部变量缓存)在嵌套函数中可能效果较差
- 递归本身已经依赖解释器对函数调用栈的处理,嵌套函数可能进一步增加解释器的执行负担
建议:
- 将嵌套函数移到递归函数外部定义,避免重复初始化
- 若需要共享变量,可通过参数传递替代闭包引用,减少作用域查找开销
默认参数是在 函数定义时 立即绑定的
核心原则:Python 中函数的默认参数值,是在函数定义时(而非调用时)计算并绑定的
这里影响我们对函数的调用行为,需要特别关注
测试一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# 只要 function 定义就会立即调用 a(), 且后续无论如何调用 function(), a()仅会被调用一次
def a():
print("a")
def function(x = a()):
print(f'function+{x}')
pass
function()
function()
function()
# a
# function+None
# function+None
# function+None测试二:
1
2
3
4
5
6
7
8
9
10
11# 文件一内定义:
import os
def function(x = os.path.abspath(__file__)):
print(f'function+{x}')
pass
# 文件一内调用:
function() # 输出是文件一: function+/path_to_file1.py
# 文件二内调用:
function() # 输出还是文件一: function+/path_to_file1.py
如何打印对象名称?
在 Python 中,对象本身没有“名字”属性(变量名只是引用对象的“标签”)
- 一个对象可能被多个变量引用,也可能没有显式变量名(如匿名对象
func()的返回值) - 因此,我们无法直接获取“对象自己的名字”
- 一个对象可能被多个变量引用,也可能没有显式变量名(如匿名对象
可以通过 查找引用该对象的变量名 来间接实现打印对象名称,核心原理为:
- 通过
globals()(全局变量字典)、locals()(局部变量字典)或inspect模块遍历变量,反向查找“值等于目标对象”的变量名(即“找到引用该对象的标签”)
- 通过
方案示例:查找全局变量中的对象名(最常用)
- 如果对象是全局变量(模块级定义),可遍历
globals()字典,匹配值等于目标对象的变量名:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20def get_object_name(obj) -> list[str]:
"""
查找全局变量中引用该对象的所有名字(一个对象可能被多个变量引用)
:param obj: 目标对象
:return: 变量名列表(无匹配则返回空列表)
"""
return [name for name, value in globals().items() if value is obj]
# 测试示例
import torch
# 全局变量(对象)
a = [1, 2, 3]
b = a # 同一对象的另一个引用
tensor1 = torch.randn(2, 3)
# 打印对象的“名字”(变量名)
print(get_object_name(a)) # 输出:['a', 'b'](a和b都引用同一个列表对象)
print(get_object_name(tensor1)) # 输出:['tensor1']
print(get_object_name([4,5,6])) # 输出:[](匿名对象,无全局变量引用)
- 如果对象是全局变量(模块级定义),可遍历
方案示例:查找局部变量中的对象名(如函数内)
- 如果对象是函数内的局部变量,需结合
locals()(当前作用域局部变量)或inspect模块获取调用者的局部变量:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import inspect
def get_object_name(obj, include_global=True) -> list[str]:
"""
同时查找局部变量和全局变量中引用该对象的名字
:param obj: 目标对象
:param include_global: 是否包含全局变量
:return: 变量名列表
"""
names = []
# 1. 查找当前调用者的局部变量(函数内的变量)
caller_locals = inspect.currentframe().f_back.f_locals
names.extend([name for name, value in caller_locals.items() if value is obj])
# 2. 可选:包含全局变量
if include_global:
names.extend([name for name, value in globals().items() if value is obj])
return list(set(names)) # 去重(避免局部和全局变量名重复)
# 测试:函数内的局部对象
def test_local():
local_dict = {"key": "value"}
local_tensor = torch.tensor([1,2])
# 查找局部变量中的对象名
print(get_object_name(local_dict)) # 输出:['local_dict']
print(get_object_name(local_tensor)) # 输出:['local_tensor']
test_local()
- 如果对象是函数内的局部变量,需结合