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 classmethod
s.
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:
- Parse a string to receive day, month and year as three integer variables or a 3-item tuple consisting of that variable.
- 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:
- We've implemented date string parsing in one place and it's reusable now.
- 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).
cls
is the class itself, not an instance of the class. It's pretty cool because if we inherit ourDate
class, all children will havefrom_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, anda.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, anda.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
"Inconsistent Use of Tabs and Spaces in Indentation"
How to Get the Source Code of a Python Function
How to Create a Text Input Box With Pygame
How to Play Wav File in Python
Finding and Replacing Elements in a List
Remove Empty Strings from a List of Strings
How to Test If a String Contains One of the Substrings in a List, in Pandas
How to Plot Data from Multiple Two Column Text Files With Legends in Matplotlib
Valueerror: Invalid Literal For Int() With Base 10: ''
Pandas: How to Easily Share a Sample Dataframe Using Df.To_Dict()
Using Global Variables Between Files
How to Find the Location of My Python Site-Packages Directory