三分钟透彻理解Python中的@staticmethod和@classmethod
这篇博文主要讲解以下问题:
@classmethod和@staticmethod在Python中是什么意思,它们有何区别?我应该在何时使用它们,为什么要使用它们,以及如何使用它们?
@classmethod告诉一个类,这是一个应该被子类继承的方法,或者某种程度上是这样。然而,这样做的目的是什么呢?为什么不直接定义类方法,而不添加@classmethod、@staticmethod或任何@定义呢?
引入
虽然@classmethod和@staticmethod非常相似,但它们在用法上有轻微的区别:classmethod必须将一个类对象的引用作为第一个参数,而staticmethod可以不带任何参数。
Example:
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')
Explanation:
让我们假设一个处理日期信息的类的示例(这将是我们的模板):
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
)用于保存对新创建实例的引用。
Class Method
有些任务可以很好地使用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')
让我们仔细看一下上面的实现,并回顾一下这里的优点:
- 我们在一个地方实现了日期字符串的解析,并且现在可以重复使用它。
- 封装在这里运作良好(如果您认为您可以在其他地方将字符串解析实现为一个单独的函数,那么这个解决方案更符合面向对象编程范式)。
cls
代表的是类本身,而不是类的实例。这相当酷,因为如果我们继承了我们的Date
类,所有的子类也都会有from_string
方法。
Static Method
那么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却有这种权限。
如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。而@classmethod因为持有cls
参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。也就是说在classmethod中可以调用类中定义的其他方法、类的属性,但staticmethod只能通过A.a调用类的属性,但无法通过在该函数内部调用A.foo2()
。用代码加以说明:
class A(object):
a = 'a'
@staticmethod
def foo1(name):
print 'hello', name
print A.a # 正常
print A.foo2('mamq') # 报错: unbound method foo2() must be called with A instance as first argument (got str instance instead)
def foo2(self, name):
print 'hello', name
@classmethod
def foo3(cls, name):
print 'hello', name
print A.a
print cls().foo2(name)