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

新手上路,请多包涵

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

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

原文由 user1632861 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 454
2 个回答

虽然 classmethodstaticmethod 非常相似,但两个实体的用法略有不同: 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 ),它包含对 a 的引用新创建的实例。

类方法

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

假设我们要创建大量 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 确实有。

原文由 Rostyslav Dzinko 发布,翻译遵循 CC BY-SA 4.0 许可协议

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 类的实例。

但是,如果您仔细观察,无论如何,Factory 进程都被硬编码为创建 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 for more details.

datetime2 不是 DateTime 的实例?什么鬼?好吧,那是因为使用了 @staticmethod 装饰器。

在大多数情况下,这是不希望的。如果您想要的是一个知道调用它的类的工厂方法,那么 @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

原文由 Yaw Boakye 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏