7.4. Classmethod¶
7.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
7.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:
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')
7.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
@classmethod
def parse(cls, text):
result = ...
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
7.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']:
...