Given
from datetime import date
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def fromBirthYear(name, year):
return Person(name, date.today().year - year)
def __repr__(self):
return f"Person('{self.name}', {self.age})"
your code works find, as long as you don't access fromBirthYear
via an instance of Person
:
>>> Person("bob", 2010)
Person('bob', 10)
However, invoking it from an instance of Person
will not:
>>> Person("bob", 2010).fromBirthYear("bob again", 10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: fromBirthYear() takes 2 positional arguments but 3 were given
This is due to how the function
type implements the descriptor protocol: access through an instance calls its __get__
method (which returns the method
object that "prepasses" the instance to the underlying function), while access through the class returns the function itself.
To make things more consistent, you can define fromBirthYear
as a static method, which always gives access to the underlying function whether accessed from the class or an instance:
from datetime import date
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def fromBirthYear(name, year):
return Person(name, date.today().year - year)
def __repr__(self):
return f"Person('{self.name}', {self.age})"
>>> Person.fromBirthYear("bob", 2010)
Person('bob', 10)
>>> Person.fromBirthYear("bob", 2010).fromBirthYear("bob again", 2015)
Person('bob again', 5)
Finally, a class method behaves somewhat like a static method, being consistent in the arguments received whether invoked from the class or an instance of the class. But, like an instance method, it does receive one implicit argument: the class itself, rather than the instance of the class. The benefit here is that the instance returned by the class method can be determined at runtime. Say you have a subclass of Person
from datetime import date
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def fromBirthYear(cls, name, year):
return cls(name, date.today().year - year)
def __repr__(self):
return f"Person('{self.name}', {self.age})"
class DifferentPerson(Person):
pass
Both classes can be used to call fromBirthYear
, but the return value now depends on the class which calls it.
>>> type(Person.fromBirthYear("bob", 2010))
<class '__main__.Person'>
>>> type(DifferentPerson.fromBirthYear("other bog", 2010))
<class '__main__.DifferentPerson'>