Staticmethod, Classmethod and Property

It’s not so commonly used in data science, but as an OOP language, Python can make good use of class, object. Unique to Python, there are staticmethod, classmethod, property written by professional programmers.

In short, a staticmethod is a method that knows nothing about the class or instance it was called on, while a classmethod provides a way to creation a function like constructor __init__. For example, in the below class of person, classmethod allows to create a person object by calling Person.fromBirthYear(parameters), while staticmethod is independent without feeding any self settings.

from datetime import date 
  
class Person: 
    def __init__(self, name, age): 
        self.name = name 
        self.age = age 
      
    # a class method to create a Person object by birth year. 
    @classmethod
    def fromBirthYear(cls, name, year): 
        return cls(name, date.today().year - year) 
      
    # a static method to check if a Person is adult or not. 
    @staticmethod
    def isAdult(age): 
        return age > 18
  
person1 = Person('mayank', 21) 
person2 = Person.fromBirthYear('mayank', 1996) 
  
print person1.age 
print person2.age 

Another example here:

class RomanNumeral:
    SYMBOLS = {'M':1000, 'D':500, 'C':100, 'L':50, 'X':10, 'V':5}
    def __init__(self, number):
        self.value = number
    @classmethod
    def from_string(cls, string):
        return cls(roman_to_int(string))
    @staticmethod
    def roman_to_int(numeral):
        total = 0
        for symbol, next_symbol in zip_longest(numeral, numeral[1:]):
            value = RomanNumeral.SYMBOLS[symbol]
            next_value = RomanNumeral.SYMBOLS.get(next_symbol, 0)
            if value < next_value:
                value = -value
            total += value
        return total    

On the other hand, there is a problem in class object that it is needed to change the setting of certain attributes arbitrarily. One way of doing it is to use @property and then setter. For example, below __population is particularly set to be private, and then population attribute can be changed.

class Golem:
    __population = 0
    __life_span = 10
    
    def __init__(self, name=None):
        self.name = name
        self.built_year = datetime.date.today().year
        self.__active = True
        Golem.__population += 1
    
    def say_hi(self):
        print('Hi!')
    
    def cease(self):
        self.__active = False
        Golem.__population -= 1
    
    def is_active(self):
        if datetime.date.today().year - self.built_year >= Golem.__life_span:
            self.cease
        return self.__active
    
       
    @property
    def population(self):
        return Golem.__population

    @population.setter
    def population(self, value):
        Golem.__population = value
        
g = Golem('Clay')
g.population

to quickly set the attr use:

setattr(Golem, ‘population’, 100)

Using Raymond H.’s example of a lean start up creating an MVP (minimal viable product) who specialized in circles, the following is the complete codes

import math

class Circle(object) :  # new-style class
    'An advanced circle analytic toolkit'

    # flyweight design pattern suppresses
    # the instance dictionary

    __slots__ = ['diameter']
    version = '0.7'

    def __init__(self, radius):
        self.radius = radius  # instance variable

    @property    # convert dotted access to method calls
    def radius(self):
        'Radius of circle'
        return self.diameter / 2.0

    @radius.setter
    def radius(self, radius):
        self.diameter = radius * 2.0

    @staticmethod   # attach function to classes
    def angle_to_grade(self, angle) :
        'Convert angle in degree to a percentage grade'
        return math.tan(math.radians(angle)) * 100.0
    
    
    def area(self) :
        'perform quadrature on a shape of uniform radius'
        p = self.__perimeter()
        r = p / math.pi / 2.0
        return math.pi  * r ** 2.0

    def perimeter(self):
        return 2.0 * math.pi * self.radius
    __perimeter =  perimeter

    @classmethod  #alternative constructor
    def from_bbd(cls, bbd) :
        'Construct a circle from a bounding box diagonal'
        radius = bbd / 2.0 / math.sqrt(2.0)
        return Circle(radius)

class Tire(Circle) :
    'Tires are circles with a corrected perimeter'
  
    def perimeter(self) :
        'Circumference corrected for the rubber'
        return Circle.perimeter(self) * 1.25

from random import random, seed

seed(8675309)
print('Using Circuituous (tm) version', Circle.version)
n = 10
circles = [Circle(random()) for i in range(n)]
print('The average area of', n, 'random circles')
avg = sum([c.area() for c in circles]) / n
print('is %.if') %avg

#subclass concept
#constructor war get their wish, dig in their position
#need alternative constructor, the technique is classmethod

To summarize, the tookset is

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.