用@staticmethod修饰的方法和用@classmethod修饰的方法有什么区别?


当前回答

Python带有几个内置的装饰器。三大类是:

@classmethod
@staticmethod
@property

首先,让我们注意,类的任何函数都可以用这个类的实例调用(在初始化这个类之后)。

@classmethod是一种方法,它不仅可以作为类的实例调用函数,还可以直接由类本身作为其第一个参数调用函数。

@staticmethod是一种将函数放入类的方法(因为它在逻辑上属于类),同时表示它不需要访问类(因此我们不需要在函数定义中使用self)。

让我们考虑一下以下课程:

class DecoratorTest(object):

    def __init__(self):
        pass

    def doubler(self, x):
        return x*2

    @classmethod
    def class_doubler(cls, x): # we need to use 'cls' instead of 'self'; 'cls' reference to the class instead of an instance of the class
        return x*2

    @staticmethod
    def static_doubler(x): # no need adding 'self' here; static_doubler() could be just a function not inside the class
        return x*2

让我们看看它是如何工作的:

decor = DecoratorTest()

print(decor.doubler(5))
# 10

print(decor.class_doubler(5)) # a call with an instance of a class
# 10
print(DecoratorTest.class_doubler(5)) # a direct call by the class itself
# 10

# staticmethod could be called in the same way as classmethod.
print(decor.static_doubler(5)) # as an instance of the class
# 10
print(DecoratorTest.static_doubler(5)) # or as a direct call 
# 10

这里您可以看到这些方法的一些用例。

奖金:您可以在这里阅读@property decorator

其他回答

静态方法是一种对所调用的类或实例一无所知的方法。它只获取传递的参数,而不是隐式的第一个参数。它在Python中基本上是无用的——您可以只使用模块函数而不是静态方法。

另一方面,类方法是一种方法,它将被调用的类或被调用的实例的类作为第一个参数传递。当您希望该方法成为类的工厂时,这很有用:因为它获得了作为第一个参数调用的实际类,所以即使涉及子类,您也可以始终实例化正确的类。例如,观察类方法dict.fromkeys()在子类上调用时如何返回子类的实例:

>>> class DictSubclass(dict):
...     def __repr__(self):
...         return "DictSubclass"
... 
>>> dict.fromkeys("abc")
{'a': None, 'c': None, 'b': None}
>>> DictSubclass.fromkeys("abc")
DictSubclass
>>> 

我认为提供一个纯Python版本的staticmethod和classmethod将有助于在语言级别上理解它们之间的区别(请参阅Descriptor Howto Guide)。

这两个都是非数据描述符(如果您首先熟悉描述符,那么更容易理解它们)。

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f


class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        def inner(*args, **kwargs):
            if cls is None:
                cls = type(obj)
            return self.f(cls, *args, **kwargs)
        return inner

@python2.4中添加了修饰符。如果您使用的是python<2.4,则可以使用classmethod()和staticmethod()函数。

例如,如果您想要创建一个工厂方法(一个函数,它返回一个类的不同实现的实例,具体取决于它获得的参数),您可以执行以下操作:

class Cluster(object):

    def _is_cluster_for(cls, name):
        """
        see if this class is the cluster with this name
        this is a classmethod
        """ 
        return cls.__name__ == name
    _is_cluster_for = classmethod(_is_cluster_for)

    #static method
    def getCluster(name):
        """
        static factory method, should be in Cluster class
        returns a cluster object for the given name
        """
        for cls in Cluster.__subclasses__():
            if cls._is_cluster_for(name):
                return cls()
    getCluster = staticmethod(getCluster)

还要注意,这是一个使用类方法和静态方法的好例子,静态方法显然属于类,因为它在内部使用了类Cluster。类方法只需要关于类的信息,而不需要对象的实例。

将_is_cluster_for方法设置为类方法的另一个好处是,子类可以决定更改其实现,这可能是因为它非常通用,可以处理多种类型的集群,因此仅检查类的名称是不够的。

我开始用C++学习编程语言,然后是Java,然后是Python,所以这个问题也困扰了我,直到我理解了每种语言的简单用法。

类方法:Python不像Java和C++那样没有构造函数重载。为了实现这一点,可以使用classmethod。以下示例将对此进行解释

让我们考虑一个Person类,它接受两个参数first_name和last_name,并创建Person的实例。

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

现在,如果您需要仅使用一个名称(仅使用first_name)创建一个类,那么在Python中不能这样做。

当您尝试创建对象(实例)时,这将给您一个错误。

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    def __init__(self, first_name):
        self.first_name = first_name

然而,您可以使用@classmethod实现以下相同的功能

class Person(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @classmethod
    def get_person(cls, first_name):
        return cls(first_name, "")

静态方法:这相当简单,它不绑定到实例或类,您可以使用类名简单地调用它。

所以,在上面的示例中,假设您需要验证first_name不应超过20个字符,您可以简单地这样做。

@staticmethod  
def validate_name(name):
    return len(name) <= 20

您可以使用类名调用

Person.validate_name("Gaurang Shah")

我想在前面所有答案的基础上补充以下内容,这不是官方的,但符合标准。

首先,你可以考虑总是给予最少的特权。因此,如果您不需要特定于实例的内容,请将其设置为类方法。如果您不需要特定于类的内容,请将其设置为静态方法。

第二件事是考虑您可以通过创建的方法类型进行通信。静态方法-助手函数意味着在类本身之外使用。类函数-可以在没有实例化的情况下调用,但意味着只能与该类一起使用-否则将是一个静态方法!实例方法-仅用于实例。

这可以帮助您沟通模式以及如何使用代码。

class Foo:
    @classmethod
    def bar(cls, id: int = None):
        query = session.query(
            a.id,
            a.name,
            a.address,
        )

        if id is not None:
            query = query.filter(a.id == id)

        return query

例如,上面提到的——没有理由说明方法栏不能是静态的。然而,通过将其设置为类方法,您可以传达它应该由类本身使用,而不是作为其他地方使用的助手函数!

请记住,以上内容不是官方的,而是我个人的偏好