有人可以向我解释python中@classmethod
和的含义吗?@staticmethod
我需要知道区别和含义。
据我了解,@classmethod
告诉一个类它是一个应该被继承到子类中的方法,或者......什么。然而,这有什么意义呢?为什么不直接定义类方法而不添加@classmethod
or@staticmethod
或任何@
定义?
tl;dr:我应该什么 时候使用它们,为什么要使用它们,我应该如何使用它们?
有人可以向我解释python中@classmethod
和的含义吗?@staticmethod
我需要知道区别和含义。
据我了解,@classmethod
告诉一个类它是一个应该被继承到子类中的方法,或者......什么。然而,这有什么意义呢?为什么不直接定义类方法而不添加@classmethod
or@staticmethod
或任何@
定义?
tl;dr:我应该什么 时候使用它们,为什么要使用它们,我应该如何使用它们?
虽然classmethod
和staticmethod
非常相似,但两个实体的用法略有不同: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
s 很好地完成。
假设我们要创建许多Date
类实例,这些实例的日期信息来自外部源,编码为格式为“dd-mm-yyyy”的字符串。假设我们必须在项目源代码的不同位置执行此操作。
所以我们必须在这里做的是:
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
定义。静态方法
怎么样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 可以。
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_year
都是类millenium_new_year
的实例Date
。
但是,如果您仔细观察,Date
无论如何,Factory 过程都经过硬编码来创建对象。这意味着即使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
?怎么回事?嗯,那是因为使用了@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
@classmethod
意思是:当这个方法被调用时,我们将类作为第一个参数而不是该类的实例传递(就像我们通常对方法所做的那样)。这意味着您可以在该方法而不是特定实例中使用类及其属性。
@staticmethod
意思是:当这个方法被调用时,我们不会将类的实例传递给它(就像我们通常对方法所做的那样)。这意味着您可以将函数放在类中,但不能访问该类的实例(当您的方法不使用实例时,这很有用)。
@staticmethod
函数只不过是在类中定义的函数。它可以在不先实例化类的情况下调用。它的定义通过继承是不可变的。
@classmethod
函数也可以在不实例化类的情况下调用,但它的定义遵循子类,而不是父类,通过继承,可以被子类覆盖。那是因为@classmethod
函数的第一个参数必须始终是cls (class)
.
这是该主题的良好链接。
@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
这将起作用。)
实例上的虚线查找按此顺序执行 - 我们查找:
__dict__
请注意,实例上的虚线查找是这样调用的:
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
当他/她想要根据哪个子类调用该方法来更改该方法的行为时,将使用该方法。请记住,我们在类方法中有对调用类的引用。
使用静态时,您希望行为在子类之间保持不变
例子:
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..."
@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
可以比较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)
我是这个网站的初学者,我已经阅读了上述所有答案,并得到了我想要的信息。但是,我没有投票的权利。所以我想以我理解的答案开始我的 StackOverflow。
@staticmethod
不需要 self 或 cls 作为方法的第一个参数@staticmethod
@classmethod
包装函数可以由实例或类变量调用@staticmethod
装饰函数影响某种“不可变属性”,子类继承不能覆盖其由@staticmethod
装饰器包装的基类函数。@classmethod
需要 cls(类名,如果需要,可以更改变量名,但不建议)作为函数的第一个参数@classmethod
一直以子类方式使用,子类继承可能会改变基类函数的效果,即@classmethod
包装好的基类函数可能被不同的子类覆盖。简而言之,@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 可以根据需要添加尽可能多的替代构造函数。
一种稍微不同的思考方式,可能对某人有用......在超类中使用类方法来定义该方法在被不同的子类调用时应该如何表现。当我们想要返回相同的东西而不考虑我们正在调用的子类时,使用静态方法。
类方法可以修改类的状态,它绑定到类,它包含 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)