Python中@classmethod和@staticmethod的区别
接上一篇介绍Python中@staticmethod和@classmethod的用法的文章。虽然@classmethod
和@staticmethod
非常相似,但两个修饰符的使用情况仍略有不同。
从它们的使用上来看:
@classmethod
必须引用一个类对象作为第一个参数,即第一个参数需要是表示自身类的cls参数。同时@classmethod
因持有cls参数,所以可以调用类的属性,类的方法,实例化对象等,避免硬编码。@staticmethod
则可以完全没有参数,但在@staticmethod
中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名()。
示例
1 | class Date(object): |
解释
让我们假设这样一个类的例子,用来处理日期信息(这将是我们的样板):
1 | class Date(object): |
显然,这个类可以用来存储关于某些日期的信息(没有时区信息;假设所有日期都以UTC表示)。
这个类中有__init__
,它是Python类实例的初始化方法,它接收参数作为类实例方法,具有第一个非可选参数self
(作为对新创建实例的引用)。
Class Method
我们有一些任务,通过使用@classmethod
可以很好地完成它们。
假设我们想要创建许多Date类实例,其日期信息来自外部输入(编码格式为’dd-mm-year’的字符串),并假设我们必须在项目源代码的不同位置执行此操作。
所以我们这里必须做到:
- 解析输入的字符串以接收日、月、年作为三个整数变量或由这些变量组成的三元组。
- 通过将上面求到的值传递给初始化调用来创建Date类实例。
代码看起来会是这样:
1 | day, month, year = map(int, string_date.split('-')) |
如果使用@classmethod
修饰符写在类中,将会是:
1 |
|
让我们更仔细地看看上面的代码实现,并回想一下我们做了什么?
我们在一个地方实现了日期字符串解析函数,现在它可以重用。
将日期字符串解析函数封装在类中并且工作正常(当然你可以在其他地方实现日期字符串解析作为单个函数,但这个解决方案更适合OOP范例)。
cls
是一个保存类本身的对象,而不是类的实例。这很酷😎,因为如果我们继承Date类,所有子类也会定义from_string()
。
Static Method
@staticmethod
确实与@classmethod
很相似,但@staticmethod
不需要任何强制性参数(如类方法或实例方法)。
让我们看看下一个任务(下一个用例):
假设我们有一个日期字符串,我们想要以某种方式进行验证它是否符合要求的格式。此任务也需要封装在Date类中,但不需要实例化它。
这里使用@staticmethod
就会很有效。让我们看一下代码:
1 |
|
运行上述代码得到is_date
是个boolen型变量,而非is_date_valid
函数返回的day,month,year三个整型数据。
因此,我们可以从@staticmethod
的使用中看到,我们无法访问类的内容——它基本上只是一个函数,在语法上称为方法,无法访问对象及其内部(字段和其他类方法)。而使用@classmethod
却可以做到。
补充
上面的文章已经很全面地总结了@classmethod
和@staticmethod
的区别。在这里我想强调当你创建构造函数时,你应该选择@classmethod
而不是@staticmethod
的另一个原因。在上面的例子中,使用@classmethod
from_string()
作为Factory,接收不符合__init__
要求的参数创建Date类实例。使用@staticmethod
可以完成同样的操作,如下面代码所示:
1 | class Date: |
运行结果显示new_year
和millenium_new_year
都是Date类实例。
但是,如果仔细观察就会发现,millenium_new_year
是以硬编码的方式创建的Date类实例。这意味着即使一个类继承Date类,该子类仍将创建普通的Date对象即父类对象,而不具有该子类本身的任何属性。请参阅以下示例代码:
1 | class DateTime(Date): |
DateTime类继承Date类,因此具有Date类的millenium()
方法。datetime2
通过调用DateTime继承来的millenium()
方法来创建DateTime类实例。然而代码却显示datetime2
并不是DateTime类实例(isinstance(datetime2, DateTime) # False
)。怎么回事?这是因为使用了@staticmethod
修饰符。
在大多数情况下,这是你不希望出现的。如果你想要的是一个”完整“的类实例,并且是通过调用它的父类方法所创建的话,那么@classmethod
就是你所需要的。
将Date.millenium()
重写为(这是上述代码中唯一改变的部分):
1 |
|
确保该类的创建不是通过硬编码。cls
可以是任何子类,生成的对象将正确地成为cls的实例。我们来试试吧:
1 | datetime1 = DateTime(10, 10, 1990) |
看吧,用@classmethod
替代@staticmethod
你不希望出现的情况就会消失。使用了@staticmethod
修饰符定义构造函数就是问题出现的关键。
文章的内容有点多,可能需要花一些时间进行理解,最后提供一个小示例帮助大家加深记忆一下@classmethod
和@staticmethod
的主要不同。
1 | class A(object): |
引用文章: