Meaning of @Classmethod and @Staticmethod For Beginner

Meaning of @classmethod and @staticmethod for beginner

Though classmethod and staticmethod are quite similar, there's a slight difference in usage for both entities: classmethod must have a reference to a class object as the first parameter, whereas staticmethod can have no parameters at all.

Example

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

Explanation

Let's assume an example of a class, dealing with date information (this will be our boilerplate):

class Date(object):

def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year

This class obviously could be used to store information about certain dates (without timezone information; let's assume all dates are presented in UTC).

Here we have __init__, a typical initializer of Python class instances, which receives arguments as a typical instance method, having the first non-optional argument (self) that holds a reference to a newly created instance.

Class Method

We have some tasks that can be nicely done using classmethods.

Let's assume that we want to create a lot of Date class instances having date information coming from an outer source encoded as a string with format 'dd-mm-yyyy'. Suppose we have to do this in different places in the source code of our project.

So what we must do here is:

  1. Parse a string to receive day, month and year as three integer variables or a 3-item tuple consisting of that variable.
  2. Instantiate Date by passing those values to the initialization call.

This will look like:

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

For this purpose, C++ can implement such a feature with overloading, but Python lacks this overloading. Instead, we can use classmethod. Let's create another constructor.

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

Let's look more carefully at the above implementation, and review what advantages we have here:

  1. We've implemented date string parsing in one place and it's reusable now.
  2. Encapsulation works fine here (if you think that you could implement string parsing as a single function elsewhere, this solution fits the OOP paradigm far better).
  3. cls is the class itself, not an instance of the class. It's pretty cool because if we inherit our Date class, all children will have from_string defined also.

Static method

What about staticmethod? It's pretty similar to classmethod but doesn't take any obligatory parameters (like a class method or instance method does).

Let's look at the next use case.

We have a date string that we want to validate somehow. This task is also logically bound to the Date class we've used so far, but doesn't require instantiation of it.

Here is where staticmethod can be useful. Let's look at the next piece of code:

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

So, as we can see from usage of staticmethod, we don't have any access to what the class is---it's basically just a function, called syntactically like a method, but without access to the object and its internals (fields and other methods), which classmethod does have.

Difference between @staticmethod and @classmethod

Maybe a bit of example code will help: Notice the difference in the call signatures of foo, class_foo and static_foo:

class A(object):
def foo(self, x):
print(f"executing foo({self}, {x})")

@classmethod
def class_foo(cls, x):
print(f"executing class_foo({cls}, {x})")

@staticmethod
def static_foo(x):
print(f"executing static_foo({x})")

a = A()

Below is the usual way an object instance calls a method. The object instance, a, is implicitly passed as the first argument.

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>, 1)

With classmethods, the class of the object instance is implicitly passed as the first argument instead of self.

a.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)

You can also call class_foo using the class. In fact, if you define something to be
a classmethod, it is probably because you intend to call it from the class rather than from a class instance. A.foo(1) would have raised a TypeError, but A.class_foo(1) works just fine:

A.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)

One use people have found for class methods is to create inheritable alternative constructors.


With staticmethods, neither self (the object instance) nor cls (the class) is implicitly passed as the first argument. They behave like plain functions except that you can call them from an instance or the class:

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

Staticmethods are used to group functions which have some logical connection with a class to the class.


foo is just a function, but when you call a.foo you don't just get the function,
you get a "partially applied" version of the function with the object instance a bound as the first argument to the function. foo expects 2 arguments, while a.foo only expects 1 argument.

a is bound to foo. That is what is meant by the term "bound" below:

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

With a.class_foo, a is not bound to class_foo, rather the class A is bound to class_foo.

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

Here, with a staticmethod, even though it is a method, a.static_foo just returns
a good 'ole function with no arguments bound. static_foo expects 1 argument, and
a.static_foo expects 1 argument too.

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

And of course the same thing happens when you call static_foo with the class A instead.

print(A.static_foo)
# <function static_foo at 0xb7d479cc>

What are the differences between @class method, @static method, and a normal function and how do I use them

Object State

Object state is the state of your variables inside the scope of an object.

Class State

Class state is the state of your variables which are defined class-wide. Class variables are available to all instances of that class, not instance specific that is.

Instance Methods

You call these normal functions, these are used to interact with object state. They get/set or do stuff with your variables inside the object.

Class Methods

These have access to the class-wide variables and may perform operations on them, but they cannot modify object state, obviously.

Static Methods

These are not related to the class or the objects derived from that class in anyway. It is a normal function that can be very well defined outside of the scope of your class. You define methods as static just to apply namespacing on them.

Difference between @staticmethod and @classmethod

Maybe a bit of example code will help: Notice the difference in the call signatures of foo, class_foo and static_foo:

class A(object):
def foo(self, x):
print(f"executing foo({self}, {x})")

@classmethod
def class_foo(cls, x):
print(f"executing class_foo({cls}, {x})")

@staticmethod
def static_foo(x):
print(f"executing static_foo({x})")

a = A()

Below is the usual way an object instance calls a method. The object instance, a, is implicitly passed as the first argument.

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>, 1)

With classmethods, the class of the object instance is implicitly passed as the first argument instead of self.

a.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)

You can also call class_foo using the class. In fact, if you define something to be
a classmethod, it is probably because you intend to call it from the class rather than from a class instance. A.foo(1) would have raised a TypeError, but A.class_foo(1) works just fine:

A.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)

One use people have found for class methods is to create inheritable alternative constructors.


With staticmethods, neither self (the object instance) nor cls (the class) is implicitly passed as the first argument. They behave like plain functions except that you can call them from an instance or the class:

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

Staticmethods are used to group functions which have some logical connection with a class to the class.


foo is just a function, but when you call a.foo you don't just get the function,
you get a "partially applied" version of the function with the object instance a bound as the first argument to the function. foo expects 2 arguments, while a.foo only expects 1 argument.

a is bound to foo. That is what is meant by the term "bound" below:

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

With a.class_foo, a is not bound to class_foo, rather the class A is bound to class_foo.

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

Here, with a staticmethod, even though it is a method, a.static_foo just returns
a good 'ole function with no arguments bound. static_foo expects 1 argument, and
a.static_foo expects 1 argument too.

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

And of course the same thing happens when you call static_foo with the class A instead.

print(A.static_foo)
# <function static_foo at 0xb7d479cc>

Why should I use a classmethod in python?

If you see _randomize method, you are not using any instance variable (declared in init) in it but it is using a class var i.e. RANDOM_CHOICE = 'abcdefg'.

import random

class Randomize:
RANDOM_CHOICE = 'abcdefg'

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

def _randomize(self, random_chars=3):
return ''.join(random.choice(self.RANDOM_CHOICE)
for _ in range(random_chars))

It means, your method can exist without being an instance method and you can call it directly on class.

  Randomize._randomize()

Now, the question comes does it have any advantages?

  • I guess yes, you don't have to go through creating an instance to use this method which will have an overhead.

    ran = Randomize() // Extra steps
    ran._randomize()

You can read more about class and instance variable here.

why should i use @classmethod when i can call the constructor in the without the annotation?

Because if you derive from Person, fromBirthYear will always return a Person object and not the derived class.

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def fromBirthYear(name, year):
return Person(name, year)


class Fred(Person):
pass
print(Fred.fromBirthYear('bob', 2019))

Output:

<__main__.Person object at 0x6ffffcd7c88>

You would want Fred.fromBirthYear to return a Fred object.

In the end the language will let you do a lot of things that you shouldn't do.

Why use a classmethod over an Instance method in python

In your second example, you've hard-coded the name and age into the class. If name and age are indeed properties of the class and not a specific instance of the class, than using a class method makes sense. However, if your class was something like Human of which there are many instances with different names and ages, then it wouldn't be possible to create a class method to access the unique names and ages of the specific instance. In that case, you would want to use an instance method.

In general:

  • If you want to access a property of a class as a whole, and not the property of a specific instance of that class, use a class method.
  • If you want to access/modify a property associated with a specific instance of the class, then you will want to use an instance method.

classmethod can change the state of a class, what does this mean?

Both of the explanations you have quoted are wrong. It is quite possible for a static method to modify the class's state:

class A:
x = 1

@staticmethod
def change_static():
A.x = 2

@classmethod
def change_class(cls):
cls.x = 3

Proof:

>>> A.x
1
>>> A.change_static()
>>> A.x
2
>>> A.change_class()
>>> A.x
3

The correct statement is that a class method takes an argument for the class it is called on, named cls in this example. This allows the class method to access the class it is called on (which may in general be a subclass of A), much like an instance method takes an argument usually named self in order to access the instance it is called on.

A static method takes no such argument, but can still access the class by name.


For the second half of your question, you need to understand how accessing an attribute on an instance works:

  • If the instance has its own attribute of that name, then you will get the value of the instance's own attribute.
  • Otherwise, if the instance's class has its own attribute of that name, then you'll get that value instead.
  • Otherwise, you'll get the attribute belonging to the nearest superclass that has one of that name.
  • Otherwise, an AttributeError is raised.

Note that this applies only to getting the value of an attribute; if you set a.x = 23 then you will always be setting the attribute belonging to a itself, even if it didn't have such an attribute before. For example:

>>> a = A()
>>> a.x
3 # a has no x attribute, so this gets the value from the class
>>> A.x = 4
>>> a.x
4 # gets the updated value from the class
>>> a.x = 5
>>> A.x = 6
>>> a.x
5 # a has its own x attribute now, so this doesn't go to the class

In your code, the __init__ method sets the self.myvar attribute, so every instance has this attribute. Therefore, a lookup like obj1.myvar will never fall back to the class's attribute.



Related Topics



Leave a reply



Submit