@classmethod和@staticmethod在Python中是什么意思,它们有什么不同?我应该何时使用它们,为什么要使用它们,以及如何使用它们?

据我所知,@classmethod告诉一个类,它是一个应该继承到子类中的方法,或者。。。某物然而,这有什么意义?为什么不直接定义class方法而不添加@classmethod或@staticmethod或任何@定义?


当前回答

@classmethod的意思是:当调用此方法时,我们将类作为第一个参数传递,而不是该类的实例(我们通常使用方法)。这意味着您可以在该方法中使用类及其财产,而不是特定的实例。

@staticmethod意味着:当调用此方法时,我们不会将类的实例传递给它(就像我们通常使用方法一样)。这意味着您可以将函数放在类中,但不能访问该类的实例(当您的方法不使用实例时,这很有用)。

其他回答

@classmethod和@staticmethod的含义?

方法是对象名称空间中的函数,可作为属性访问。常规(即实例)方法获取实例(我们通常称其为self)作为隐式第一个参数。类方法获取类(我们通常称之为cls)作为隐式第一个参数。静态方法没有得到隐式的第一个参数(像正则函数)。

我应该何时使用它们,为什么要使用它们,以及如何使用它们?

你不需要任何一个装饰器。但是,基于应该最小化函数的参数数量的原则(请参见Clean Coder),它们对于实现这一点非常有用。

class Example(object):

    def regular_instance_method(self):
        """A function of an instance has access to every attribute of that 
        instance, including its class (and its attributes.)
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_f(self)

    @classmethod
    def a_class_method(cls):
        """A function of a class has access to every attribute of the class.
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_g(cls)

    @staticmethod
    def a_static_method():
        """A static method has no information about instances or classes
        unless explicitly given. It just lives in the class (and thus its 
        instances') namespace.
        """
        return some_function_h()

对于实例方法和类方法,不接受至少一个参数是TypeError,但不理解该参数的语义是用户错误。

(定义某些函数,例如:

some_function_h = some_function_g = some_function_f = lambda x=None: x

这将起作用。)

实例和类上的虚线查找:

实例上的虚线查找按以下顺序执行:

类名称空间中的数据描述符(如属性)实例__dict中的数据__类名称空间(方法)中的非数据描述符。

注意,实例上的虚线查找是这样调用的:

instance = Example()
instance.regular_instance_method 

方法是可调用的属性:

instance.regular_instance_method()

实例方法

参数self是通过虚线查找隐式给出的。

必须从类的实例访问实例方法。

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

类方法

参数cls是通过虚线查找隐式给出的。

您可以通过实例或类(或子类)访问此方法。

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

静态方法

未隐式给出任何参数。此方法的工作方式与(例如)在模块名称空间上定义的任何函数类似,但它可以被查找

>>> print(instance.a_static_method())
None

同样,我什么时候应该使用它们,为什么要使用它们?

与实例方法相比,这些方法中的每一个在传递方法的信息方面都越来越严格。

当你不需要这些信息时使用它们。

这使您的函数和方法更易于推理和单元测试。

哪个更容易推理?

def function(x, y, z): ...

or

def function(y, z): ...

or

def function(z): ...

参数较少的函数更容易推理。它们也更容易进行单元测试。

这些类似于实例、类和静态方法。记住,当我们有一个实例时,我们也有它的类,再次问问自己,哪个更容易推理?:

def an_instance_method(self, arg, kwarg=None):
    cls = type(self)             # Also has the class of instance!
    ...

@classmethod
def a_class_method(cls, arg, kwarg=None):
    ...

@staticmethod
def a_static_method(arg, kwarg=None):
    ...

内置示例

下面是几个我最喜欢的内置示例:

str.maketrans静态方法是字符串模块中的一个函数,但从str命名空间访问它要方便得多。

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

dict.fromkeys类方法返回一个从可迭代键实例化的新字典:

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

当进行子类化时,我们看到它以类方法的形式获取类信息,这非常有用:

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

我的建议-结论

当您不需要类或实例参数,但函数与对象的使用相关,并且函数位于对象的命名空间中时,可以使用静态方法。

当您不需要实例信息,但需要类信息(可能是其他类或静态方法的类信息,也可能是构造函数本身的类信息)时,请使用类方法。(您不会对类进行硬编码,以便在此处使用子类。)

何时使用每个

@staticmethod函数只不过是在类中定义的函数。它可以在不首先实例化类的情况下调用。它的定义通过继承是不可变的。

Python不必为对象实例化绑定方法。它简化了代码的可读性:看到@staticmethod,我们知道该方法不依赖于对象本身的状态;

@classmethod函数也可以在不实例化类的情况下调用,但它的定义遵循子类,而不是父类,通过继承,可以被子类重写。这是因为@classmethod函数的第一个参数必须始终是cls(class)。

工厂方法,用于使用例如某种预处理为类创建实例。静态方法调用静态方法:如果将静态方法拆分为多个静态方法,则不应硬编码类名,而应使用类方法

这里有一个很好的链接。

尽管classmethod和staticmethod非常相似,但这两个实体的用法略有不同:classmethod必须将对类对象的引用作为第一个参数,而staticmethod可以完全没有参数。

实例

class Date(object):
    
    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')

解释

让我们假设一个类的例子,处理日期信息(这将是我们的样板):

class Date(object):
    
    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

这个类显然可以用来存储某些日期的信息(没有时区信息;假设所有日期都以UTC表示)。

这里我们有__init__,这是Python类实例的典型初始化器,它作为一个典型的实例方法接收参数,具有第一个非可选参数(self),该参数保存对新创建实例的引用。

Class方法

我们有一些任务可以使用类方法很好地完成。

假设我们要创建许多Date类实例,这些实例的日期信息来自外部源,编码为“dd-mm-yyyy”格式的字符串。假设我们必须在项目源代码的不同位置执行此操作。

因此,我们在这里必须做的是:

分析一个字符串,以接收日、月和年作为三个整数变量或由该变量组成的三项元组。通过将这些值传递给初始化调用来实例化Date。

这将看起来像:

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

为此,C++可以通过重载实现这样的特性,但Python缺少这种重载。相反,我们可以使用classmethod。让我们创建另一个构造函数。

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

date2 = Date.from_string('11-09-2012')

让我们更仔细地看看上面的实现,并回顾一下我们在这里的优势:

我们在一个地方实现了日期字符串解析,现在可以重用了。封装在这里工作得很好(如果您认为可以在其他地方将字符串解析作为单个函数来实现,则此解决方案更适合OOP范式)。cls是类本身,而不是类的实例。这很酷,因为如果我们继承了Date类,所有的孩子都将定义from_string。

静态方法

静态方法呢?它与classmethod非常相似,但不接受任何强制参数(就像类方法或实例方法那样)。

让我们看看下一个用例。

我们有一个日期字符串,我们想以某种方式验证它。这个任务也在逻辑上绑定到我们目前使用的Date类,但不需要实例化它。

这里是静态方法可能有用的地方。让我们看下一段代码:

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

# usage:
is_date = Date.is_date_valid('11-09-2012')

因此,正如我们从staticmethod的用法中看到的那样,我们无法访问类是什么——它基本上只是一个函数,在语法上像方法一样被调用,但无法访问对象及其内部(字段和其他方法),而类方法确实具有这样的功能。

当他/她希望根据调用方法的子类来更改方法的行为时,可以使用@classmethod。请记住,我们在类方法中引用了调用类。

在使用静态时,您希望行为在子类之间保持不变

例子:

class Hero:

  @staticmethod
  def say_hello():
     print("Helllo...")

  @classmethod
  def say_class_hello(cls):
     if(cls.__name__=="HeroSon"):
        print("Hi Kido")
     elif(cls.__name__=="HeroDaughter"):
        print("Hi Princess")

class HeroSon(Hero):
  def say_son_hello(self):
     print("test  hello")



class HeroDaughter(Hero):
  def say_daughter_hello(self):
     print("test  hello daughter")


testson = HeroSon()

testson.say_class_hello() #Output: "Hi Kido"

testson.say_hello() #Outputs: "Helllo..."

testdaughter = HeroDaughter()

testdaughter.say_class_hello() #Outputs: "Hi Princess"

testdaughter.say_hello() #Outputs: "Helllo..."

类方法可以修改类状态,它绑定到类并且包含cls作为参数。

静态方法不能修改类状态,它绑定到类,它不知道类或实例

class empDetails:
    def __init__(self,name,sal):
        self.name=name
        self.sal=sal
    @classmethod
    def increment(cls,name,none):
        return cls('yarramsetti',6000 + 500)
    @staticmethod
    def salChecking(sal):
        return sal > 6000

emp1=empDetails('durga prasad',6000)
emp2=empDetails.increment('yarramsetti',100)
# output is 'durga prasad'
print emp1.name
# output put is 6000
print emp1.sal
# output is 6500,because it change the sal variable
print emp2.sal
# output is 'yarramsetti' it change the state of name variable
print emp2.name
# output is True, because ,it change the state of sal variable
print empDetails.salChecking(6500)