Python——编程规范

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
    9
    list1 = [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
    14
    def 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
    3
    def add(x, y):
    return x + y
    reduce(add, [1,2,3,4,5])
1
2
from operator import mul
reduce(mul, range(1, 10))

多使用pprint

  • (Pretty Print)而不是print
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import 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
    5
    try:
    read some thing
    except IOError, ValueError, e:
    exception_type = type(e)
    print("%s" % e)
  • 当然, 我们一般为了方便也会直接使用下面的方法

    1
    2
    3
    4
    5
    try:
    read some thing
    except Exception, e:
    exception_type = type(e)
    print("%s" % e)

位运算


函数

  • 内部函数访问全局变量时使用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
    10
    a = [[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
2
3
4
5
6
7
8
9
10
11
12
13
14
print 0 or 1
print 10 or 1
print None or 1
print 0 and 1
print 1 and 10
print None and 10

# # output:
# 1
# 10
# 1
# 0
# 10
# None
  • 理解

    1
    2
    3
    4
    5
    6
    7
    print 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
    5
    for i in reversed(range(n)):

    <==>

    for i in range(n-1, -1, -1):
  • reversed的使用似乎更优雅,也更容易理解

  • 容易遗忘的点,需要注意: reversed的参数必须是可迭代的对象,而不是两个数字


将简单的句子优雅的写到一行

  • 返回值

    1
    2
    3
    4
    if 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import collections

counter = collections.Counter([1, 2, 1, 3, 4, "a", "a", "c"])

print counter
print counter["a"]
print counter[1]
print counter[-1]
print counter.get("a")
print counter.get(-1, 0)

# # Output:
# Counter({'a': 2, 1: 2, 2: 1, 3: 1, 4: 1, 'c': 1})
# 2
# 2
# 0
# 2
# 0

括号用作分组表达式

  • 在 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 和 x
  • Python 中,for 和 while 语句不会产生新的定义域,在 for 和 while 语句内部定义的变量,可以直接在外面访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    for 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
    9
    for 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
    4
    a = [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
      7
      def 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
      20
      def 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
      29
      import 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()