6.4. Classmethod¶
6.4.1. Rationale¶
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
6.4.2. 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
Traceback (most recent call last):
TypeError: __init__() missing 2 required positional arguments: 'firstname' and 'lastname'
>>>
>>> User(None, None).from_json(DATA)
User(firstname='Jan', 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
User(firstname='Jan', 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> print(User.from_json(DATA))
User(firstname='Jan', 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
User(firstname='Jan', 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
User(firstname='Jan', 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
Traceback (most recent call last):
TypeError: from_json() missing 1 required positional argument: 'data'
>>>
>>> User().from_json(DATA)
User(firstname='Jan', 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> User.from_json(DATA)
User(firstname='Jan', lastname='Twardowski')
6.4.3. Use Cases¶
>>> 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": "Jan", "lastname": "Twardowski"}'
>>>
>>> Guest.from_json(DATA)
Guest(firstname='Jan', lastname='Twardowski')
>>>
>>> Admin.from_json(DATA)
Admin(firstname='Jan', lastname='Twardowski')
>>> 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
6.4.4. Assignments¶
"""
* Assignment: Protocol Classmethod CSV
* Complexity: easy
* Lines of code: 5 lines
* Time: 13 min
English:
1. Use data from "Given" section (see below)
2. To class `CSVMixin` add methods:
a. `to_csv(self) -> str`
b. `from_csv(self, text: str) -> Union['Astronaut', 'Cosmonaut']`
3. `CSVMixin.to_csv()` should return attribute values separated with coma
4. `CSVMixin.from_csv()` should return instance of a class on which it was called
5. Use `@classmethod` decorator in proper place
6. All tests must pass
7. Compare result with "Tests" section (see below)
Polish:
1. Użyj danych z sekcji "Given" (patrz poniżej)
2. Do klasy `CSVMixin` dodaj metody:
a. `to_csv(self) -> str`
b. `from_csv(self, text: str) -> Union['Astronaut', 'Cosmonaut']`
3. `CSVMixin.to_csv()` powinna zwracać wartości atrybutów klasy rozdzielone po przecinku
4. `CSVMixin.from_csv()` powinna zwracać instancje klasy na której została wywołana
5. Użyj dekoratora `@classmethod` w odpowiednim miejscu
6. Wszystkie testy muszą przejść
7. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
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:
>>> from dataclasses import dataclass
>>> @dataclass
... class Astronaut(CSVMixin):
... firstname: str
... lastname: str
...
>>> @dataclass
... class Cosmonaut(CSVMixin):
... firstname: str
... lastname: str
>>> mark = Astronaut('Mark', 'Watney')
>>> jan = Cosmonaut('Jan', 'Twardowski')
>>> mark.to_csv()
'Mark,Watney\\n'
>>> jan.to_csv()
'Jan,Twardowski\\n'
>>> with open('_temporary.txt', mode='wt') as file:
... data = mark.to_csv() + jan.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='Jan', lastname='Twardowski')]
>>> from os import remove
>>> remove('_temporary.txt')
"""
# Given
from typing import Union
class CSVMixin:
def to_csv(self) -> str:
...
@classmethod
def from_csv(cls, line: str) -> Union['Astronaut', 'Cosmonaut']:
...