3.8. OOP Classmethod¶
Using class as namespace
Will pass class as a first argument
self
is not required
>>> class MyClass:
... def mymethod(self):
... pass
>>> class MyClass:
... @staticmethod
... def mymethod():
... pass
>>> class MyClass:
... @classmethod
... def mymethod(cls):
... pass
3.8.1. Example¶
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... def from_json(self, data):
... data = json.loads(data)
... return User(**data)
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: User.from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
Traceback (most recent call last):
TypeError: User.__init__() missing 2 required positional arguments: 'firstname' and 'lastname'
>>>
>>> User(None, None).from_json(DATA)
User(firstname='Pan', lastname='Twardowski')
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class User:
... firstname: str
... lastname: str
...
... @staticmethod
... def from_json(data):
... data = json.loads(data)
... return User(**data)
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
User(firstname='Pan', lastname='Twardowski')
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> class JSONMixin:
... @staticmethod
... def from_json(data):
... data = json.loads(data)
... return User(**data)
>>>
>>>
>>> @dataclass
... class User(JSONMixin):
... firstname: str
... lastname: str
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> print(User.from_json(DATA))
User(firstname='Pan', lastname='Twardowski')
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> class JSONMixin:
... def from_json(self, data):
... data = json.loads(data)
... return User(**data)
>>>
>>>
>>> @dataclass
... class User(JSONMixin):
... firstname: str = None
... lastname: str = None
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: JSONMixin.from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
User(firstname='Pan', lastname='Twardowski')
Trying to use method with self
:
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> class JSONMixin:
... def from_json(self, data):
... data = json.loads(data)
... return self(**data)
>>>
>>>
>>> @dataclass
... class User(JSONMixin):
... firstname: str = None
... lastname: str = None
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: JSONMixin.from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
Traceback (most recent call last):
TypeError: 'User' object is not callable
Trying to use method with self.__init__()
:
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> class JSONMixin:
... def from_json(self, data):
... data = json.loads(data)
... self.__init__(**data)
... return self
>>>
>>>
>>> @dataclass
... class User(JSONMixin):
... firstname: str = None
... lastname: str = None
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: JSONMixin.from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
User(firstname='Pan', lastname='Twardowski')
Trying to use methods self.__new__()
and self.__init__()
:
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> class JSONMixin:
... def from_json(self, data):
... data = json.loads(data)
... instance = object.__new__(type(self))
... instance.__init__(**data)
... return instance
>>>
>>>
>>> @dataclass
... class User(JSONMixin):
... firstname: str = None
... lastname: str = None
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: JSONMixin.from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
User(firstname='Pan', lastname='Twardowski')
>>> 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
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
User(firstname='Pan', lastname='Twardowski')
3.8.2. Use Case - 0x01¶
>>> import json
>>> from dataclasses import dataclass
>>>
>>>
>>> class JSONMixin:
... @classmethod
... def from_json(cls, data):
... data = json.loads(data)
... return cls(**data)
>>>
>>> @dataclass
... class Guest(JSONMixin):
... firstname: str
... lastname: str
>>>
>>> @dataclass
... class Admin(JSONMixin):
... firstname: str
... lastname: str
>>>
>>>
>>> DATA = '{"firstname": "Pan", "lastname": "Twardowski"}'
>>>
>>> Guest.from_json(DATA)
Guest(firstname='Pan', lastname='Twardowski')
>>>
>>> Admin.from_json(DATA)
Admin(firstname='Pan', lastname='Twardowski')
3.8.3. Use Case - 0x02¶
>>> class AbstractTime:
... tzname: str
... tzcode: str
...
... def __init__(self, date, time):
... ...
...
... @classmethod
... def parse(cls, text):
... result = {'date': ..., 'time': ...}
... return cls(**result)
>>>
>>> class MartianTime(AbstractTime):
... tzname = 'Coordinated Mars Time'
... tzcode = 'MTC'
>>>
>>> class LunarTime(AbstractTime):
... tzname = 'Lunar Standard Time'
... tzcode = 'LST'
>>>
>>> class EarthTime(AbstractTime):
... tzname = 'Universal Time Coordinated'
... tzcode = 'UTC'
>>>
>>>
>>> # Settings
>>> time = MartianTime
>>>
>>> # Usage
>>> from settings import time
>>>
>>> UTC = '1969-07-21T02:53:07Z'
>>>
>>> dt = time.parse(UTC)
>>> print(dt.tzname)
Coordinated Mars Time
3.8.4. Assignments¶
"""
* Assignment: OOP Classmethod Time
* Complexity: easy
* Lines of code: 5 lines
* Time: 8 min
English:
1. Define class `Timezone` with:
a. Field `when: datetime`
b. Field `tzname: str`
c. Method `convert()` taking class and `datetime` as arguments
2. Method `convert()` returns instance of a class, which was given
as an argument with field set `when: datetime`
3. Run doctests - all must succeed
Polish:
1. Zdefiniuj klasę `Timezone` z:
a. polem `when: datetime`
b. polem `tzname: str`
c. Metodą `convert()` przyjmującą klasę oraz `datetime` jako argumenty
2. Metoda `convert()` zwraca instancję klasy, którą dostała jako argument
z ustawionym polem `when: datetime`
3. Uruchom doctesty - wszystkie muszą się powieść
Hints:
Tests:
>>> import sys; sys.tracebacklimit = 0
>>> from inspect import isclass
>>> assert isclass(Timezone)
>>> assert isclass(CET)
>>> assert isclass(CEST)
>>> dt = datetime(1969, 7, 21, 2, 56, 15)
>>> cet = CET.convert(dt)
>>> assert cet.tzname == 'Central European Time'
>>> assert cet.when == datetime(1969, 7, 21, 2, 56, 15)
>>> cest = CEST.convert(dt)
>>> assert cest.tzname == 'Central European Summer Time'
>>> assert cest.when == datetime(1969, 7, 21, 2, 56, 15)
"""
from datetime import datetime
class Timezone:
tzname: str
class CET(Timezone):
tzname = 'Central European Time'
class CEST(Timezone):
tzname = 'Central European Summer Time'
"""
* Assignment: OOP Classmethod CSV
* Complexity: easy
* Lines of code: 5 lines
* Time: 13 min
English:
1. To class `CSVMixin` add methods:
a. `to_csv(self) -> str`
b. `from_csv(self, text: str) -> 'Astronaut' | 'Cosmonaut'`
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:
a. `to_csv(self) -> str`
b. `from_csv(self, text: str) -> 'Astronaut' | 'Cosmonaut'`
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
>>> from dataclasses import dataclass
>>> @dataclass
... class Astronaut(CSVMixin):
... firstname: str
... lastname: str
...
>>> @dataclass
... class Cosmonaut(CSVMixin):
... firstname: str
... lastname: str
>>> mark = Astronaut('Mark', 'Watney')
>>> pan = Cosmonaut('Pan', 'Twardowski')
>>> mark.to_csv()
'Mark,Watney\\n'
>>> pan.to_csv()
'Pan,Twardowski\\n'
>>> with open('_temporary.txt', mode='wt') as file:
... data = mark.to_csv() + pan.to_csv()
... file.writelines(data)
>>> result = []
>>> with open('_temporary.txt', mode='rt') as file:
... lines = file.readlines()
... result += [Astronaut.from_csv(lines[0])]
... result += [Cosmonaut.from_csv(lines[1])]
>>> result # doctest: +NORMALIZE_WHITESPACE
[Astronaut(firstname='Mark', lastname='Watney'),
Cosmonaut(firstname='Pan', lastname='Twardowski')]
>>> remove('_temporary.txt')
"""
class CSVMixin:
def to_csv(self) -> str:
...
@classmethod
def from_csv(cls, line: str):
...