Class methods

Class methods are slightly different from static methods in that, like instance methods, they also take a special first argument, but in this case, it is the class object itself. A very common use case for coding class methods is to provide factory capability to a class. Let's see an example:

# oop/class.methods.factory.py
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

@classmethod
def from_tuple(cls, coords): # cls is Point
return cls(*coords)

@classmethod
def from_point(cls, point): # cls is Point
return cls(point.x, point.y)


p = Point.from_tuple((3, 7))
print(p.x, p.y) # 3 7
q = Point.from_point(p)
print(q.x, q.y) # 3 7

In the preceding code, I showed you how to use a class method to create a factory for the class. In this case, we want to create a Point instance by passing both coordinates (regular creation p = Point(3, 7)), but we also want to be able to create an instance by passing a tuple (Point.from_tuple) or another instance (Point.from_point).

Within the two class methods, the cls argument refers to the Point class. As with the instance method, which takes self as the first argument, the class method takes a cls argument. Both self and cls are named after a convention that you are not forced to follow but are strongly encouraged to respect. This is something that no Python coder would change because it is so strong a convention that parsers, linters, and any tool that automatically does something with your code would expect, so it's much better to stick to it.

Class and static methods play well together. Static methods are actually quite helpful in breaking up the logic of a class method to improve its layout. Let's see an example by refactoring the StringUtil class:

# oop/class.methods.split.py
class StringUtil:

@classmethod
def is_palindrome(cls, s, case_insensitive=True):
s = cls._strip_string(s)
# For case insensitive comparison, we lower-case s
if case_insensitive:
s = s.lower()
return cls._is_palindrome(s)

@staticmethod
def _strip_string(s):
return ''.join(c for c in s if c.isalnum())

@staticmethod
def _is_palindrome(s):
for c in range(len(s) // 2):
if s[c] != s[-c -1]:
return False
return True

@staticmethod
def get_unique_words(sentence):
return set(sentence.split())

print(StringUtil.is_palindrome('A nut for a jar of tuna')) # True
print(StringUtil.is_palindrome('A nut for a jar of beans')) # False

Compare this code with the previous version. First of all, note that even though is_palindrome is now a class method, we call it in the same way we were calling it when it was a static one. The reason why we changed it to a class method is that after factoring out a couple of pieces of logic (_strip_string and _is_palindrome), we need to get a reference to them, and if we have no cls in our method, the only option would be to call them like this: StringUtil._strip_string(...) and StringUtil._is_palindrome(...), which is not good practice, because we would hardcode the class name in the is_palindrome method, thereby putting ourselves in the position of having to modify it whenever we want to change the class name. Using cls will act as the class name, which means our code won't need any amendments.

Notice how the new logic reads much better than the previous version. Moreover, notice that, by naming the factored-out methods with a leading underscore, I am hinting that those methods are not supposed to be called from outside the class, but this will be the subject of the next paragraph.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.156.212