类(静态)变量和方法

2545

我如何在Python中创建类(即static)变量或方法?


35
是的。缺少关键词“static”可能会产生误导,但是在类中初始化的任何对象(仅在类内部缩进一次,而不是在构造函数中)都是静态的。它不依赖于实例化(因为它不是构造函数的一部分)。至于方法,您可以使用@staticmethod修饰符来实现。 - user11991978
7
对于所有类的实例都存在的东西使用“静态”这个术语,对我来说总是感觉很奇怪。 - Tony Suffolk 66
12
我认为是因为C++将已有的关键字"static"从C中借用,它在C中表示变量的生命周期超出其声明时的作用域。C++将其扩展为指代一个变量其值在类的单个实例的"作用域"之外。Python(更合理地)将其称为类属性,因为它们是与类本身关联的属性,而不是类的一个实例。 - chepner
4
在 C++ 中,“static” 实际上有几个含义(由于评论长度非常严格,以下是缩写的定义)。有来自 C 的文件作用域 static,表示“此变量或函数仅可在此文件中使用”。还有类作用域 static,表示“此方法或字段与类型相关,而不是任何类型的实例”(在 C++ 中很少用,但在 C#、Java 和 ObjC 中很常见,例如,我认为这就是 OP 所问的内容)。还有函数中的局部变量 static,表示“此变量的值在函数调用之间保持不变”。 - jrh
4
转换到“个人观点”的角度,我认为在C#/Java中经常使用静态方法是因为这些语言采用了“禁止函数”的严格立场。在C#/Java中,只能有方法(即属于类的函数),而Python没有这个限制(在我看来,这是最好的)。个人来说,我宁愿使用C++的命名空间或从文件导入函数(Python),也不愿为不必要的原因创建一个类来保存函数。面向对象编程有其用途,但有时你只想要一个函数。 - jrh
显示剩余3条评论
27个回答

12

你也可以使用元类强制一个类为静态的。

class StaticClassError(Exception):
    pass


class StaticClass:
    __metaclass__ = abc.ABCMeta

    def __new__(cls, *args, **kw):
        raise StaticClassError("%s is a static class and cannot be initiated."
                                % cls)

class MyClass(StaticClass):
    a = 1
    b = 3

    @staticmethod
    def add(x, y):
        return x+y

如果你不小心尝试初始化 MyClass,你将会得到一个 StaticClassError。


4
如果你不打算实例化它,为什么还要将其定义为一个类呢?这感觉就像是在把 Python 扭曲成 Java... - Ned Batchelder
1
"Borg模式"是处理这种情况的更好方式。 - Rick
@NedBatchelder 这是一个抽象类,仅用于子类化(和实例化子类)。 - stevepastelan
1
我希望子类不要使用super()来调用其父类的__new__方法... - Ned Batchelder

11

有关Python属性查找的一个非常有趣的点是,它可以用于创建“虚拟变量”:

class A(object):

  label="Amazing"

  def __init__(self,d): 
      self.data=d

  def say(self): 
      print("%s %s!"%(self.label,self.data))

class B(A):
  label="Bold"  # overrides A.label

A(5).say()      # Amazing 5!
B(3).say()      # Bold 3!

通常创建这些对象后就不会有任何分配了。请注意,查找使用 self ,因为尽管 label 在静态意义上不与特定的实例相关联,但该值仍取决于(类的)实例。


11

使用对象数据类型是可能的,但对于像boolintfloatstr等原始类型,其行为与其他面向对象编程语言不同。因为在继承类中不存在静态属性。如果继承类中不存在属性,则Python会开始查找其父类。如果在父类中找到了该属性,则返回其值。当您决定在继承类中更改值时,静态属性将在运行时创建。下次读取继承静态属性时,它的值将被返回,因为它已经定义了。对象(列表、字典)作为引用工作,因此安全使用它们作为静态属性并继承它们。当您更改其属性值时,对象地址不会更改。

整数数据类型示例:

class A:
    static = 1


class B(A):
    pass


print(f"int {A.static}")  # get 1 correctly
print(f"int {B.static}")  # get 1 correctly

A.static = 5
print(f"int {A.static}")  # get 5 correctly
print(f"int {B.static}")  # get 5 correctly

B.static = 6
print(f"int {A.static}")  # expected 6, but get 5 incorrectly
print(f"int {B.static}")  # get 6 correctly

A.static = 7
print(f"int {A.static}")  # get 7 correctly
print(f"int {B.static}")  # get unchanged 6

使用refdatatypes库解决方案:

from refdatatypes.refint import RefInt


class AAA:
    static = RefInt(1)


class BBB(AAA):
    pass


print(f"refint {AAA.static.value}")  # get 1 correctly
print(f"refint {BBB.static.value}")  # get 1 correctly

AAA.static.value = 5
print(f"refint {AAA.static.value}")  # get 5 correctly
print(f"refint {BBB.static.value}")  # get 5 correctly

BBB.static.value = 6
print(f"refint {AAA.static.value}")  # get 6 correctly
print(f"refint {BBB.static.value}")  # get 6 correctly

AAA.static.value = 7
print(f"refint {AAA.static.value}")  # get 7 correctly
print(f"refint {BBB.static.value}")  # get 7 correctly

10

是的,在Python中可以编写静态变量和方法。

静态变量:在类级别声明的变量称为静态变量,可以使用类名直接访问。

    >>> class A:
        ...my_var = "shagun"

    >>> print(A.my_var)
        shagun

实例变量:与类的实例相关并由该实例访问的变量称为实例变量。

   >>> a = A()
   >>> a.my_var = "pruthi"
   >>> print(A.my_var,a.my_var)
       shagun pruthi

静态方法:和变量类似,可以使用类名直接访问静态方法,无需创建实例。

但需要注意的是,在Python中,静态方法不能调用非静态方法。

    >>> class A:
   ...     @staticmethod
   ...     def my_static_method():
   ...             print("Yippey!!")
   ... 
   >>> A.my_static_method()
   Yippey!!

你所谓的“静态”变量,我认为应该是类变量。例如:class A(): inner_var = 0class B(A): passA.inner_var = 15 B.inner_var = 30print ("A:static=" + str(A.inner_var)) print ("B:static=" + str(B.inner_var))

输出:

A:static=15

B:static=30

- Andrew

9

关于这个答案,对于一个常量静态变量,您可以使用描述符。下面是一个例子:

class ConstantAttribute(object):
    '''You can initialize my value but not change it.'''
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, type=None):
        return self.value

    def __set__(self, obj, val):
        pass


class Demo(object):
    x = ConstantAttribute(10)


class SubDemo(Demo):
    x = 10


demo = Demo()
subdemo = SubDemo()
# should not change
demo.x = 100
# should change
subdemo.x = 100
print "small demo", demo.x
print "small subdemo", subdemo.x
print "big demo", Demo.x
print "big subdemo", SubDemo.x

导致...的结果是...
small demo 10
small subdemo 100
big demo 10
big subdemo 10

如果静默忽略设置值(如上面的pass)不是你想要的,你总可以引发异常。如果你正在寻找类似于 C++ 或 Java 静态类变量的东西:

class StaticAttribute(object):
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, type=None):
        return self.value

    def __set__(self, obj, val):
        self.value = val

请查看这篇答案和官方文档HOWTO,了解有关描述符的更多信息。


2
你也可以使用@property,它与使用描述符相同,但代码量要少得多。 - Rick

9
当然可以,Python本身没有显式的静态数据成员,但我们可以通过以下方式实现:
class A:
    counter =0
    def callme (self):
        A.counter +=1
    def getcount (self):
        return self.counter  
>>> x=A()
>>> y=A()
>>> print(x.getcount())
>>> print(y.getcount())
>>> x.callme() 
>>> print(x.getcount())
>>> print(y.getcount())

输出

0
0
1
1

说明

here object (x) alone increment the counter variable
from 0 to 1 by not object y. But result it as "static counter"

6

总结了其他答案,并添加了一些内容,有很多方式可以在Python中声明静态方法或变量。

1. 使用staticmethod()作为装饰器:

可以简单地在声明的方法(函数)上方放置一个装饰器,将其变成静态方法。例如:

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

print(Calculator.multiply(1, 2, 3, 4))              # 24

2. 使用staticmethod()作为参数函数:

这个方法可以接收一个函数类型的参数,并返回传递进来的函数的静态版本。例如:

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

Calculator.add = staticmethod(Calculator.add)
print(Calculator.add(1, 2, 3, 4))                   # 10

3. 使用 @classmethod 作为装饰器:

@classmethod 对函数的影响与 @staticmethod 相似,但这次需要在函数中接受一个额外的参数(类似于实例变量的 self 参数)。例如:

class Calculator:
    num = 0
    def __init__(self, digits) -> None:
        Calculator.num = int(''.join(digits))

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

print(Calculator.get_digits(314159))                # 314159

4. 使用 classmethod() 作为参数函数:

@classmethod也可以作为参数函数使用,这样就不需要修改类定义。例如:

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

Calculator.divide = classmethod(Calculator.divide)

print(Calculator.divide(15, 3, 5))                  # 1.0

5. 直接声明

在一个类中,如果一个方法/变量是在所有其他方法之外声明的,则自动将其声明为static。

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

print(Calculator.subtract(10, 2, 3, 4))             # 1

整个程序

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文档以精通Python中的面向对象编程。


有点吓人的计算器... int(''.join(digits)) 会引发错误,因为 digits 被传递为整数列表,而 join 不会自动进行转换。请注意,get_digits 也是一个 setter... 尽管 Calculator 程序几乎在语法上保持一致,但它是一组糟糕的编程结构。 - cards

6
我发现最好的方法是使用另一个类。你可以创建一个对象,然后在其他对象上使用它。
class staticFlag:
    def __init__(self):
        self.__success = False
    def isSuccess(self):
        return self.__success
    def succeed(self):
        self.__success = True

class tryIt:
    def __init__(self, staticFlag):
        self.isSuccess = staticFlag.isSuccess
        self.succeed = staticFlag.succeed

tryArr = []
flag = staticFlag()
for i in range(10):
    tryArr.append(tryIt(flag))
    if i == 5:
        tryArr[i].succeed()
    print tryArr[i].isSuccess()

通过上面的示例,我创建了一个名为staticFlag的类。

这个类应该包含静态变量__success(私有静态变量)。

tryIt类代表我们需要使用的常规类。

现在,我为一个标志(staticFlag)创建了一个对象。这个标志将作为引用发送给所有常规对象。

所有这些对象都被添加到列表tryArr中。


此脚本结果:

False
False
False
False
False
True
True
True
True
True

5
为了避免任何潜在的混淆,我想对静态变量和不可变对象进行对比。
在 Python 中,一些原始对象类型如整数、浮点数、字符串和元组是不可变的。这意味着如果给定名称引用的对象属于上述对象类型之一,则该对象不会发生更改。名称可以重新分配到不同的对象,但对象本身可能不会被更改。
将变量设置为静态需要更进一步地禁止变量名指向除当前指向的对象之外的任何对象。(注意:这是一个通用的软件概念,而不是 Python 特有的;请参见其他帖子以获取有关在 Python 中实现静态的信息)。

4

Python 3.6中类工厂的静态变量

对于使用 Python 3.6 及以上版本的类工厂,请使用 nonlocal 关键字将其添加到正在创建的类的作用域/上下文中,如下所示:

>>> def SomeFactory(some_var=None):
...     class SomeClass(object):
...         nonlocal some_var
...         def print():
...             print(some_var)
...     return SomeClass
... 
>>> SomeFactory(some_var="hello world").print()
hello world

是的,但在这种情况下,hasattr(SomeClass,'x')False。我怀疑这根本不是任何人所说的静态变量。 - Rick
@RickTeachey 哈哈,看到你的静态变量代码,https://dev59.com/3nVD5IYBdhLWcg3wKYL-#27568860 加1个互联网先生,我还以为 hasattr 不是这样工作的?那么 some_var 是不可变的和静态定义的,还是不是?外部 getter 访问与变量是否静态有什么关系?现在我有很多问题。希望在你有时间的时候能听到一些答案。 - jmunsch
是的,那个元类相当荒谬。我不确定我理解这些问题,但在我看来,上面的 some_var 根本不是类成员。在 Python 中,所有的类成员都可以从类外部访问。 - Rick
nonlocal 关键字可以“提升”变量的作用域。类定义体的作用域是独立于其所处的作用域的 - 当你说 nonlocal some_var 时,它只是创建了一个非局部(即:不在类定义作用域中)的名称引用到另一个命名对象。因此它不会附加到类定义中,因为它不在类体作用域内。 - Rick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接