Class (static) variables and methods

Class (static) variables and methods

技术背景

在Python编程中,类变量和静态方法是面向对象编程的重要概念。与其他编程语言(如C++、Java)不同,Python中的类变量和静态方法有其独特的实现方式和使用场景。理解这些概念对于编写高效、可维护的Python代码至关重要。

实现步骤

类变量的定义和使用

在Python中,在类定义内部但在方法外部声明的变量是类变量或静态变量。以下是一个简单的示例:

1
2
3
4
class MyClass:
i = 3

print(MyClass.i) # 输出: 3

可以通过类名直接访问类变量。但需要注意的是,类变量和实例变量是不同的:

1
2
3
m = MyClass()
m.i = 4
print(MyClass.i, m.i) # 输出: (3, 4)

这里,m.i = 4 创建了一个实例级别的 i 变量,而类级别的 i 变量值并未改变。

静态方法和类方法的定义和使用

Python提供了内置的装饰器来实现静态方法和类方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test:
# 普通实例方法
def my_method(self):
pass

# 类方法
@classmethod
def my_class_method(cls):
pass

# 静态方法
@staticmethod
def my_static_method():
pass
  • 普通实例方法的第一个参数 self 绑定到类的实例对象。
  • 类方法的第一个参数 cls 绑定到类对象本身。
  • 静态方法没有绑定的参数,参数是可选的。

模拟静态变量的行为

Python的类属性并非真正意义上的静态变量。可以通过将类属性转换为属性(property)来实现部分静态变量的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test:
_i = 3

@property
def i(self):
return type(self)._i

@i.setter
def i(self, val):
type(self)._i = val

x1 = Test()
x2 = Test()
x1.i = 50
print(x2.i) # 输出: 50

这样,静态变量在所有类实例之间保持同步。

不可变的“静态变量”

如果要实现不可变的“静态变量”,可以省略 propertysetter 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test:
_i = 3

@property
def i(self):
return type(self)._i

x = Test()
print(x.i) # 输出: 3
try:
x.i = 12
except AttributeError:
print("无法设置属性")

使用元类模拟其他语言的静态变量行为

可以使用元类来模拟其他语言的静态变量行为:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
from functools import wraps

class StaticVarsMeta(type):
statics = {}
def __new__(mcls, name, bases, namespace):
cls = super().__new__(mcls, name, bases, namespace)
cls.__sro__ = tuple(c for c in cls.__mro__ if isinstance(c, mcls))
cls.__getattribute__ = StaticVarsMeta.__inst_getattribute__(cls, cls.__getattribute__)
cls.__setattr__ = StaticVarsMeta.__inst_setattr__(cls, cls.__setattr__)
cls.__delattr__ = StaticVarsMeta.__inst_delattr__(cls, cls.__delattr__)
try:
mcls.statics[cls] = getattr(cls, '__statics__')
except AttributeError:
mcls.statics[cls] = namespace['__statics__'] = set()
if any(not isinstance(static, str) for static in mcls.statics[cls]):
typ = dict(zip((not isinstance(static, str) for static in mcls.statics[cls]), map(type, mcls.statics[cls])))[True].__name__
raise TypeError('__statics__ items must be strings, not {0}'.format(typ))
if len(cls.__sro__) > 1:
for attr, value in namespace.items():
if attr not in StaticVarsMeta.statics[cls] and attr != ['__statics__']:
for c in cls.__sro__[1:]:
if attr in StaticVarsMeta.statics[c]:
setattr(c, attr, value)
delattr(cls, attr)
return cls

def __inst_getattribute__(self, orig_getattribute):
@wraps(orig_getattribute)
def wrapper(self, attr):
if StaticVarsMeta.is_static(type(self), attr):
return StaticVarsMeta.__getstatic__(type(self), attr)
else:
return orig_getattribute(self, attr)
return wrapper

def __inst_setattr__(self, orig_setattribute):
@wraps(orig_setattribute)
def wrapper(self, attr, value):
if StaticVarsMeta.is_static(type(self), attr):
StaticVarsMeta.__setstatic__(type(self), attr, value)
else:
orig_setattribute(self, attr, value)
return wrapper

def __inst_delattr__(self, orig_delattribute):
@wraps(orig_delattribute)
def wrapper(self, attr):
if StaticVarsMeta.is_static(type(self), attr):
StaticVarsMeta.__delstatic__(type(self), attr)
else:
orig_delattribute(self, attr)
return wrapper

def __getstatic__(cls, attr):
for c in cls.__sro__:
if attr in StaticVarsMeta.statics[c]:
try:
return getattr(c, attr)
except AttributeError:
pass
raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))

def __setstatic__(cls, attr, value):
for c in cls.__sro__:
if attr in StaticVarsMeta.statics[c]:
setattr(c, attr, value)
break

def __delstatic__(cls, attr):
for c in cls.__sro__:
if attr in StaticVarsMeta.statics[c]:
try:
delattr(c, attr)
break
except AttributeError:
pass
raise AttributeError(cls.__name__ + " object has no attribute '{0}'".format(attr))

def __delattr__(cls, attr):
if attr == '__sro__':
raise AttributeError('readonly attribute')
super().__delattr__(attr)

def is_static(cls, attr):
if any(attr in StaticVarsMeta.statics[c] for c in cls.__sro__):
return True
return False

class MyBaseClass(metaclass=StaticVarsMeta):
__statics__ = {'a', 'b', 'c'}
i = 0
a = 1

class MyParentClass(MyBaseClass):
__statics__ = {'d', 'e', 'f'}
j = 2
d, e, f = 3, 4, 5
a, b, c = 6, 7, 8

class MyChildClass(MyParentClass):
__statics__ = {'a', 'b', 'c'}
j = 2
d, e, f = 9, 10, 11
a, b, c = 12, 13, 14

核心代码

以下是一个完整的示例,展示了类变量、静态方法和类方法的使用:

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
30
31
32
33
34
35
36
class Calculator:
num = 0
def __init__(self, digits) -> None:
Calculator.num = int(''.join(digits))

@staticmethod
def multiply(n1, n2, *args):
Res = 1
for num in args: Res *= num
return n1 * n2 * Res

def add(n1, n2, *args):
return n1 + n2 + sum(args)

@classmethod
def get_digits(cls, num):
digits = list(str(num))
calc = cls(digits)
return calc.num

def divide(cls, n1, n2, *args):
Res = 1
for num in args: Res *= num
return n1 / n2 / Res

def subtract(n1, n2, *args):
return n1 - n2 - sum(args)

Calculator.add = staticmethod(Calculator.add)
Calculator.divide = classmethod(Calculator.divide)

print(Calculator.multiply(1, 2, 3, 4)) # 输出: 24
print(Calculator.add(1, 2, 3, 4)) # 输出: 10
print(Calculator.get_digits(314159)) # 输出: 314159
print(Calculator.divide(15, 3, 5)) # 输出: 1.0
print(Calculator.subtract(10, 2, 3, 4)) # 输出: 1

最佳实践

  • 类变量:当多个实例需要共享同一个值时,使用类变量。但要注意,修改实例的类变量属性会创建一个新的实例变量,而不是修改类变量。
  • 静态方法:当方法不需要访问类或实例的状态时,使用静态方法。静态方法可以提高代码的可读性和可维护性。
  • 类方法:当方法需要访问类的状态(如类变量)时,使用类方法。类方法可以用于创建工厂方法等。

常见问题

类变量和实例变量混淆

在Python中,类变量和实例变量是不同的。直接通过实例修改类变量属性会创建一个新的实例变量,而不是修改类变量。例如:

1
2
3
4
5
6
7
class Test:
i = 3

t = Test()
t.i = 5
print(Test.i) # 输出: 3
print(t.i) # 输出: 5

要修改类变量,需要通过类名进行修改:

1
2
Test.i = 6
print(Test.i) # 输出: 6

静态方法和类方法的使用场景混淆

静态方法不需要访问类或实例的状态,而类方法需要访问类的状态。如果方法需要访问类变量,应该使用类方法;如果方法不需要访问类或实例的任何状态,应该使用静态方法。

元类的使用复杂

使用元类模拟其他语言的静态变量行为比较复杂,需要深入理解Python的元类机制。在实际开发中,除非确实需要,否则不建议使用元类来实现静态变量。


Class (static) variables and methods
https://119291.xyz/posts/class-static-variables-and-methods/
作者
ww
发布于
2025年5月13日
许可协议