Meaning of @classmethod and @staticmethod for begi

2018-12-31 03:18发布

Could someone explain to me the meaning of @classmethod and @staticmethod in python? I need to know the difference and the meaning.

As far as I understand, @classmethod tells a class that it's a method which should be inherited into subclasses, or... something. However, what's the point of that? Why not just define the class method without adding @classmethod or @staticmethod or any @ definitions?

tl;dr: when should I use them, why should I use them, and how should I use them?

I'm pretty advanced with C++, so using more advanced programming concepts shouldn't be a problem. Feel free giving me a corresponding C++ example if possible.

12条回答
忆尘夕之涩
2楼-- · 2018-12-31 03:59

I'm a beginner on this site, I have read all above answers, and got the information what I want. However, I don't have the right to upvote. So I want to get my start on StackOverflow with the answer as I understand it.

  • @staticmethod doesn't need self or cls as the first parameter of the method
  • @staticmethod and @classmethod wrapped function could be called by instance or class variable
  • @staticmethod decorated function impact some kind 'immutable property' that subclass inheritance can't overwrite its base class function which is wrapped by a @staticmethod decorator.
  • @classmethod need cls (Class name, you could change the variable name if you want, but it's not advised) as the first parameter of function
  • @classmethod always used by subclass manner, subclass inheritance may change the effect of base class function, i.e. @classmethod wrapped base class function could be overwritten by different subclasses.
查看更多
人气声优
3楼-- · 2018-12-31 04:00

One would use @classmethod when he/she would want to change the behaviour of the method based on which subclass is calling the method. remember we have a reference to the calling class in a class method.

While using static you would want the behaviour to remain unchanged across subclasses

Example:

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..."
查看更多
与君花间醉酒
4楼-- · 2018-12-31 04:02

@classmethod

@classmethod may be compared with __init__. You could think it is another __init__(). It is the way python realize class constructor overloading in c++.

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

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

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

notice they both has a reference for class as first argument in definitioin while __init__ use self but construct_from_func use cls conventionally.

@staticmethod

@staticmethod may be compared with 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)
查看更多
姐姐魅力值爆表
5楼-- · 2018-12-31 04:05

Meaning of @classmethod and @staticmethod?

  • A method is a function in an object's namespace, accessible as an attribute.
  • A regular (i.e. instance) method gets the instance (we usually call it self) as the implicit first argument.
  • A class method gets the class (we usually call it cls) as the implicit first argument.
  • A static method gets no implicit first argument (like a regular function).

when should I use them, why should I use them, and how should I use them?

You don't need either decorator. But on the principle that you should minimize the number of arguments to functions (see Clean Coder), they are useful for doing just that.

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

For both instance methods and class methods, not accepting at least one argument is a TypeError, but not understanding the semantics of that argument is a user error.

(Define some_function's, e.g.:

some_function_h = some_function_g = some_function_f = lambda x=None: x

and this will work.)

dotted lookups on instances and classes:

A dotted lookup on an instance is performed in this order - we look for:

  1. a data descriptor in the class namespace (like a property)
  2. data in the instance __dict__
  3. a non-data descriptor in the class namespace (methods).

Note, a dotted lookup on an instance is invoked like this:

instance = Example()
instance.regular_instance_method 

and methods are callable attributes:

instance.regular_instance_method()

instance methods

The argument, self, is implicitly given via the dotted lookup.

You must access instance methods from instances of the class.

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

class methods

The argument, cls, is implicitly given via dotted lookup.

You can access this method via an instance or the class (or subclasses).

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

static methods

No arguments are implicitly given. This method works like any function defined (for example) on a modules' namespace, except it can be looked up

>>> print(instance.a_static_method())
None

Again, when should I use them, why should I use them?

Each of these are progressively more restrictive in the information they pass the method versus instance methods.

Use them when you don't need the information.

This makes your functions and methods easier to reason about and to unittest.

Which is easier to reason about?

def function(x, y, z): ...

or

def function(y, z): ...

or

def function(z): ...

The functions with fewer arguments are easier to reason about. They are also easier to unittest.

These are akin to instance, class, and static methods. Keeping in mind that when we have an instance, we also have its class, again, ask yourself, which is easier to reason about?:

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):
    ...

Builtin examples

Here are a couple of my favorite builtin examples:

The str.maketrans static method was a function in the string module, but it is much more convenient for it to be accessible from the str namespace.

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

The dict.fromkeys class method returns a new dictionary instantiated from an iterable of keys:

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

When subclassed, we see that it gets the class information as a class method, which is very useful:

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

My advice - Conclusion

Use static methods when you don't need the class or instance arguments, but the function is related to the use of the object, and it is convenient for the function to be in the object's namespace.

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

查看更多
步步皆殇っ
6楼-- · 2018-12-31 04:09

Rostyslav Dzinko's answer is very appropriate. I thought I could highlight one other reason you should choose @classmethod over @staticmethod when you are creating additional constructor.

In the example above, Rostyslav used the @classmethod from_string as a Factory to create Date objects from otherwise unacceptable parameters. The same can be done with @staticmethod as is shown in the code below:

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

Thus both new_year and millenium_new_year are instances of Date class.

But, if you observe closely, the Factory process is hard-coded to create Date objects no matter what. What this means is that even if the Date class is subclassed, the subclasses will still create plain Date object (without any property of the subclass). See that in the example below:

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 is not an instance of DateTime? WTF? Well that's because of the @staticmethod decorator used.

In most cases, this is undesired. If what you want is a Factory method that is aware of the class that called it, then @classmethod is what you need.

Rewriting the Date.millenium as (that's the only part of the above code that changes)

@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

ensures that the class is not hard-coded but rather learnt. cls can be any subclass. The resulting object will rightly be an instance of cls. Let's test that out.

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"

The reason is, as you know by now, @classmethod was used instead of @staticmethod

查看更多
与君花间醉酒
7楼-- · 2018-12-31 04:09

In short, @classmehtod turns a normal method to a factory method.

Let's explore it with an example:

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'

Without a @classmethod,you should labor to creat instances one by one and they are 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

As for example with @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')

Test it:

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

See? Instances are successfully created inside a class definition and they are collected together.

In conclusion, @classmethod decorator convert a conventional method to a factory method,Using classmethods makes it possible to add as many alternative constructors as necessary.

查看更多
登录 后发表回答