3.2. OOP Classmethod
Using class as namespace
Will pass class as a first argument
self
is not required
In Python, classmethod
is a built-in decorator that defines a method
that belongs to the class rather than to an instance of the class.
This means that the method can be called on the class itself, rather
than on an instance of the class.
A classmethod
takes a cls
parameter as its first argument,
which refers to the class itself, rather than to an instance of the class.
This allows the method to access and modify class-level attributes and methods.
Here's an example of using the classmethod
decorator to define a method
that returns the number of instances of a class:
>>> class MyClass:
... count = 0
...
... def __init__(self):
... MyClass.count += 1
...
... @classmethod
... def get_count(cls):
... return cls.count
>>>
>>> # Create some instances of MyClass
>>> obj1 = MyClass()
>>> obj2 = MyClass()
>>> obj3 = MyClass()
>>>
>>> # Call the classmethod on the class itself
>>> print(MyClass.get_count())
3
In this example, the MyClass
class defines a class-level attribute
count
and a classmethod
called get_count()
. The __init__()
method of the class increments the count
attribute each time a new
instance of the class is created.
The get_count()
method is decorated with the classmethod
decorator,
which means that it can be called on the class itself, rather than on an
instance of the class. The method returns the value of the count
attribute, which represents the number of instances of the class that have
been created.
The get_count()
method takes a cls
parameter as its first argument,
which refers to the class itself. This allows the method to access the
count
attribute of the class and return its value.
Dynamic methods:
>>> class User:
... def login(self):
... print('User login')
Static methods:
>>> class User:
... def login():
... print('User login')
Static and dynamic method:
>>> class User:
... @staticmethod
... def login():
... print('User login')
Class methods:
>>> class User:
... @classmethod
... def login(cls):
... print('User login')
3.2.1. Recap
>>> class User:
... @staticmethod
... def login():
... print('User login')
>>>
>>> class Admin:
... @staticmethod
... def login():
... print('Admin login')
>>>
>>>
>>> User.login()
User login
>>>
>>> Admin.login()
Admin login
3.2.2. Problem
>>> class User:
... @staticmethod
... def login():
... print('User login')
>>>
>>> class Admin(User):
... pass
>>>
>>>
>>> User.login()
User login
>>>
>>> Admin.login()
User login
3.2.3. Solution
@classmethod
decoratorFirst argument is
cls
which refers to the class itself
>>> class User:
... @classmethod
... def login(cls):
... print(f'{cls.__name__} login')
>>>
>>> class Admin(User):
... pass
>>>
>>>
>>> User.login()
User login
>>>
>>> Admin.login()
Admin login
3.2.4. Case Study - 1
>>> from dataclasses import dataclass
>>> import json
This is a continuation of the example from staticmethod chapter We received a JSON formatted data from the REST API endpoint:
>>> DATA = '{"firstname": "Mark", "lastname": "Watney"}'
And we modeled a User
class to store this data, together
with a helper function from_json
, which does the conversion
from JSON to Python dictionary and returns an instance of the class:
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... @staticmethod
... def from_json(string):
... data = json.loads(string)
... return User(**data)
>>> DATA = '{"firstname":"Mark", "lastname":"Watney"}'
>>> mark = User.from_json(string=DATA)
>>>
>>> mark
User(firstname='Mark', lastname='Watney')
Now, we want to create a subclass Admin
which will inherit
from the User
class. And we want to use the same from_json
method to create an instance of the the class on which it was called:
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... @staticmethod
... def from_json(string):
... data = json.loads(string)
... return User(**data)
...
...
>>> class Admin(User):
... pass
The same data as before:
>>> DATA = '{"firstname":"Mark", "lastname":"Watney"}'
Let's create an instance of a User
class using helper function.
This work as expected:
>>> User.from_json(string=DATA)
User(firstname='Mark', lastname='Watney')
Now, let's create an instance of an Admin
class using the same
helper function. This will not work as expected:
>>> Admin.from_json(string=DATA)
User(firstname='Mark', lastname='Watney')
It has created an instance of the User
class, not the Admin
class.
This is because the from_json
method is defined on the User
class,
and it will always return an instance of the User
class.
To solve this problem, we can use a classmethod
decorator.
Note, the change from @staticmethod
to @classmethod
for the from_json
helper method, also, there is another
parameter cls
. The first argument of a class method is
a class itself. The return value of the method is the class
on which it was called.
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... @classmethod
... def from_json(cls, string):
... data = json.loads(string)
... return cls(**data)
...
...
>>> class Admin(User):
... pass
Let's create an instance of a User
class using helper function.
This work as expected:
>>> User.from_json(string=DATA)
User(firstname='Mark', lastname='Watney')
And calling it on a subclass will return an instance of the subclass: This work as expected:
>>> Admin.from_json(string=DATA)
Admin(firstname='Mark', lastname='Watney')
3.2.5. Case Study - 3
>>> from datetime import date
>>>
>>> class date:
... """Concrete date type.
...
... Constructors:
...
... __new__()
... fromtimestamp()
... today()
... fromordinal()
...
... Operators:
...
... __repr__, __str__
... __eq__, __le__, __lt__, __ge__, __gt__, __hash__
... __add__, __radd__, __sub__ (add/radd only with timedelta arg)
...
... Methods:
...
... timetuple()
... toordinal()
... weekday()
... isoweekday(), isocalendar(), isoformat()
... ctime()
... strftime()
...
... Properties (readonly):
... year, month, day
... """
... __slots__ = '_year', '_month', '_day', '_hashcode'
...
... def __new__(cls, year, month=None, day=None):
... """Constructor.
...
... Arguments:
...
... year, month, day (required, base 1)
... """
... if (month is None and
... isinstance(year, (bytes, str)) and len(year) == 4 and
... 1 <= ord(year[2:3]) <= 12):
... # Pickle support
... if isinstance(year, str):
... try:
... year = year.encode('latin1')
... except UnicodeEncodeError:
... # More informative error message.
... raise ValueError(
... "Failed to encode latin1 string when unpickling "
... "a date object. "
... "pickle.load(data, encoding='latin1') is assumed.")
... self = object.__new__(cls)
... self.__setstate(year)
... self._hashcode = -1
... return self
... year, month, day = _check_date_fields(year, month, day)
... self = object.__new__(cls)
... self._year = year
... self._month = month
... self._day = day
... self._hashcode = -1
... return self
...
... # Additional constructors
...
... @classmethod
... def fromtimestamp(cls, t):
... "Construct a date from a POSIX timestamp (like time.time())."
... if t is None:
... raise TypeError("'NoneType' object cannot be interpreted as an integer")
... y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
... return cls(y, m, d)
...
... @classmethod
... def today(cls):
... "Construct a date from time.time()."
... t = _time.time()
... return cls.fromtimestamp(t)
...
... @classmethod
... def fromordinal(cls, n):
... """Construct a date from a proleptic Gregorian ordinal.
...
... January 1 of year 1 is day 1. Only the year, month and day are
... non-zero in the result.
... """
... y, m, d = _ord2ymd(n)
... return cls(y, m, d)
...
... @classmethod
... def fromisoformat(cls, date_string):
... """Construct a date from a string in ISO 8601 format."""
... if not isinstance(date_string, str):
... raise TypeError('fromisoformat: argument must be str')
...
... if len(date_string) not in (7, 8, 10):
... raise ValueError(f'Invalid isoformat string: {date_string!r}')
...
... try:
... return cls(*_parse_isoformat_date(date_string))
... except Exception:
... raise ValueError(f'Invalid isoformat string: {date_string!r}')
...
... @classmethod
... def fromisocalendar(cls, year, week, day):
... """Construct a date from the ISO year, week number and weekday.
...
... This is the inverse of the date.isocalendar() function"""
... return cls(*_isoweek_to_gregorian(year, week, day))
...
... ...
3.2.6. Use Case - 1
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> class JSONMixin:
... @classmethod
... def from_json(cls, data):
... data = json.loads(data)
... return cls(**data)
>>>
>>> @dataclass
... class User(JSONMixin):
... firstname: str
... lastname: str
>>>
>>> @dataclass
... class Admin(JSONMixin):
... firstname: str
... lastname: str
>>>
>>>
>>> data = '{"firstname": "Mark", "lastname": "Watney"}'
>>>
>>> User.from_json(data)
User(firstname='Mark', lastname='Watney')
>>>
>>> Admin.from_json(data)
Admin(firstname='Mark', lastname='Watney')
3.2.7. Use Case - 2
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Account:
... firstname: str = None
... lastname: str = None
...
... @classmethod
... def from_json(cls, data):
... import json
... data = json.loads(data)
... return cls(**data)
>>>
>>> class User(Account):
... pass
>>>
>>> class Admin(Account):
... pass
>>>
>>>
>>> data = '{"firstname": "Mark", "lastname": "Watney"}'
>>>
>>> User.from_json(data)
User(firstname='Mark', lastname='Watney')
>>>
>>> Admin.from_json(data)
Admin(firstname='Mark', lastname='Watney')
3.2.8. Use Case - 3
Singleton
>>> class Singleton:
... _instance = None
...
... @classmethod
... def get_instance(cls):
... if cls._instance is None:
... cls._instance = object.__new__(cls)
... return cls._instance
>>>
>>>
>>> class DatabaseConnection(Singleton):
... pass
>>>
>>>
>>> db1 = DatabaseConnection.get_instance()
>>> db2 = DatabaseConnection.get_instance()
>>>
>>>
>>> print(db1)
<__main__.DatabaseConnection object at 0x102453ee0>
>>>
>>> print(db2)
<__main__.DatabaseConnection object at 0x102453ee0>
3.2.9. Use Case - 4
File myapp/timezone.py
:
>>> class AbstractTimezone:
... tzname: str
... tzcode: str
...
... def __init__(self, date, time):
... ...
...
... @classmethod
... def from_string(cls, string):
... values = datetime.fromisoformat(string)
... return cls(**values)
>>>
>>>
>>> class CET(AbstractTimezone):
... tzname = 'Central European Time'
... tzcode = 'CET'
>>>
>>> class CEST(AbstractTimezone):
... tzname = 'Central European Summer Time'
... tzcode = 'CEST'
Operating system:
export TIMEZONE=CET
File: myapp/settings.py
:
>>>
... import myapp.timezone
... from os import getenv
...
... time = getattr(myapp.timezone, getenv('TIMEZONE'))
File myapp/usage.py:
>>>
... from myapp.settings import time
...
... dt = time.from_string('1969-07-21T02:53:15Z')
... print(dt.tzname)
Central European Time
3.2.10. Use Case - 5
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... @classmethod
... def from_json(cls, string: str):
... data = json.loads(string)
... return cls(**data)
...
... @classmethod
... def from_csv(cls, string: str):
... data = string.split(',')
... return cls(*data)
...
... @classmethod
... def from_dict(cls, data: dict):
... return cls(**data)
...
... @classmethod
... def from_list(cls, data: list):
... return cls(*data)
...
... @classmethod
... def from_tuple(cls, data: tuple):
... return cls(*data)
>>>
>>>
>>> @dataclass
... class Admin(User):
... pass
>>> DATA = '{"firstname":"Mark","lastname":"Watney"}'
>>>
>>> mark = User.from_json(DATA)
>>> mark
User(firstname='Mark', lastname='Watney')
>>>
>>> melissa = Admin.from_json(DATA)
>>> melissa
Admin(firstname='Mark', lastname='Watney')
3.2.11. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromTuple
# - Difficulty: easy
# - Lines: 3
# - Minutes: 3
# %% English
# 1. Define class `Book` with:
# - Field `title: str`
# - Field `author: str`
# - Method `from_tuple()`
# 2. Method `from_tuple()`:
# - Parameter `data: tuple[str,str]`, example: ('Martian', 'Andy Weir')
# - Returns instance of a class on which was called
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Book` z:
# - Polem `title: str`
# - Polem `author: str`
# - Metodą `from_tuple()`
# 2. Metoda `from_tuple()`:
# - Parametr `data: tuple[str,str]`, przykład: ('Martian', 'Andy Weir')
# - Zwraca instancję klasy na której została wykonana
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(Book)
>>> assert isclass(ScienceFiction)
>>> assert isclass(History)
>>> assert isclass(Adventure)
>>> MARTIAN = ('Martian', 'Andy Weir')
>>> martian = ScienceFiction.from_tuple(MARTIAN)
>>> assert type(martian.title) is str
>>> assert type(martian.author) is str
>>> assert martian.title == 'Martian'
>>> assert martian.author == 'Andy Weir'
>>> DUNE = ('Dune', 'Frank Herbert')
>>> dune = Adventure.from_tuple(DUNE)
>>> assert type(dune.title) is str
>>> assert type(dune.author) is str
>>> assert dune.title == 'Dune'
>>> assert dune.author == 'Frank Herbert'
>>> RIGHT_STUFF = ('The Right Stuff', 'Tom Wolfe')
>>> right_stuff = History.from_tuple(RIGHT_STUFF)
>>> assert type(right_stuff.title) is str
>>> assert type(right_stuff.author) is str
>>> assert right_stuff.title == 'The Right Stuff'
>>> assert right_stuff.author == 'Tom Wolfe'
"""
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
# parameter: `data: tuple[str,str]`
# example: ('Martian', 'Andy Weir')
# return: instance of a class on which was called
# type: Callable[[type[Self], tuple[str, str]], Self]
def from_tuple():
...
class ScienceFiction(Book):
pass
class History(Book):
pass
class Adventure(Book):
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromCsv
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3
# %% English
# 1. Define class `Iris` with:
# - Field `sepal_length: float`
# - Field `sepal_width: float`
# - Field `petal_length: float`
# - Field `petal_width: float`
# - Field `species: str`
# - Method `from_csv()`
# 2. Method `from_csv()`:
# - Parameter `data: str`, example: '5.8,2.7,5.1,1.9,virginica'
# - Returns instance of a class on which was called
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Iris` z:
# - Polem `sepal_length: float`
# - Polem `sepal_width: float`
# - Polem `petal_length: float`
# - Polem `petal_width: float`
# - Polem `species: str`
# - Metodą `from_csv()`
# 2. Metoda `from_csv()`:
# - Parametr `data: str`, przykład: '5.8,2.7,5.1,1.9,virginica'
# - Zwraca instancję klasy na której została wykonana
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `str.split()`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(Iris)
>>> assert isclass(Setosa)
>>> assert isclass(Versicolor)
>>> assert isclass(Virginica)
>>> data = '5.8,2.7,5.1,1.9,virginica'
>>> virginica = Virginica.from_csv(data)
>>> assert type(virginica.sepal_length) is float
>>> assert type(virginica.sepal_width) is float
>>> assert type(virginica.petal_length) is float
>>> assert type(virginica.petal_width) is float
>>> assert type(virginica.species) is str
>>> assert virginica.sepal_length == 5.8
>>> assert virginica.sepal_width == 2.7
>>> assert virginica.petal_length == 5.1
>>> assert virginica.petal_width == 1.9
>>> assert virginica.species == 'virginica'
>>> data = '5.1,3.5,1.4,0.2,setosa'
>>> setosa = Setosa.from_csv(data)
>>> assert type(setosa.sepal_length) is float
>>> assert type(setosa.sepal_width) is float
>>> assert type(setosa.petal_length) is float
>>> assert type(setosa.petal_width) is float
>>> assert type(setosa.species) is str
>>> assert setosa.sepal_length == 5.1
>>> assert setosa.sepal_width == 3.5
>>> assert setosa.petal_length == 1.4
>>> assert setosa.petal_width == 0.2
>>> assert setosa.species == 'setosa'
>>> data = '5.7,2.8,4.1,1.3,versicolor'
>>> versicolor = Versicolor.from_csv(data)
>>> assert type(versicolor.sepal_length) is float
>>> assert type(versicolor.sepal_width) is float
>>> assert type(versicolor.petal_length) is float
>>> assert type(versicolor.petal_width) is float
>>> assert type(versicolor.species) is str
>>> assert versicolor.sepal_length == 5.7
>>> assert versicolor.sepal_width == 2.8
>>> assert versicolor.petal_length == 4.1
>>> assert versicolor.petal_width == 1.3
>>> assert versicolor.species == 'versicolor'
"""
class Iris:
def __init__(self, sepal_length, sepal_width,
petal_length, petal_width, species):
self.sepal_length = sepal_length
self.sepal_width = sepal_width
self.petal_length = petal_length
self.petal_width = petal_width
self.species = species
# parameter: `data: str`
# example: '5.8,2.7,5.1,1.9,virginica'
# return: instance of a class on which was called
# type: Callable[[type[Self], str], Self]
def from_csv():
...
class Setosa(Iris):
pass
class Virginica(Iris):
pass
class Versicolor(Iris):
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromString
# - Difficulty: easy
# - Lines: 4
# - Minutes: 5
# %% English
# 1. Define class `Account` with:
# - Field `username: str`
# - Field `password: str`
# - Field `uid: int`
# - Field `gid: int`
# - Field `gecos: str`
# - Field `home: str`
# - Field `shell: str`
# - Method `from_passwd()`
# 2. Method `from_passwd()`:
# - Parameter `data: str`, example: 'root:x:0:0:root:/root:/bin/bash'
# - Returns instance of a class on which was called
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Account` z:
# - Polem `username: str`
# - Polem `password: str`
# - Polem `uid: int`
# - Polem `gid: int`
# - Polem `gecos: str`
# - Polem `home: str`
# - Polem `shell: str`
# - Metodą `from_passwd()`
# 2. Metoda `from_passwd()`:
# - Parametr `data: str`, przykład: 'root:x:0:0:root:/root:/bin/bash'
# - Zwraca instancję klasy na której została wykonana
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `str.split()`
# - `int()`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(Account)
>>> assert isclass(SystemAccount)
>>> assert isclass(UserAccount)
>>> data = 'root:x:0:0:root:/root:/bin/bash'
>>> admin = SystemAccount.from_passwd(data)
>>> assert type(admin.username) is str
>>> assert type(admin.password) is str
>>> assert type(admin.uid) is int
>>> assert type(admin.gid) is int
>>> assert type(admin.gecos) is str
>>> assert type(admin.home) is str
>>> assert type(admin.shell) is str
>>> assert admin.username == 'root'
>>> assert admin.password == 'x'
>>> assert admin.uid == 0
>>> assert admin.gid == 0
>>> assert admin.gecos == 'root'
>>> assert admin.home == '/root'
>>> assert admin.shell == '/bin/bash'
>>> data = 'watney:x:1000:1000:Mark Watney:/home/watney:/bin/bash'
>>> user = UserAccount.from_passwd(data)
>>> assert type(user.username) is str
>>> assert type(user.password) is str
>>> assert type(user.uid) is int
>>> assert type(user.gid) is int
>>> assert type(user.gecos) is str
>>> assert type(user.home) is str
>>> assert type(user.shell) is str
>>> assert user.username == 'watney'
>>> assert user.password == 'x'
>>> assert user.uid == 1000
>>> assert user.gid == 1000
>>> assert user.gecos == 'Mark Watney'
>>> assert user.home == '/home/watney'
>>> assert user.shell == '/bin/bash'
"""
class Account:
def __init__(self, username, password, uid, gid, gecos, home, shell):
self.username = username
self.password = password
self.uid = uid
self.gid = gid
self.gecos = gecos
self.home = home
self.shell = shell
# parameter: `data: str`
# example: 'root:x:0:0:root:/root:/bin/bash'
# hint: uid and gid must be int
# return: instance of a class on which was called
# type: Callable[[type[Self], str], Self]
def from_passwd():
...
class SystemAccount(Account):
pass
class UserAccount(Account):
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromDict
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3
# %% English
# 1. Define class `Animal` with:
# - Field `english_name: str`
# - Field `latin_name: str`
# - Method `from_dict()`
# 2. Method `from_dict()`:
# - Parameter `data: dict[str,str]`, example: {'english_name': 'Cat', 'latin_name': 'Felis catus'}
# - Returns instance of a class on which was called
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Animal` z:
# - Polem `english_name: str`
# - Polem `latin_name: str`
# - Metodą `from_dict()`
# 2. Metoda `from_dict()`:
# - Parametr `data: dict[str,str]`, przykład: {'english_name': 'Cat', 'latin_name': 'Felis catus'}
# - Zwraca instancję klasy na której została wykonana
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `dict.get()`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> from types import NoneType
>>> assert isclass(Animal)
>>> assert isclass(Cat)
>>> assert isclass(Dog)
>>> CAT = {'english_name': 'Cat', 'latin_name': 'Felis catus'}
>>> cat = Cat.from_dict(CAT)
>>> assert type(cat.english_name) is str
>>> assert type(cat.latin_name) is str
>>> assert cat.english_name == 'Cat'
>>> assert cat.latin_name == 'Felis catus'
>>> DOG = {'english_name': 'Dog', 'latin_name': 'Canis familiaris'}
>>> dog = Dog.from_dict(DOG)
>>> assert type(dog.english_name) is str
>>> assert type(dog.latin_name) is str
>>> assert dog.english_name == 'Dog'
>>> assert dog.latin_name == 'Canis familiaris'
>>> PLATYPUS = {'english_name': 'Platypus'}
>>> platypus = Platypus.from_dict(PLATYPUS)
>>> assert type(platypus.english_name) is str
>>> assert type(platypus.latin_name) is NoneType
>>> assert platypus.english_name == 'Platypus'
>>> assert platypus.latin_name is None
"""
class Animal:
def __init__(self, english_name, latin_name):
self.english_name = english_name
self.latin_name = latin_name
# parameter: `data: dict[str,str]`
# example: {'english_name': 'Cat', 'latin_name': 'Felis catus'}
# hint: Platypus has `latin_name` field empty (None)
# return: instance of a class on which was called
# type: Callable[[type[Self], dict[str,str]], Self]
def from_dict():
...
class Cat(Animal):
pass
class Dog(Animal):
pass
class Platypus(Animal):
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromJson
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3
# %% English
# 1. Define class `Movie` with:
# - Field `title: str`
# - Field `director: str`
# - Method `from_json()`
# 2. Method `from_json()`:
# - Parameter `data: str`, example: '{"title":"Martian","director":"Ridley Scott"}'
# - Returns instance of a class on which was called
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Movie` z:
# - Polem `title: str`
# - Polem `director: str`
# - Metodą `from_json()`
# 2. Metoda `from_json()`:
# - Parametr `data: str`, przykład: '{"title":"Martian","director":"Ridley Scott"}'
# - Zwraca instancję klasy na której została wykonana
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `json.loads()`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(Movie)
>>> assert isclass(ScienceFiction)
>>> assert isclass(History)
>>> assert isclass(Adventure)
>>> MARTIAN = '{"title":"Martian","director":"Ridley Scott"}'
>>> martian = ScienceFiction.from_json(MARTIAN)
>>> assert type(martian.title) is str
>>> assert type(martian.director) is str
>>> assert martian.title == 'Martian'
>>> assert martian.director == 'Ridley Scott'
>>> DUNE = '{"title":"Dune","director":"Denis Villeneuve"}'
>>> dune = Adventure.from_json(DUNE)
>>> assert type(dune.title) is str
>>> assert type(dune.director) is str
>>> assert dune.title == 'Dune'
>>> assert dune.director == 'Denis Villeneuve'
>>> RIGHT_STUFF = '{"title":"The Right Stuff","director":"Philip Kaufman"}'
>>> right_stuff = History.from_json(RIGHT_STUFF)
>>> assert type(right_stuff.title) is str
>>> assert type(right_stuff.director) is str
>>> assert right_stuff.title == 'The Right Stuff'
>>> assert right_stuff.director == 'Philip Kaufman'
"""
import json
class Movie:
def __init__(self, title, director):
self.title = title
self.director = director
# parameter: `data: str`
# example: '{"title":"Martian","director":"Ridley Scott"}'
# return: instance of a class on which was called
# type: Callable[[type[Self], str], Self]
def from_json():
...
class ScienceFiction(Movie):
pass
class History(Movie):
pass
class Adventure(Movie):
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromDatetime
# - Difficulty: easy
# - Lines: 10
# - Minutes: 5
# %% English
# 1. Define class `DateTime` with:
# - Field `year: int`
# - Field `month: int`
# - Field `day: int`
# - Field `hour: int`
# - Field `minute: int`
# - Field `second: int`
# - Field `tzinfo: str`
# - Method `from_datetime()`
# 2. Method `from_datetime()`:
# - Parameter `dt: datetime`, example: datetime(1969, 7, 21, 2, 56, 15)
# - Returns instance of a class on which was called
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `DateTime` z:
# - Polem `year: int`
# - Polem `month: int`
# - Polem `day: int`
# - Polem `hour: int`
# - Polem `minute: int`
# - Polem `second: int`
# - Polem `tzinfo: str`
# - Metodą `from_datetime()`
# 2. Metoda `from_datetime()`:
# - Parametr `dt: datetime`, przykład: datetime(1969, 7, 21, 2, 56, 15)
# - Zwraca instancję klasy na której została wykonana
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `zoneinfo.ZoneInfo()`
# - `datetime.datetime()`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(DateTime)
>>> assert isclass(UTC)
>>> assert isclass(CET)
>>> assert isclass(CEST)
>>> data = datetime(1969, 7, 21, 2, 56, 15)
>>> utc = UTC.from_datetime(data)
>>> assert type(utc.year) is int
>>> assert type(utc.month) is int
>>> assert type(utc.day) is int
>>> assert type(utc.hour) is int
>>> assert type(utc.minute) is int
>>> assert type(utc.second) is int
>>> assert type(utc.tzinfo) is ZoneInfo
>>> assert utc.year == 1969
>>> assert utc.month == 7
>>> assert utc.day == 21
>>> assert utc.hour == 2
>>> assert utc.minute == 56
>>> assert utc.second == 15
>>> assert utc.tzinfo == ZoneInfo('Etc/UTC')
>>> data = datetime(1969, 7, 21, 2, 56, 15)
>>> cet = CET.from_datetime(data)
>>> assert type(cet.year) is int
>>> assert type(cet.month) is int
>>> assert type(cet.day) is int
>>> assert type(cet.hour) is int
>>> assert type(cet.minute) is int
>>> assert type(cet.second) is int
>>> assert type(cet.tzinfo) is ZoneInfo
>>> assert cet.year == 1969
>>> assert cet.month == 7
>>> assert cet.day == 21
>>> assert cet.hour == 2
>>> assert cet.minute == 56
>>> assert cet.second == 15
>>> assert cet.tzinfo == ZoneInfo('Etc/GMT-1')
>>> data = datetime(1969, 7, 21, 2, 56, 15)
>>> cest = CEST.from_datetime(data)
>>> assert type(cest.year) is int
>>> assert type(cest.month) is int
>>> assert type(cest.day) is int
>>> assert type(cest.hour) is int
>>> assert type(cest.minute) is int
>>> assert type(cest.second) is int
>>> assert type(cest.tzinfo) is ZoneInfo
>>> assert cest.year == 1969
>>> assert cest.month == 7
>>> assert cest.day == 21
>>> assert cest.hour == 2
>>> assert cest.minute == 56
>>> assert cest.second == 15
>>> assert cest.tzinfo == ZoneInfo('Etc/GMT-2')
"""
from datetime import datetime
from zoneinfo import ZoneInfo
class DateTime:
tzname: str
def __init__(self, year, month, day, hour, minute, second, tzinfo):
self.year = year
self.month = month
self.day = day
self.hour = hour
self.minute = minute
self.second = second
self.tzinfo = tzinfo
# parameter: `dt: datetime`
# example: datetime(1969, 7, 21, 2, 56, 15)
# hint: tzinfo = ZoneInfo(cls.tzname)
# return: instance of a class on which was called
# type: Callable[[type[Self], datetime], Self]
def from_datetime():
...
class UTC(DateTime):
tzname = 'Etc/UTC'
class CET(DateTime):
tzname = 'Etc/GMT-1'
class CEST(DateTime):
tzname = 'Etc/GMT-2'
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromTimestamp
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3
# %% English
# 1. Define class `Timezone` with:
# - Field `timesamp: int`
# - Field `tzname: str`
# - Method `from_timestamp()`
# 2. Method `from_timestamp()`:
# - Parameter `timestamp: int`, example: 1234567890
# - Returns instance of a class on which was called
# 3. Use `timezone.utc` as a parameter to `datetime.fromtimestamp()`
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Timezone` z:
# - Polem `timesamp: int`
# - Polem `tzname: str`
# - Metodą `from_timestamp()`
# 2. Metoda `from_timestamp()`:
# - Parametr `timestamp: int`, przykład: 1234567890
# - Zwraca instancję klasy na której została wykonana
# 3. Użyj `timezone.utc` jako parametr do `datetime.fromtimestamp()`
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `datetime.fromtimestamp(tz=timezone.utc)`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(Timezone)
>>> assert isclass(CET)
>>> assert isclass(CEST)
>>> cet = CET.from_timestamp(1234567890)
>>> assert type(cet.tzname) is str
>>> assert type(cet.dt) is datetime
>>> assert cet.tzname == 'Central European Time'
>>> assert cet.dt == datetime(2009, 2, 13, 23, 31, 30, tzinfo=timezone.utc)
>>> cest = CEST.from_timestamp(1234567890)
>>> assert type(cest.tzname) is str
>>> assert type(cest.dt) is datetime
>>> assert cest.tzname == 'Central European Summer Time'
>>> assert cest.dt == datetime(2009, 2, 13, 23, 31, 30, tzinfo=timezone.utc)
"""
from datetime import datetime, timezone
class Timezone:
tzname: str
dt: datetime
def __init__(self, dt):
self.dt = dt
# hint: use datetime.fromtimestamp with tz=timezone.utc
# parameter: `timestamp: int`
# return: instance of a class on which was called
# type: Callable[[type[Timezone], int], Timezone]
def from_timestamp():
...
class CET(Timezone):
tzname = 'Central European Time'
class CEST(Timezone):
tzname = 'Central European Summer Time'
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod FromToml
# - Difficulty: easy
# - Lines: 6
# - Minutes: 8
# %% English
# 1. Define class `Character` with:
# - Field `character_class: str`
# - Field `race: str`
# - Field `alignment: str`
# - Field `strength: int`
# - Field `dexterity: int`
# - Field `constitution: int`
# - Field `intelligence: int`
# - Field `wisdom: int`
# - Field `charisma: int`
# - Method `from_toml()`
# 2. Method `from_toml()`:
# - Parameter `filename: str`, example: 'myfile.toml'
# - Parameter `name: str`, example: 'Sarevok'
# - Returns instance of a class on which was called
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Character` z:
# - Polem `character_class: str`
# - Polem `race: str`
# - Polem `alignment: str`
# - Polem `strength: int`
# - Polem `dexterity: int`
# - Polem `constitution: int`
# - Polem `intelligence: int`
# - Polem `wisdom: int`
# - Polem `charisma: int`
# - Metodą `from_toml()`
# 2. Metoda `from_toml()`:
# - Parametr `filename: str`, przykład: 'myfile.toml'
# - Parametr `name: str`, przykład: 'Sarevok'
# - Zwraca instancję klasy na której została wykonana
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `open(filename, mode='rb')`
# - `import tomllib`
# - `tomllib.load()`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from os import remove
>>> from inspect import isclass
>>> assert isclass(Character)
>>> assert isclass(Fighter)
>>> assert isclass(WildMage)
>>> assert isclass(Ranger)
>>> assert isclass(Thief)
>>> DATA = b'''
... [Imoen]
... character_class = 'Thief'
... race = 'Human'
... alignment = 'Neutral Good'
... strength = 9
... dexterity = 18
... constitution = 16
... intelligence = 17
... wisdom = 11
... charisma = 16
...
... [Minsc]
... character_class = 'Ranger'
... race = 'Human'
... alignment = 'Neutral Good'
... strength = 18
... dexterity = 15
... constitution = 15
... intelligence = 8
... wisdom = 6
... charisma = 9
...
... [Neera]
... character_class = 'Wild Mage'
... race = 'Half-elf'
... alignment = 'Chaotic Neutral'
... strength = 11
... dexterity = 17
... constitution = 14
... intelligence = 17
... wisdom = 10
... charisma = 11
...
... [Sarevok]
... character_class = 'Fighter'
... race = 'Human'
... alignment = 'Chaotic Evil'
... strength = 18
... dexterity = 17
... constitution = 18
... intelligence = 17
... wisdom = 10
... charisma = 15
... '''
>>>
>>> with open(FILE, mode='wb') as file:
... file.write(DATA)
683
>>> imoen = Thief.from_toml(FILE, 'Imoen')
>>> assert type(imoen.character_class) is str
>>> assert type(imoen.race) is str
>>> assert type(imoen.alignment) is str
>>> assert type(imoen.stats) is Stats
>>> assert imoen.character_class == 'Thief'
>>> assert imoen.race == 'Human'
>>> assert imoen.alignment == 'Neutral Good'
>>> assert imoen.stats == Stats(strength=9, dexterity=18,
... constitution=16, intelligence=17,
... wisdom=11, charisma=16)
>>> minsc = Ranger.from_toml(FILE, 'Minsc')
>>> assert type(minsc.character_class) is str
>>> assert type(minsc.race) is str
>>> assert type(minsc.alignment) is str
>>> assert type(minsc.stats) is Stats
>>> assert minsc.character_class == 'Ranger'
>>> assert minsc.race == 'Human'
>>> assert minsc.alignment == 'Neutral Good'
>>> assert minsc.stats == Stats(strength=18, dexterity=15,
... constitution=15, intelligence=8,
... wisdom=6, charisma=9)
>>> neera = WildMage.from_toml(FILE, 'Neera')
>>> assert type(neera.character_class) is str
>>> assert type(neera.race) is str
>>> assert type(neera.alignment) is str
>>> assert type(neera.stats) is Stats
>>> assert neera.character_class == 'Wild Mage'
>>> assert neera.race == 'Half-elf'
>>> assert neera.alignment == 'Chaotic Neutral'
>>> assert neera.stats == Stats(strength=11, dexterity=17,
... constitution=14, intelligence=17,
... wisdom=10, charisma=11)
>>> sarevok = Fighter.from_toml(FILE, 'Sarevok')
>>> assert type(sarevok.character_class) is str
>>> assert type(sarevok.race) is str
>>> assert type(sarevok.alignment) is str
>>> assert type(sarevok.stats) is Stats
>>> assert sarevok.character_class == 'Fighter'
>>> assert sarevok.race == 'Human'
>>> assert sarevok.alignment == 'Chaotic Evil'
>>> assert sarevok.stats == Stats(strength=18, dexterity=17,
... constitution=18, intelligence=17,
... wisdom=10, charisma=15)
>>> remove(FILE)
"""
import tomllib
from dataclasses import dataclass
from typing import NamedTuple
FILE = '_temporary.toml'
class Stats(NamedTuple):
strength: int
dexterity: int
constitution: int
intelligence: int
wisdom: int
charisma: int
@dataclass
class Character:
character_class: str
race: str
alignment: str
strength: int
dexterity: int
constitution: int
intelligence: int
wisdom: int
charisma: int
@property
def stats(self) -> Stats:
return Stats(
strength=self.strength,
dexterity=self.dexterity,
constitution=self.constitution,
intelligence=self.intelligence,
wisdom=self.wisdom,
charisma=self.charisma)
# parameter: `filename: str`, example: 'myfile.toml'
# parameter: `name: str`, example: 'Sarevok'
# hint: open file and read TOML content, parse it for given `name`
# return: instance of a class on which was called
# type: Callable[[type[Self], str, str], Self]
def from_toml():
...
class Fighter(Character):
pass
class WildMage(Character):
pass
class Ranger(Character):
pass
class Thief(Character):
pass
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP MethodClassmethod CSVMixin
# - Difficulty: medium
# - Lines: 4
# - Minutes: 5
# %% English
# 1. To class `CSVMixin` add methods:
# - `to_csv(self) -> str`
# - `from_csv(self, text: str) -> 'User' | 'Admin'`
# 2. `CSVMixin.to_csv()` should return attribute values separated with coma
# 3. `CSVMixin.from_csv()` should return instance of a class on which it was called
# 4. Use `@classmethod` decorator in proper place
# 5. Run doctests - all must succeed
# %% Polish
# 1. Do klasy `CSVMixin` dodaj metody:
# - `to_csv(self) -> str`
# - `from_csv(self, text: str) -> 'User' | 'Admin'`
# 2. `CSVMixin.to_csv()` powinna zwracać wartości atrybutów klasy rozdzielone po przecinku
# 3. `CSVMixin.from_csv()` powinna zwracać instancje klasy na której została wywołana
# 4. Użyj dekoratora `@classmethod` w odpowiednim miejscu
# 5. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `CSVMixin.to_csv()` should add newline `\n` at the end of line
# - `CSVMixin.from_csv()` should remove newline `\n` at the end of line
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> from os import remove
>>> mark = User('Mark', 'Watney')
>>> melissa = Admin('Melissa', 'Lewis')
>>> mark.to_csv()
'Mark,Watney\\n'
>>> melissa.to_csv()
'Melissa,Lewis\\n'
>>> with open('_temporary.txt', mode='wt') as file:
... data = mark.to_csv() + melissa.to_csv()
... file.writelines(data)
>>> result = []
>>> with open('_temporary.txt', mode='rt') as file:
... lines = file.readlines()
... result += [User.from_csv(lines[0])]
... result += [Admin.from_csv(lines[1])]
>>> result # doctest: +NORMALIZE_WHITESPACE
[User(firstname='Mark', lastname='Watney'),
Admin(firstname='Melissa', lastname='Lewis')]
>>> remove('_temporary.txt')
TODO: dodać test sprawdzający czy linia kończy się newline
"""
from dataclasses import dataclass
class CSVMixin:
# return: attribute values separated with coma
# type: Callable[[Self], str]
def to_csv(self) -> str:
...
# return: instance of a class on which it was called
# type: Callable[[type[Self], str], Self]
@classmethod
def from_csv(cls, line: str):
...
@dataclass
class User(CSVMixin):
firstname: str
lastname: str
@dataclass
class Admin(CSVMixin):
firstname: str
lastname: str