首页 文章

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

提问于
浏览
1368

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

据我了解, @classmethod 告诉一个 class ,它的意思是_184409?为什么不在不添加 @classmethod@staticmethod 或任何 @ 定义的情况下定义类方法?

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

我对C非常先进,所以使用更高级的编程概念应该不是问题 . 如果可能,请随意给我一个相应的C示例 .

12 回答

  • 1

    类方法可以修改类状态,它绑定到类并且它包含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)
    
  • 29

    稍微不同的思考方式可能对某人有用...在超类中使用类方法来定义该方法在被不同子类调用时应该如何表现 . 当我们想要返回相同的东西时使用静态方法,而不管我们调用的子类 .

  • -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类实例的典型初始化器,它接收参数作为典型的 instancemethod ,具有第一个非可选参数( self ),其中包含对新创建的实例的引用 .

    Class Method

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

    假设我们想要创建许多 Date 类实例,其日期信息来自外部源,编码为格式为'dd-mm-yyyy'的字符串 . 假设我们必须在项目源代码的不同位置执行此操作 .

    所以我们必须做的是:

    • 将字符串解析为接收日,月和年作为三个整数变量或由该变量组成的3项元组 .

    • 通过将这些值传递给初始化调用来实例化 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')
    

    让我们更仔细地看一下上面的实现,并回顾一下我们在这里有什么优势:

    • 我们've implemented date string parsing in one place and it'现在可以重复使用 .

    • 封装在这里运行正常(如果你认为你可以在其他地方实现字符串解析作为单个函数,这个解决方案更适合OOP范例) .

    • cls 是一个保存 class itself 的对象,而不是该类的实例 . 这很酷,因为如果我们继承了 Date 类,所有孩子也会定义 from_string .

    Static method

    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则可以 .

  • 30

    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
    

    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

  • 0

    A little compilation

    @staticmethod 一种在类中编写方法而不引用它所调用的对象的方法 . 所以不需要传递像self或cls这样的隐式参数 . 它的写法与在类外写的完全相同,但它在python中没有用,因为如果你需要在一个类中封装一个方法,因为这个方法需要成为该类的一部分@staticmethod就派上用了案件 .

    @classmethod 当您想要编写工厂方法并且此自定义属性可以附加到类中时,这很重要 . 可以在继承的类中重写此属性 .

    这两种方法之间的比较如下

  • 31

    @classmethod 表示:当调用此方法时,我们将类作为第一个参数而不是该类的实例传递(正如我们通常使用的方法) . 这意味着您可以在该方法中使用类及其属性,而不是特定实例 .

    @staticmethod 表示:调用此方法时,我们不会访问该类的实例(当您的方法不使用该实例时,这很有用) .

  • 221

    我是这个网站的初学者,我已经阅读了上述所有答案,并获得了我想要的信息 . 但是,我没有权利进行投票 . 所以我希望在StackOverflow上开始我的理解答案 .

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

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

    • @staticmethod 装饰函数影响某种'immutable property'该子类继承不能覆盖其由 @staticmethod 装饰器包装的基类函数 .

    • @classmethod 需要cls(类名,如果需要,可以更改变量名,但不建议)作为函数的第一个参数

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

  • 756

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

    注意他们都有一个类的引用作为definitioin中的第一个参数,而 __init__ 使用 selfconstruct_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)
    
  • 0

    何时使用每个

    @staticmethod 函数只不过是在类中定义的函数 . 它可以在不首先实例化类的情况下调用 . 它的定义是通过继承不可变的 .

    • Python不必为对象实例化 bound-method .

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

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

    • Factory methods ,用于为类创建实例,例如使用某种预处理 .

    • Static methods calling static methods :如果在几个静态方法中拆分静态方法,则不应对类名进行硬编码,而应使用类方法

    here是这个主题的良好链接 .

  • 2308

    当他/她想要根据调用方法的子类更改方法的行为时,可以使用 @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..."
    
  • 61

    @classmethod和@staticmethod的含义?

    • 方法是对象命名空间中的一个函数,可作为属性访问 .

    • 常规(即实例)方法获取实例(我们通常将其称为 self )作为隐式的第一个参数 .

    • class 方法获取类(我们通常称之为 cls )作为隐式的第一个参数 .

    • static 方法没有隐含的第一个参数(就像常规函数一样) .

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

    你不需要装饰器 . 但是根据你应该最小化函数参数数量的原则(参见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'>
    

    我的建议 - 结论

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

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

  • 1

    简而言之,@ 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}'
    

    如果没有@ classmethod,你应该逐个创建实例并且它们是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 decorator将传统方法转换为工厂方法,使用classmethods可以根据需要添加任意数量的替代构造函数 .

相关问题