有人可以向我解释@classmethod
和@staticmethod
在 python 中的含义吗?我需要知道差异和意义。
据我所知, @classmethod
告诉一个类,它是一个应该继承到子类的方法,或者...... 某种东西。但是,重点是什么?为什么不在不添加@classmethod
或@staticmethod
或任何@
definitions 的情况下定义类方法?
tl; dr:我应该何时使用它们, 为什么要使用它们,我应该如何使用它们?
我在 C ++ 方面非常先进,所以使用更高级的编程概念应该不是问题。如果可能的话,请随意给我一个相应的 C ++ 示例。
虽然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' 的字符串。假设我们必须在项目源代码的不同位置执行此操作。
所以我们必须做的是:
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')
让我们更仔细地看一下上面的实现,并回顾一下我们在这里有什么优势:
cls
是一个保存类本身的对象,而不是类的实例。这很酷,因为如果我们继承Date
类,所有子from_string
定义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 的回答非常合适。我想我可以突出另一个原因,当你创建额外的构造函数时,你应该选择@classmethod
不是@staticmethod
。
在上面的示例中,Rostyslav 使用@classmethod
from_string
作为 Factory 从其他不可接受的参数创建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
类是子类,子类仍将创建普通的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
@classmethod
意味着:当调用此方法时,我们将类作为第一个参数而不是该类的实例传递(正如我们通常使用的方法)。这意味着您可以在该方法中使用类及其属性,而不是特定实例。
@staticmethod
意味着:当调用此方法时,我们不会将类的实例传递给它(正如我们通常使用的方法)。这意味着您可以将一个函数放在一个类中,但是您无法访问该类的实例(当您的方法不使用该实例时,这很有用)。