Python-工具方法使用类静态方法还是使用函数、全局变量

前言

在Python中,写工具方法时可以选择使用类的静态方法或直接写函数。

每种方式都有其优缺点,选择哪种方式取决于具体的使用场景和个人或团队的编码风格。

函数方式如果有执行的方法,会在文件被引用的时候执行。就算不调用,只要引用就会执行。

所以建议函数方式的测试代码不要写在函数文件内。

静态方法/函数

静态方法

静态方法是类中的方法,不需要实例化类就可以调用。它们通常用于与类的逻辑相关但不依赖于实例状态的操作。

优点:

  • 逻辑组织:静态方法可以更好地组织代码,尤其是在方法与类逻辑相关时。
  • 命名空间管理:可以通过类名访问静态方法,有助于避免全局命名空间的污染。
  • 代码可读性:静态方法通常与类的其他方法和属性相关联,提高了代码的可读性和维护性。

缺点:

  • 类依赖:静态方法依赖于类的存在,即使它们不依赖于类的实例或类属性。

示例:

1
2
3
4
5
6
7
8
9
10
11
class MathUtils:
@staticmethod
def add(x, y):
return x + y

@staticmethod
def subtract(x, y):
return x - y

result = MathUtils.add(5, 3)
print(result) # 输出: 8

直接写函数

直接写函数是Python中非常常见的做法,尤其是在处理独立的工具方法时。

优点:

  • 简单直接:函数是Python的一等公民,使用起来非常简单和直接。
  • 无类依赖:不依赖于类的存在,适合处理独立的工具方法。
  • 灵活性:可以轻松地在不同的模块中导入和使用。

缺点:

  • 命名空间管理:需要在模块级别管理函数,可能导致全局命名空间的污染。
  • 代码组织:如果函数数量众多,可能会导致代码组织不够清晰。

示例:

1
2
3
4
5
6
7
8
def add(x, y):
return x + y

def subtract(x, y):
return x - y

result = add(5, 3)
print(result) # 输出: 8

选择指南

  • 逻辑相关性:如果方法与类的逻辑紧密相关,使用类的静态方法是一个好选择。例如,数学工具方法可以放在一个 MathUtils 类中。
  • 独立性:如果方法是完全独立的,不需要与类的逻辑关联,直接写函数更为合适。
  • 代码组织:如果你的工具方法数量众多,可以使用模块来组织这些函数,保持代码的整洁和可维护性。
  • 团队约定:如果你的团队有明确的编码风格和约定,遵循团队的约定也是一个重要的考虑因素。

函数中使用全局变量

要在函数内部修改全局变量,需要使用 global 关键字声明。

在函数内部,如果没有使用 global 关键字,Python会认为你在定义一个新的局部变量。

对于不可变类型的全局变量(如整数、字符串、元组),如果要在函数内部修改它们,必须使用 global 关键字。

对于可变类型的全局变量(如列表、字典、集合),可以在函数内部直接修改它们而无需使用 global 关键字。

1
2
3
4
5
6
7
8
# 加载模型
loaded_model = None


def load_model_init():
global loaded_model
if loaded_model is None:
loaded_model = models.load_model(model_filepath)

命名冲突

全局变量和局部变量的命名冲突可能会导致意想不到的行为。

在函数内部,如果没有使用 global 关键字,Python会认为你在定义一个新的局部变量。

1
2
3
4
5
6
7
8
global_var = 10  # 全局变量

def modify_global():
global_var = 20 # 这里没有使用 global 关键字,所以这是一个局部变量
print("Inside function:", global_var)

modify_global()
print("Outside function:", global_var) # 输出: Outside function: 10

不可变类型的全局变量

对于不可变类型的全局变量(如整数、字符串、元组),如果要在函数内部修改它们,必须使用 global 关键字。

1
2
3
4
5
6
7
8
global_var = 10  # 全局变量

def modify_global():
global global_var
global_var += 1

modify_global()
print(global_var) # 输出: 11

可变类型的全局变量

对于可变类型的全局变量(如列表、字典、集合),可以在函数内部直接修改它们而无需使用 global 关键字。

1
2
3
4
5
6
7
global_list = [1, 2, 3]  # 全局变量

def modify_global():
global_list.append(4) # 直接修改全局变量

modify_global()
print(global_list) # 输出: [1, 2, 3, 4]

类中的全局变量

在Python中,类中定义的变量通常被称为类属性(Class Attributes)或静态变量(Static Variables)。这些变量是属于类的,而不是类的实例。类属性可以在类的所有实例之间共享,并且可以通过类名或实例来访问。

1. 定义类属性

类属性在类定义的顶部直接定义,不在任何方法内部。

1
2
3
4
5
class MyClass:
class_var = 10 # 类属性

def __init__(self, instance_var):
self.instance_var = instance_var # 实例属性

2. 访问类属性

可以通过类名或实例来访问类属性。

1
2
3
4
print(MyClass.class_var)  # 通过类名访问,输出: 10

obj = MyClass(20)
print(obj.class_var) # 通过实例访问,输出: 10

3. 修改类属性

类属性可以在类的实例之间共享,因此修改类属性会影响所有实例。

1
2
3
4
5
6
7
8
9
10
print(MyClass.class_var)  # 输出: 10

obj = MyClass(20)
print(obj.class_var) # 输出: 10

MyClass.class_var = 30 # 修改类属性
print(obj.class_var) # 输出: 30

obj2 = MyClass(40)
print(obj2.class_var) # 输出: 30

4. 类属性和实例属性的区别

  • 类属性:属于类本身,所有实例共享同一个类属性。
  • 实例属性:属于类的实例,每个实例都有自己的实例属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyClass:
class_var = 10 # 类属性

def __init__(self, instance_var):
self.instance_var = instance_var # 实例属性

obj1 = MyClass(20)
obj2 = MyClass(30)

print(obj1.instance_var) # 输出: 20
print(obj2.instance_var) # 输出: 30

print(obj1.class_var) # 输出: 10
print(obj2.class_var) # 输出: 10

MyClass.class_var = 100
print(obj1.class_var) # 输出: 100
print(obj2.class_var) # 输出: 100

5. 注意事项

5.1. 实例属性覆盖类属性

如果在实例上定义了一个与类属性同名的实例属性,实例属性将覆盖类属性。

1
2
3
4
5
6
7
8
9
class MyClass:
class_var = 10 # 类属性

def __init__(self):
self.class_var = 20 # 实例属性,覆盖了类属性

obj = MyClass()
print(obj.class_var) # 输出: 20
print(MyClass.class_var) # 输出: 10

5.2. 类属性的修改

类属性的修改会影响所有实例,因此在修改类属性时要特别小心,避免意外的影响。

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass:
class_var = [] # 类属性

def __init__(self):
pass

obj1 = MyClass()
obj2 = MyClass()

obj1.class_var.append(1)
print(obj1.class_var) # 输出: [1]
print(obj2.class_var) # 输出: [1]

总结

类属性是Python类中定义的全局变量,它们属于类本身,而不是类的实例。

类属性可以在类的所有实例之间共享,并且可以通过类名或实例来访问。

在使用类属性时,需要注意实例属性可能会覆盖类属性,以及类属性的修改会影响所有实例。