python中@classmethod和@staticmethod的理解

https://eclipsesv.com/2017/08/03/关于python的@classmethod和@staticmethod的理解/

一直对python中@classmethod和@staticmethod的用法和区别不是很理解,一顿google之后发现还是stackoverflow真的是程序员的好朋友.在这个问题Meaning of @classmethod and @staticmethod for beginner?中,几个大神的回答还是很好的.

@Rostyslav Dzinko这样说 虽然classmethod和staticmethod十分相似,但他们之间确实存在一些细微的差别:classmethod必须有一个类对象作为方法的第一个参数,然而staticmethod却可以不需要参数. 下面👇,举个例子🌰:

样板代码 假设我们需要一个类来处理和日期相关的信息:

  
  
  
  1. class Date(object):

  2.    def __init__(self, day=0, month=0, year=0):

  3.        self.day = day

  4.        self.month = month

  5.        self.year = year

显然,这个类能够存储一些日期相关的信息,通过init方法,我们传入day,month和year能够实例化一个Date对象,其中init方法的第一个参数self就代表我们新建的Date实例.

Class Method 通过@classmethod我们可以比较优雅的完成一些任务.如果仅仅通过init方法来完成Date类的实例化,就必须这样实现:x = Date(day,month,year).如果现在想要将一个日期的字符串形式(‘dd-mm-yy’)转为Date对象,我们需要完成这两个步骤:

将字符串转为day,month,year三个整型对象或者一个包含三个值的元组; 通过init方法完成Date对象的实例化. 上边的两步实现过程就像这样:

  
  
  
  1. day, month, year = map(int, string_date.split('-'))

  2. date1 = Date(day, month, year)

在其他编程语言中,以c++为例,它可以重构自己的构造函数来接受某个日期的字符串形式最终返回一个Date实例.但是python没有这样的特性,于是classmethod就在这里派上了用场:

  
  
  
  1. @classmethod

  2.    def from_string(cls, date_as_string):

  3.        day, month, year = map(int, date_as_string.split('-'))

  4.        date1 = cls(day, month, year)

  5.        return date1

  6. date2 = Date.from_string('11-09-2012')

上边利用classmethod来完成将字符串转为Date实例主要有这些优势:

将字符串转化的过程放在类中,并且能够重用; 封装的较好,符合面向对象思想; classmethod中的cls代表Date,它不是类的一个实例,就是类对象本身,如果我们派生了其他的子类,它们也都能继承fromstring方法. Static Method 说完了classmethod,接着唠一唠staticmethod.它其实和classmethod十分相似,但是它不需要像类方法或者普通的实例方法一样需要必须的参数(cls或者self). 再举个例子🌰: 通过classmethod我们完成了将一个字符串转为Date实例的过程,现在给我们一个字符串,在使用Date.fromstring(‘str’)生成实例之前,判断这个str是否满足要求. 很显然,这个方法和类Date有密切的联系,但仅仅判断一个字符串是否满足转换的要求,并不需要实例化一个Date对象,这时候staticmethod就可以派上用场:

  
  
  
  1. @staticmethod

  2.    def is_date_valid(date_as_string):

  3.        day, month, year = map(int, date_as_string.split('-'))

  4.        return day <= 31 and month <= 12 and year <= 3999

  5. # usage:

  6. is_date = Date.is_date_valid('11-09-2012')

也就是说,staticmethod可以像一个普通的方法被调用,它与这个类有明确的相关性,但是不需要访问这个类内部的属性或者方法.

@Yaw Boakye补充说 Rostyslav Dzinko说的非常好,不过他(Yaw Boakye)从另外一个方面对classmethod和staticmethod的区别做了补充: 在上边的例子中,使用@classmethod from_string作为一个生成Date实例的工厂,然而通过@staticmethod也可以完成类似的操作:

  
  
  
  1. class Date:

  2.    def __init__(self, month, day, year):

  3.        self.month = month

  4.        self.day   = day

  5.        self.year  = year

  6.      def display(self):

  7.        return "{0}-{1}-{2}".format(self.month, self.day, self.year)

  8.      @staticmethod

  9.      def millenium(month, day):

  10.        return Date(month, day, 2000)

  11. new_year = Date(1, 1, 2013)               # Creates a new Date object

  12. millenium_new_year = Date.millenium(1, 1) # also creates a Date object.

  13. # Proof:

  14. new_year.display()           # "1-1-2013"

  15. millenium_new_year.display() # "1-1-2000"

  16. isinstance(new_year, Date) # True

  17. isinstance(millenium_new_year, Date) # True

通过@staticmethod millenium我们现在也可以生成类似于工厂函数@classmethod from_string的功能,生成Date实例.但是这样的实现却有点硬编码的嫌疑,因为@staticmethod millenium的最终返回结果是通过return Date(month, day, 2000)这句代码实现的,也就是说它明确了返回对象就是就是一个Date的实例. 下面我们派生一个Date的子类DateTime:

  
  
  
  1. class DateTime(Date):

  2.  def display(self):

  3.      return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)

  4. datetime1 = DateTime(10, 10, 1990)

  5. datetime2 = DateTime.millenium(10, 10)

  6. datetime1.display() # returns "10-10-1990 - 00:00:00PM"

  7. datetime2.display() # returns "10-10-2000"

我们可以看到datetime1.display()的返回值"10-10-1990 - 00:00:00PM"而datetime2.display()的返回值是returns "10-10-2000",这是怎么回事?原因在于datetime1和datetime2实例创建的方法是不同的:

datetime1通过DateTime的init方法生成 isinstance(datetime1, DateTime) # True datetime2通过DateTime.millenium方法生成 isinstance(datetime2, DateTime) # False 这里就可以看到@staticmethod和@classmethod的一些不同,将millenium使用@classmethod重写:

  
  
  
  1. @classmethod

  2. def millenium(cls, month, day):

  3.    return cls(month, day, 2000)

  4. 这里的cls替代了之前的Date,它可以是任何一个派生出来的子类,millenium返回的实例当然也是对应的cls的实例.

  5. datetime1 = DateTime(10, 10, 1990)

  6. datetime2 = DateTime.millenium(10, 10)

  7. isinstance(datetime1, DateTime) # True

  8. isinstance(datetime2, DateTime) # True

  9. datetime1.display() # "10-10-1990 - 00:00:00PM"

  10. datetime2.display() # "10-10-2000 - 00:00:00PM"

小小的总结一下 通过这两个很精彩的解释,现在对@classmethod和@staticmethod有了进一步的理解:

@classmethod,由于其强制要求有cls参数存在,可以更多的用于当作一个类实例工厂🏭,或者可以作为一个可以用于派生类中的构造函数; @staticmethod,如果一个方法不需要使用类内部的属性和方法,但确实和类有明确的相关性,它就可以使用@staticmethod来修饰.


本文分享自微信公众号 - 我爱问读书(wawds_)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章