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


有人可以向我解释一下这个意思吗? @classmethod 和 @staticmethod 在python?我需要知道差异和意义。

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

TL;博士:  什么时候 我应该用它们吗 为什么 我应该使用它们吗? 怎么样 我应该用它们吗?

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


1258
2017-08-29 13:37


起源




答案:


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

类方法

我们有一些任务可以很好地使用 classmethod秒。

我们假设我们想创造很多 Date 具有来自外部源的日期信息的类实例被编码为下一格式的字符串('dd-mm-yyyy')。我们必须在项目的源代码的不同位置执行此操作。

所以我们必须做的是:

  1. 解析字符串以接收日,月和年作为三个整数变量或由该变量组成的3项元组。
  2. 实例 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')

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

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

静态方法

关于什么 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。


2163
2017-08-29 14:03



@hayden,关于自我。 instancemethod(不是静态方法类)必须有1个必需参数(由实例替换)。虽然“self”是按照惯例的参数名称,但您可以自由使用任何您喜欢的有效Python标识符(但不建议这样做,因为它可能会导致其他开发人员阅读您的代码时出现混淆)。 - Rostyslav Dzinko
是否应该命名初始变量 cls 要么 Cls?我觉得分歧 - 一方面,这是一个争论。另一方面,您正在使用它来构造一个对象。我感到 Cls(day, month, year) 更清楚地表明指定类型的对象 Cls 正在创建并返回,但是 cls(day, month, year),似乎只是一个普通的函数调用。 - ArtOfWarfare
@RostyslavDzinko如果省略了将会发生什么 @classmethod 关键词。例如 __init__ 永远都是 self 作为第一个参数,它不需要python糖 @classmethod - Alexander Cska
@AlexanderCska会发生什么,而不是调用date2 = Date.from_string(string_date),你需要说Date.from_string(Date,string_date)。因为你需要给出类对象,这有点不方便。 - zwep
如果没有别的,我现在知道为什么它被称为锅炉板! - chris dorn


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_year 和 millenium_new_year 是的例子 Date 类。

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

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 as(这是上面代码中唯一改变的部分)

@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


718
2018-01-30 13:35



+1为整洁的例子: isinstance(datetime2, DateTime) # False。这是对使用的强烈支持 @classmethod 拧工厂的方法。 - Viet


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

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


196
2017-08-29 13:40





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

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

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

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

这里 是这个主题的良好链接。


53
2018-04-14 07:12





一个人会用 @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..."

27
2017-08-21 07:18





一点汇编

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

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

这两种方法之间的比较如下

Table


26
2017-07-19 16:43





的意思 @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的,例如:

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

这将有效。)

实例和类的虚线查找:

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

  1. 类命名空间中的数据描述符(如属性)
  2. 实例中的数据 __dict__
  3. 类命名空间(方法)中的非数据描述符。

注意,对实例的虚线查找调用如下:

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 class方法返回从可迭代的键实例化的新字典:

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

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

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

我的建议 - 结论

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

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


18
2017-10-24 16:09



只要在简洁的解释和微观例子之间取得恰当的平衡,这就非常清楚了。 - abalter


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

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

1
2017-07-23 11:38



在这种情况下,我不确定你的意思是什么? - Philip Adler