对于初学者来说 @classmethod 和 @staticmethod 的含义 [重复]

2024-11-29 08:41:00
admin
原创
184
摘要:问题描述:@classmethodPython 中的和 是什么@staticmethod意思?它们有什么不同?什么时候应该使用它们?为什么要使用它们?如何使用它们?据我所知,@classmethod告诉类,这是一个应该被子类继承的方法,或者……其他的。但是,这样做有什么意义呢?为什么不直接定义类方法而不添加@...

问题描述:

@classmethodPython 中的和 是什么@staticmethod意思?它们有什么不同?什么时候应该使用它们?为什么要使用它们?如何使用它们?

据我所知,@classmethod告诉类,这是一个应该被子类继承的方法,或者……其他的。但是,这样做有什么意义呢?为什么不直接定义类方法而不添加@classmethod@staticmethod任何@定义呢?


解决方案 1:

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

类方法

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

假设我们想要创建大量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')

让我们更仔细地看一下上述实现,并回顾一下我们这里有哪些优势:

  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

解决方案 2:

Rostyslav Dzinko 的回答非常恰当。我想我可以强调一下在创建附加构造函数时应该选择@classmethod的另一个原因。@staticmethod

在示例中,Rostyslav 使用@classmethod from_string作为工厂来从其他不可接受的参数创建对象。 可以使用以下代码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_yearmillenium_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 for more details.

datetime2不是DateTime? WTF? 的一个实例,这是因为@staticmethod使用了装饰器。

在大多数情况下,这是不希望看到的。如果您想要的是工厂方法,它知道调用它的类,那么这@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

解决方案 3:

@classmethod意思是:当调用此方法时,我们将类作为第一个参数传递,而不是该类的实例(就像我们通常对方法所做的那样)。这意味着您可以在该方法中使用类及其属性,而不是特定实例。

@staticmethod意思是:当调用此方法时,我们不会将类的实例传递给它(就像我们通常对方法所做的那样)。这意味着您可以将函数放在类中,但无法访问该类的实例(当您的方法不使用该实例时,这很有用)。

解决方案 4:

何时使用

@staticmethod函数只不过是类中定义的函数。无需先实例化类即可调用它。它的定义通过继承是不可变的。

  • Python 不必为对象实例化绑定方法

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

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

  • 工厂方法,用于通过某种预处理等方式为类创建实例。

  • 静态方法调用静态方法:如果将静态方法拆分为多个静态方法,则不应硬编码类名,而应使用类方法

这是该主题的良好链接。

解决方案 5:

@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'> 

我的建议 - 结论

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

当您不需要实例信息,但需要类信息(可能用于其他类或静态方法,或者可能将其本身用作构造函数)时,请使用类方法。(您不会对类进行硬编码,以便可以在此处使用子类。)

解决方案 6:

@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..."

解决方案 7:

一些汇编

@staticmethod
一种在类内部编写方法而不引用被调用对象的方法。因此无需传递隐式参数(如 self 或 cls)。它的编写方式与类外部的编写方式完全相同,但在 Python 中并非毫无用处,因为如果您需要将方法封装在类中,而该方法需要成为该类的一部分,那么 @staticmethod 在这种情况下就派上用场了。

@classmethod
当您想编写工厂方法并通过此方法将自定义属性附加到类中时,这一点很重要。此属性可在继承的类中被覆盖。

这两种方法的比较如下

桌子

解决方案 8:

@classmethod

@classmethod可以与进行比较__init__。您可以认为它是另一个__init__()。这是python在c ++中实现类构造函数重载的方式。

class C:
    def __init__(self, parameters):
        ....

    @classmethod
    def construct_from_func(cls, parameters):
        ....

obj1 = C(parameters)
obj2 = C.construct_from_func(parameters)

注意,它们在定义中都有一个对类的引用作为第一个参数,但__init__使用时是按照惯例使用的。self`construct_from_func`cls

@staticmethod

@staticmethod可以与object method

class C:
    def __init__(self):
        ....

    @staticmethod
    def static_method(args):
        ....

    def normal_method(parameters):
        ....

result = C.static_method(parameters)
result = obj.normal_method(parameters)

解决方案 9:

简而言之,@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,你就必须费力地逐个创建实例,而且它们会很分散。

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 可以根据需要添加尽可能多的替代构造函数。

解决方案 10:

我是这个网站的新手,我已阅读上述所有答案,并获得了我想要的信息。但是,我没有点赞的权利。所以我想在 StackOverflow 上以我理解的答案开始。

  • @staticmethod不需要 self 或 cls 作为方法的第一个参数

  • @staticmethod包装@classmethod函数可以通过实例或类变量调用

  • @staticmethod装饰函数影响某种“不可变属性”,子类继承不能覆盖由@staticmethod装饰器包装的基类函数。

  • @classmethod需要 cls(类名,你可以根据需要更改变量名,但不建议这样做)作为函数的第一个参数

  • @classmethod总是以子类的方式使用,子类继承可能会改变基类函数的效果,即@classmethod包装的基类函数可能会被不同的子类覆盖。

解决方案 11:

稍微不同的思考方式可能对某些人有用... 类方法在超类中用于定义该方法在被不同子类调用时的行为方式。当我们想要返回相同内容而不管调用的是哪个子类时,就会使用静态方法。

解决方案 12:

类方法可以修改类状态,它绑定到类并且包含 cls 作为参数。

静态方法不能修改类状态,它绑定到类并且不知道类或实例

class empDetails:
    def __init__(self,name,sal):
        self.name=name
        self.sal=sal
    @classmethod
    def increment(cls,name,none):
        return cls('yarramsetti',6000 + 500)
    @staticmethod
    def salChecking(sal):
        return sal > 6000

emp1=empDetails('durga prasad',6000)
emp2=empDetails.increment('yarramsetti',100)
# output is 'durga prasad'
print emp1.name
# output put is 6000
print emp1.sal
# output is 6500,because it change the sal variable
print emp2.sal
# output is 'yarramsetti' it change the state of name variable
print emp2.name
# output is True, because ,it change the state of sal variable
print empDetails.salChecking(6500)
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1267  
  IPD(Integrated Product Development)即集成产品开发,是一套先进的、成熟的产品开发管理理念、模式和方法。随着市场竞争的日益激烈,企业对于提升产品开发效率、降低成本、提高产品质量的需求愈发迫切,IPD 项目管理咨询市场也迎来了广阔的发展空间。深入探讨 IPD 项目管理咨询的市场需求与发展,...
IPD集成产品开发流程   27  
  IPD(Integrated Product Development)产品开发流程是一套先进的、被广泛应用的产品开发管理体系,它涵盖了从产品概念产生到产品推向市场并持续优化的全过程。通过将市场、研发、生产、销售等多个环节紧密整合,IPD旨在提高产品开发的效率、质量,降低成本,增强企业的市场竞争力。深入了解IPD产品开发...
IPD流程中TR   31  
  IPD(Integrated Product Development)测试流程是确保产品质量、提升研发效率的关键环节。它贯穿于产品从概念到上市的整个生命周期,对企业的成功至关重要。深入理解IPD测试流程的核心要点,有助于企业优化研发过程,打造更具竞争力的产品。以下将详细阐述IPD测试流程的三大核心要点。测试策略规划测试...
华为IPD   26  
  华为作为全球知名的科技企业,其成功背后的管理体系备受关注。IPD(集成产品开发)流程作为华为核心的产品开发管理模式,在创新管理与技术突破方面发挥了至关重要的作用。深入剖析华为 IPD 流程中的创新管理与技术突破,对于众多企业探索自身发展路径具有重要的借鉴意义。IPD 流程概述IPD 流程是一种先进的产品开发管理理念和方...
TR评审   26  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用