Python Functions are First-class Objects
In Python functions are first-class objects which means that functions can be assigned to variables or passed as an argument for another function in the same manner as primitive data types and objects. For example, in the following code, the function operator
has three parameters operation
, x
, and y
. While x
and y
can be primitive data types, operation
must be a function. Inside of operator
the arguments x
and y
are passed to operation
, whose result is then returned:
1
2
3
4
5
6
7
8
9
10
11
12
def operator(operation, x, y):
return operation(x, y)
def add(x, y):
return x+y
def sub(x, y):
return x-y
if __name__ == "__main__":
print(operator(add, 23, 42))
print(operator(sub, 23, 42))
Output
1
2
65
-19
In the __main__
(check out this article over here if you want to know why you should always use __name__ == "__main__"
) the functions add
and sub
are passed as operation
to operator
and those two carry out addition or subtraction when called inside of operator
. This concept of passing functions as arguments to other functions is also called higher-order functions which allows us to embed Python functions into other functions.
What is a Decorator in Python
The first-class nature of Python functions gives us the ability to encapsulate a function inside another function:
1
2
3
4
5
6
7
8
9
10
11
12
def succ(function):
def wrapper(*args):
result = function(*args)
return result+1
return wrapper
def add(x, y):
return x+y
if __name__ == "__main__":
add_plus_one = succ(add)
print(add_plus_one(23, 42))
Output
1
66
In the code above, the succ
function takes a function as an argument and then calls that function and adds one to the result, hence the name succ
meaning successor function. In the __main__
section, add
is passed to succ
to construct a new function add_plus_one
, which adds two numbers and adds an additional one to the addition.
Instead of passing a function to a function to get a new function, we can use decorators as a shortcut:
1
2
3
4
5
6
7
8
9
10
11
12
def succ(function):
def wrapper(*args):
result = function(*args)
return result+1
return wrapper
@succ
def add(x, y):
return x+y
if __name__ == "__main__":
print(add(23, 42))
Output
1
66
The statement @succ
above add
does exactly the same thing as succ(add)
but a lot cleaner and globally.
Python @staticmethod
When you have a class and want a method from that class without instantiating an object, you are looking for a static method. In Java or C++ one would declare a function as static with the static
keyword. In Python however, we use the decorator @staticmethod
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Accumulator:
def __init__(self):
self.acc = 0
def add(self, x):
self.acc += x
@staticmethod
def sub(x,y):
return x-y
if __name__ == "__main__":
a = Accumulator()
a.add(23)
a.add(42)
print(a.acc)
print(Accumulator.sub(23, 42))
Output
1
2
65
-19
In the class Accumulator
above, a value passed to add
is added to the variable self.acc
, which is only instantiated when an object of Accumulator
is created. However, our class might have useful methods that don’t depend on object attributes, such as the sub
method, which only subtracts two numbers from each other that are passed as arguments and therefore does not access data in self
. To make sub
usable without instantiating an object of Accumulator
sub
is decorated with @staticmethod
and is missing the parameter self
. In the __main__
section, we see that sub
is called, putting the class name in front of it.
A Python static method knows nothing about the class it is part of and works only with its parameters.
Python @classmethod
Python classes can have attributes and methods such as objects. A class attribute can be helpful if all objects share a common value, while class methods allow us to manipulate class attributes. For example, it might be helpful to track how many instances of a class have been created:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Accumulator:
count = 0
def __init__(self):
Accumulator.increase_count()
self.acc = 0
def add(self, x):
self.acc += x
@classmethod
def increase_count(cls):
cls.count += 1
@classmethod
def get_count(cls):
return cls.count
if __name__ == "__main__":
a = Accumulator()
print(Accumulator.get_count())
b = Accumulator()
print(Accumulator.get_count())
Output
1
2
1
2
The code above Accumulator
has the class attribute count
and the class methods increase_count
and get_count
, which are both decorated with @classmethod
. Every time a new Accumulator
object is created, __init__
calls Accumulator.increase_count
, which adds one to count
and thereby tracks how many Accumulator
objects have been instantiated. Looking closely at the class methods, we can see they have the cls
parameter, representing the Accumulator
class, and there is no self
parameter. Under __main__
, we can see that every time a new object is created, the count
is increased by one, which is an attribute of the class and not the individual objects.
A Python class method can access and manipulate the attributes of a class and its first parameter is always the class itself.
Conclusion
This article looked at the difference between a static and a class method in Python. While static methods know nothing about the class, class methods can access and manipulate class attributes. The decorators @staticmethod
and @classmethod
are used to declare a static or class method in Python and allow you to use functions of a class without instantiating an object of that class.