@classmethod和@staticmethod对初学者的意义?

时间:2012-08-29 13:37:33

标签: python oop static-methods class-method

有人可以向我解释python中@classmethod@staticmethod的含义吗?我需要知道差异和意义。

据我所知,@classmethod告诉一个类,它是一个应该继承到子类的方法,或者......某种东西。但是,重点是什么?为什么不在不添加@classmethod@staticmethod或任何@定义的情况下定义类方法?

tl; dr: 我应该使用它们时,为什么我应该使用它们, 应该如何使用它们我用它们吗?

我使用C ++非常先进,所以使用更高级的编程概念应该不是问题。如果可能,请随意给我一个相应的C ++示例。

12 个答案:

答案 0 :(得分:2430)

虽然classmethodstaticmethod非常相似,但两个实体的使用情况略有不同:classmethod必须引用类对象作为第一个参数,而staticmethod 1}}根本没有参数。

实施例

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类实例的典型初始化器,它接收作为典型instancemethod的参数,具有保存引用的第一个非可选参数(self)到新创建的实例。

课程方法

我们可以使用classmethod来完成一些任务。

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

所以我们必须做的是:

  1. 解析字符串以接收日,月和年作为三个整数变量或由该变量组成的3项元组。
  2. 通过将这些值传递给初始化调用来实例化Date
  3. 这看起来像是:

    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')
    

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

    1. 我们在一个地方实现了日期字符串解析,现在可以重复使用。
    2. 封装在这里工作正常(如果你认为你可以在其他地方实现字符串解析作为单个函数,这个解决方案更适合OOP范例)。
    3. cls是一个包含类本身的对象,而不是该类的实例。这很酷,因为如果我们继承我们的Date课程,所有孩子也会定义from_string
    4. 静态方法

      staticmethod怎么样?它与classmethod非常相似,但不采用任何强制性参数(如类方法或实例方法)。

      让我们看一下下一个用例。

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

      这里staticmethod可能有用。让我们看看下一段代码:

          @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则。

答案 1 :(得分:797)

Rostyslav Dzinko的回答非常合适。我想我可以强调在创建其他构造函数时应该选择@classmethod而不是@staticmethod的另一个原因。

在上面的示例中,Rostyslav使用@classmethod from_string作为工厂从其他不可接受的参数创建Date个对象。使用@staticmethod可以完成同样的操作,如下面的代码所示:

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


  def display(self):
    return "{0}-{1}-{2}".format(self.month, self.day, self.year)


  @staticmethod
  def millenium(month, day):
    return Date(month, day, 2000)

new_year = Date(1, 1, 2013)               # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object. 

# Proof:
new_year.display()           # "1-1-2013"
millenium_new_year.display() # "1-1-2000"

isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True

因此,new_yearmillenium_new_year都是Date类的实例。

但是,如果仔细观察,工厂流程将被硬编码以创建Date个对象,无论如何。这意味着即使Date类是子类,子类仍将创建普通的Date对象(没有子类的任何属性)。请参阅以下示例中的内容:

class DateTime(Date):
  def display(self):
      return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)


datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False

datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class

datetime2不是DateTime的实例? WTF?那是因为使用了@staticmethod装饰器。

在大多数情况下,这是不受欢迎的。如果您想要的是一个知道调用它的类的Factory方法,那么@classmethod就是您所需要的。

Date.millenium重写为(这是上述代码中唯一改变的部分)

@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

确保class不是硬编码的,而是学习的。 cls可以是任何子类。生成的object将正确地成为cls的实例。让我们测试一下。

datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True


datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"

原因是,正如您现在所知,@classmethod代替了@staticmethod

答案 2 :(得分:229)

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

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

答案 3 :(得分:69)

何时使用

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

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

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

  • 工厂方法,用于为类创建实例,例如使用某种预处理。
  • 调用静态方法的静态方法:如果在多个静态方法中拆分静态方法,则不应该对类名进行硬编码,而是使用类方法

here是此主题的良好链接。

答案 4 :(得分:36)

  

@classmethod@staticmethod的含义?

  • 方法是对象命名空间中的一个函数,可以作为属性访问。
  • 常规(即实例)方法获取实例(我们通常将其称为self)作为隐式的第一个参数。
  • class 方法将类(我们通常称之为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,但不理解该参数的语义是用户错误。

(定义{{​​1}}'s,例如:

some_function

这将有效。)

实例和类的点缀查找:

按顺序执行实例的虚线查找 - 我们查找:

  1. 类名称空间中的数据描述符(如属性)
  2. 实例some_function_h = some_function_g = some_function_f = lambda x=None: x
  3. 中的数据
  4. 类名称空间(方法)中的非数据描述符。
  5. 注意,对实例的虚线查找调用如下:

    __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): ...
    

    def function(y, z): ...
    

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

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

    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模块中的一个函数,但从string命名空间访问它会更方便。

    str

    >>> 'abc'.translate(str.maketrans({'a': 'b'})) 'bbc' 类方法返回从可迭代键实例化的新字典:

    dict.fromkeys

    当子类化时,我们看到它将类信息作为类方法获取,这非常有用:

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

    我的建议 - 结论

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

    当您不需要实例信息时使用类方法,但是可能需要类信息用于其他类或静态方法,或者可能本身作为构造函数。 (你不会对类进行硬编码,以便可以在这里使用子类。)

答案 5 :(得分:34)

当他/她想要根据调用方法的子类更改方法的行为时,可以使用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..." 。记得我们在类方法中引用了调用类。

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

示例:

{{1}}

答案 6 :(得分:33)

一点汇编

<强> @staticmethod 一种在类中编写方法而不引用它所调用的对象的方法。所以不需要传递像self或cls这样的隐式参数。 它的写法与在类外写的完全相同,但它在python中没有用,因为如果你需要在类中封装一个方法,因为这个方法需要成为该类的一部分@staticmethod就派上用了情况下。

<强> @classmethod 当您想要编写工厂方法并且此自定义属性可以附加到类中时,这很重要。可以在继承的类中覆盖此属性。

这两种方法的比较如下:

Table

答案 7 :(得分:1)

我是这个网站的初学者,我已经阅读了上述所有答案,并获得了我想要的信息。但是,我没有权利进行投票。所以我希望在StackOverflow上开始我的理解答案。

  • @staticmethod不需要self或cls作为方法的第一个参数
  • @staticmethod@classmethod包装函数可以通过实例或类变量
  • 调用
  • @staticmethod修饰函数会影响某种“不可变属性”,即子类继承不能覆盖由@staticmethod装饰器包装的基类函数。
  • @classmethod需要cls(类名,如果需要,可以更改变量名,但不建议)作为函数的第一个参数
  • @classmethod总是以子类方式使用,子类继承可能会改变基类函数的效果,即@classmethod包装的基类函数可能会被不同的子类覆盖。

答案 8 :(得分:1)

简而言之,@ classmehtod将普通方法转换为工厂方法。

让我们用一个例子探讨它:

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'

如果没有@ classmethod,你应该逐个创建实例,并且它们是scartted。

book1 = PythonBook('Learning Python', 'Mark Lutz')
In [20]: book1
Out[20]: Book: Learning Python, Author: Mark Lutz
book2 = PythonBook('Python Think', 'Allen B Dowey')
In [22]: book2
Out[22]: Book: Python Think, Author: Allen B Dowey

例如@classmethod

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'
    @classmethod
    def book1(cls):
        return cls('Learning Python', 'Mark Lutz')
    @classmethod
    def book2(cls):
        return cls('Python Think', 'Allen B Dowey')

测试它:

In [31]: PythonBook.book1()
Out[31]: Book: Learning Python, Author: Mark Lutz
In [32]: PythonBook.book2()
Out[32]: Book: Python Think, Author: Allen B Dowey

请参阅?实例在类定义中成功创建,并一起收集。

总之,@ classmethod装饰器将传统方法转换为工厂方法,使用classmethods可以根据需要添加任意数量的替代构造函数。

答案 9 :(得分:-1)

一种稍微不同的思考方式可能对某些人有用...在超类中使用类方法来定义该方法在被不同的子类调用时应该如何表现。当我们想要返回相同的东西时使用静态方法,而不管我们正在调用的子类。

答案 10 :(得分:-2)

类方法可以修改类状态,它绑定到类,它包含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)

答案 11 :(得分:-2)

@classmethod

@classmethod可以与__init__进行比较。 您可能会认为这是另一个__init__()。这是python在c ++中实现类构造函数重载的方式。

class C:
    def __init__(self, parameters):
        ....

    @classmethod
    def construct_from_func(cls, parameters):
        ....

obj1 = C(parameters)
obj2 = C.construct_from_func(parameters)

请注意,在__init__使用self但传统上construct_from_func使用cls的情况下,它们在definitioin中都将类的引用作为第一个参数。

@staticmethod

@staticmethod可与object method

进行比较
class C:
    def __init__(self):
        ....

    @staticmethod
    def static_method(args):
        ....

    def normal_method(parameters):
        ....

result = C.static_method(parameters)
result = obj.normal_method(parameters)