Python中并不像Java或者C++一样有原生支持的static关键字,那么我们要如何定义一个class的static variable或是static method呢?
我在Google上搜了一下,结合自己的一些理解,这里给出一些实现static关键字的方式,并且给出了一定的解释。下面的所有代码均在Python3.6中调试,2.7中的实现会略有不同。
0. Python的类变量
Python类变量的行为类似于Java中的static变量,但是还是有一些小的区别。
|
|
在上面的代码中,我们的Foo
类含有一个类变量count
和一个实例变量self.count
。类变量是可以直接通过类名.变量名
进行访问的,所有该类的实例对象共享这一个变量。
|
|
但是,如果对实例进行类变量的操作,那么会生成一个新的同名实例变量,覆盖掉该对象之前的类变量,导致与预期的static行为不符。
|
|
因此,使用Python类变量时要小心,如果想要保持static的行为,实例变量的明明应当与类变量相区分。
在这顺带也扯一些类内访问控制的东西。众所周知,如果想要一个实例变量或是类变量无法在类的外部被访问,我们可以在变量名前添加两条下划线,例如varName
就可以改成__varName
。
但是实际上,我们仍然可以从类的外部进行访问。。。
|
|
可以使用形如实例名._类名__变量名
的方式来访问受控制的类内变量。
(最好别这么做,这样做的行为是不可预知的,况且别人已经明确了不想给你看…)
1. 使用global关键字
Python中的global
关键字表示声明的变量是当前module的全局变量。
注意:global变量并不是当前Python进程(process)中的全局变量!
@property
装饰器可以将类中的方法包装成为类的属性,在类定义的对象中外部调用方法时,可以直接采用类似于获取对象的语句: obj.attr
|
|
上面的测试输出中可以看到,对于不同的实例对象,他们的count
属性(通过使用@property
装饰器包装count()
方法模拟)指向的是同一个类变量。
如果我们像像Java一样对类名直接调用方法,我们可以使用如下的方式:
|
|
2. 使用@classmethod装饰器
|
|
这种方法相比于方法1要更为简洁,而且相当于@classmethod
装饰器已经为我们完成了大部分的封装。我们可以直接通过类名来调用对应的方法。但是_count
变量在这里是可以被外部直接修改的,使用如Foo._count = 12
这样的语句即可。
在这里也可以使用@staticmethod
装饰器,它跟@classmethod
的区别可以参考这篇文章。
这种实现存在的问题是,_count
本质上仍然是类的成员变量,如果直接手动修改每个对象的_count
变量,那么_count
的行为与预期的static是不符的。在Java中,static变量是与类绑定的,修改任意一个实例的static变量,其变化都会反映在所有该类的对象上。
|
|
3. 设计一个callable的类
|
|
这种方法实质上是将类包装成了一个可调用的方法。也就是说,当我们调用默认的constructor(__init(self
)时,生成的其实是一个可调用的函数对象。而这个函数对象的行为则在__call(self,x)__
中定义了。每次我们调用这个constructor,返回的都是不同的函数对象。
因此,从原理上来看这种方法有着与前面方法2一样的问题。不同实例对象的类变量依然是独立的。
因此,这种方式仅适合实现static类方法。
4. 使用metaclass
|
|
这种方法是在网上看到的,大部分开发过程中并不会用到metaclass这样的特性,因此看看就好了。。。
5. 使用默认参数
|
|
这种方法是最tricky的。这种做法的原理在于:函数的默认参数只在module被载入(load)时初始化一次, 所以在一个module中,同一个函数的同一个默认参数都指向同一个内存中的对象。我们在实例代码中令默认参数_static_var
指向了一个list对象。在程序的生命周期中,该引用的指向没有变化,因此每次更新都会反映在整个类及所有该类的对象中。
EoF
其实看到这个问题,我的第一想法是用闭包(closure)来实现。但是在Google上看了一下似乎没人这么做。Python的设计哲学是能用一种方法解决的事,就不要反复造不同花式的轮子来做。但是static这个轮子,不知为什么语言的设计者没有考虑,我个人的猜想可能是与Python的解释运行模式有关?
References:
- https://www.cnblogs.com/2gua/archive/2012/09/03/2668125.html
- https://stackoverflow.com/questions/26630821/static-variable-in-python
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.