staticmethod使用时机 - python黑魔法之描述符




@classmethod和@staticmethod对初学者的意义? (8)

@classmethod@staticmethod含义?

  • 方法是对象名称空间中的一个函数,可以作为属性访问。
  • 常规(即实例)方法将实例(我们通常称它为self )作为隐含的第一个参数。
  • 方法将类(我们通常称之为cls )作为隐式的第一个参数。
  • 静态方法不会获得隐含的第一个参数(如常规函数)。

我应该何时使用它们,为什么要使用它们,我应该如何使用它们?

不需要任何装饰器。 但根据你应该尽量减少函数参数数量的原则(参见Clean Coder),它们对于做这件事很有用。

class Example(object):

    def regular_instance_method(self):
        """A function of an instance has access to every attribute of that 
        instance, including its class (and its attributes.)
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_f(self)

    @classmethod
    def a_class_method(cls):
        """A function of a class has access to every attribute of the class.
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_g(cls)

    @staticmethod
    def a_static_method():
        """A static method has no information about instances or classes
        unless explicitly given. It just lives in the class (and thus its 
        instances') namespace.
        """
        return some_function_h()

对于实例方法和类方法,不接受至少一个参数是TypeError,但不理解该参数的语义是用户错误。

(定义some_function的,例如:

some_function_h = some_function_g = some_function_f = lambda x=None: x

这将起作用。)

虚线查找实例和类:

按照此顺序对实例进行虚线查找 - 我们寻找:

  1. 类名称空间中的数据描述符(如属性)
  2. 实例中的数据__dict__
  3. 类名称空间(方法)中的非数据描述符。

请注意,实例上的虚线查找是这样调用的:

instance = Example()
instance.regular_instance_method 

和方法是可调用的属性:

instance.regular_instance_method()

实例方法

争论, self ,通过虚线查找隐含给出。

您必须从类的实例中访问实例方法。

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

类方法

参数cls隐含地通过虚线查找给出。

您可以通过实例或类(或子类)来访问此方法。

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

静态方法

没有任何参数是隐含给出的。 这个方法就像在模块名字空间中定义的任何函数一样,除了它可以被查找

>>> print(instance.a_static_method())
None

再次,我应该什么时候使用它们,我为什么要使用它们?

它们中的每一个在它们通过方法和实例方法的信息中越来越严格。

当你不需要这些信息时使用它们。

这使得你的函数和方法更容易推理和单元测试。

哪个更容易推理?

def function(x, y, z): ...

要么

def function(y, z): ...

要么

def function(z): ...

参数较少的函数更容易推理。 他们也更容易进行单元测试。

这些类似于实例,类和静态方法。 请记住,当我们有一个实例时,我们也有它的课程,再次问自己,这更容易推理?:

def an_instance_method(self, arg, kwarg=None):
    cls = type(self)             # Also has the class of instance!
    ...

@classmethod
def a_class_method(cls, arg, kwarg=None):
    ...

@staticmethod
def a_static_method(arg, kwarg=None):
    ...

内置的例子

以下是我最喜欢的一些内置示例:

str.maketrans静态方法是string模块中的一个函数,但它可以更方便地从str命名空间访问。

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

dict.fromkeys类方法返回一个从一个可迭代键实例化的新字典:

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

当子类化时,我们看到它将类信息作为类方法获得,这非常有用:

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

我的建议 - 结论

不需要类或实例参数时使用静态方法,但函数与对象的使用相关,并且函数在对象的名称空间中很方便。

当您不需要实例信息时使用类方法,但需要类信息可能用于其他类或静态方法,或者可能本身作为构造函数。 (你不会硬编码这个类,以便在这里使用子类。)

有人能向我解释@classmethod@staticmethod在python中的含义吗? 我需要知道其中的差异和意义。

据我所知, @classmethod告诉一个类,它是一种应该被继承到子类或者...的方法。 但是,这有什么意义呢? 为什么不定义类方法而不添加@classmethod@staticmethod或任何@定义?

tl; dr:我应该什么时候使用它们, 为什么要使用它们,我应该如何使用它们?

我用C ++很先进,所以使用更高级的编程概念应该不成问题。 如果可能,请随意给我一个相应的C ++示例。


Rostyslav Dzinko的回答非常合适。 我想我可以突出显示另一个原因,当你创建额外的构造函数时,你应该选择@classmethod over @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_yearnew_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

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


@staticmethod函数不过是一个在类中定义的函数。 它不需要首先实例化类就可以调用。 它的定义通过继承是不可变的。

  • Python不必为对象实例化绑定方法
  • 它简化了代码的可读性:看到@staticmethod ,我们知道该方法不依赖于对象本身的状态;

@classmethod函数也可以在没有实例化类的情况下调用,但其定义如下Sub类,而不是Parent类,通过继承,可以被子类重写。 这是因为@classmethod函数的第一个参数必须始终是cls (class)

  • 工厂方法 ,用于使用例如某种预处理为类创建实例。
  • 调用静态方法的静态方法 :如果在多个静态方法中拆分静态方法,则不应该对类名称进行硬编码,而应使用类方法

here是这个主题的好链接。


一点汇编

@staticmethod一种在类中编写方法而不参考它所调用的对象的方法。 所以不需要传递像self或cls这样的隐含参数。 它的写法与在类之外编写的完全相同,但它在python中没有用处,因为如果需要在类中封装方法,因为此方法需要成为该类的一部分@staticmethod便于使用案件。

@classmethod当你想写一个工厂方法并且通过这个自定义属性(s)可以附加到一个类中时,这很重要。 该属性可以在继承的类中重写。

这两种方法的比较如下


尽管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类实例的典型初始化方法,它接收参数作为典型的instancemethod ,具有第一个非可选参数( self ),该参数持有对新创建实例的引用。

类方法

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

我们假设我们想创建大量的Date类实例,它们将来自外部源的日期信息编码为下一个格式('dd-mm-yyyy')的字符串。 我们必须在项目中源代码的不同位置执行此操作。

所以我们在这里必须做的是:

  1. 将字符串解析为接收日,月和年作为三个整型变量或由该变量组成的三项目元组。
  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呢。


当他/她想要根据哪个子类调用方法来改变方法的行为时,可以使用@classmethod 。 请记住,我们在类方法中引用了调用类。

在使用静态时,您希望行为在子类中保持不变

例:

class Hero:

  @staticmethod
  def say_hello():
     print("Helllo...")

  @classmethod
  def say_class_hello(cls):
     if(cls.__name__=="HeroSon"):
        print("Hi Kido")
     elif(cls.__name__=="HeroDaughter"):
        print("Hi Princess")

class HeroSon(Hero):
  def say_son_hello(self):
     print("test  hello")



class HeroDaughter(Hero):
  def say_daughter_hello(self):
     print("test  hello daughter")


testson = HeroSon()

testson.say_class_hello() #Output: "Hi Kido"

testson.say_hello() #Outputs: "Helllo..."

testdaughter = HeroDaughter()

testdaughter.say_class_hello() #Outputs: "Hi Princess"

testdaughter.say_hello() #Outputs: "Helllo..."

我试着写一个简单的例子来展示使用staticmethod和classmethod(也不使用它们)之间的区别。 代码如下:

#without decorator:
class Duck(object):
    def sound(self):
        print("Quack")


#pay attention to the "sound" function:

#with @staticmethod
class Cat(object):
    @staticmethod
    def sound():
        print("Meow")

#with @classmethod
class Dog(object):
    @classmethod
    def sound(self):
        print("woof")


#notice the differences between calling them:
Duck().sound()
Cat.sound()
Dog.sound()

#prints:
"""
Quack
Meow
woof
"""

简而言之,@classmehtod将常规方法转换为工厂方法。

我们来看一个例子:

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'

没有@ class方法,你应该努力一个一个地创建实例,并且它们都是scartted。

book1 = PythonBook('Learning Python', 'Mark Lutz')
In [20]: book1
Out[20]: Book: Learning Python, Author: Mark Lutz
book2 = PythonBook('Python Think', 'Allen B Dowey')
In [22]: book2
Out[22]: Book: Python Think, Author: Allen B Dowey

以@classmethod为例

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'
    @classmethod
    def book1(cls):
        return cls('Learning Python', 'Mark Lutz')
    @classmethod
    def book2(cls):
        return cls('Python Think', 'Allen B Dowey')

测试它:

In [31]: PythonBook.book1()
Out[31]: Book: Learning Python, Author: Mark Lutz
In [32]: PythonBook.book2()
Out[32]: Book: Python Think, Author: Allen B Dowey

看到? 实例在类定义中成功创建,并且它们一起收集。

总之,@classmethod装饰器将常规方法转换为工厂方法,使用classmethods可以根据需要添加尽可能多的替代构造器。





class-method