【问题标题】:Meaning of @classmethod and @staticmethod for beginner? [duplicate]@classmethod 和 @staticmethod 对初学者的意义? [复制]
【发布时间】:2012-08-24 03:24:50
【问题描述】:

有人可以向我解释@classmethod@staticmethod 在python 中的含义吗?我需要知道区别和含义。

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

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

【问题讨论】:

    标签: python oop static-methods class-method


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

      虽然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),它包含对新创建实例的引用。

      类方法

      我们有一些任务可以使用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 有。

      【讨论】:

      • “from_string”方法将“Date”类(不是Date对象)作为第一个参数“cls”,通过调用cls(day,month,year)返回构造函数,相当于Date (日、月、年)并返回一个 Date 对象。
      • 那么类方法可以被视为“替代构造函数”吗?我认为这是迄今为止最好的解释!
      • 关于您的classmethod 示例,通过将from_string 定义为stringmethod 而不是classmethod,然后,而不是打电话给cls(day, month, year) 你会打电话给Date(day, month, year)?我想在这里使用classmethod唯一 好处是,如果您希望该方法可用于可能继承并期望from_string 工作的子类对于继承的类,对吗?还是我错过了你的意思?
      • @Josh--stringmethod 需要一个现有的对象来处理,所以你不能做同样的事情。正如 Crypoharf84 所提到的,这允许使用另一种构造对象的机制。这似乎类似于 Dart 语言中的“命名构造函数”,它提供了为同一个类创建多个构造函数的机制。不同的构造函数允许轻松创建 API,例如 Date.from_json、Date.from_sql、Date.from_file 等。
      • @DarrylG @Josh 什么是stringmethod?你们是在说staticmethod吗?如果是这样,我仍然不明白为什么您不能使用staticmethod 来完成与@Josh 建议的相同的事情。
      【解决方案3】:

      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 类的实例。

      但是,如果您仔细观察,无论如何,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 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 是什么的最有帮助的事情。
      • 什么是cls?根据上面的答案 - cls 可以是任何子类。结果对象将是 cls 的一个实例。 cls 是调用日期或日期时间的对象或方法吗?请解释一下。
      • @rishijain 就像 self 指代实例一样, cls 指代类 - 例如cls(month, day, 2000) == DateTime(month, day, 2000)
      • 这个答案真正阐明了@classmethod 对支持继承的工厂方法的实用性:通过一些预处理等创建调用类的实例。
      【解决方案4】:

      何时使用每个

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

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

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

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

      here 是该主题的良好链接。

      【讨论】:

      • 感谢您比接受的答案更快地到达重点。
      【解决方案5】:

      一点汇编

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

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

      这两种方法的比较如下

      【讨论】:

        【解决方案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】:

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

          【讨论】:

            【解决方案8】:

            我是这个网站的初学者,我已经阅读了上述所有答案,并得到了我想要的信息。但是,我没有投票的权利。所以我想从 StackOverflow 开始,回答我理解的问题。

            • @staticmethod 不需要 self 或 cls 作为方法的第一个参数
            • @staticmethod@classmethod 包装的函数可以被实例或类变量调用
            • @staticmethod 装饰函数会影响某种“不可变属性”,即子类继承无法覆盖其由 @staticmethod 装饰器包装的基类函数。
            • @classmethod 需要 cls(类名,你可以根据需要更改变量名,但不建议)作为函数的第一个参数
            • @classmethod 一直以子类方式使用,子类继承可能会改变基类函数的效果,即@classmethod 封装的基类函数可能会被不同的子类覆盖。

            【讨论】:

              【解决方案9】:

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

              【讨论】:

                【解决方案10】:

                @classmethod@staticmethod的含义?

                • 方法是对象命名空间中的函数,可作为属性访问。
                • 常规(即实例)方法获取实例(我们通常称之为self)作为隐式的第一个参数。
                • class 方法获取类(我们通常称之为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'> 
                

                我的建议 - 结论

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

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

                【讨论】:

                • 在简洁的解释和微观示例之间取得了恰到好处的平衡,这非常清楚。
                • Use class methods when you don't need instance information, but need the class information perhaps for its other class or static methods, or perhaps itself as a constructor. (You wouldn't hardcode the class so that subclasses could be used here.) 正是我想要的。实际的为什么-我会使用它的答案。
                • 这是最好的解释。谢谢!
                【解决方案11】:

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

                【讨论】:

                • 如果你创建那些没有classmethod的方法,你仍然可以获得相同的结果,这不是真正的区别
                【解决方案12】:

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2019-08-14
                  • 2013-04-16
                  • 2020-04-11
                  • 2018-03-30
                  • 1970-01-01
                  • 2021-08-14
                  • 2019-04-28
                  • 1970-01-01
                  相关资源
                  最近更新 更多