6.8. OOP Method Classmethod¶
Using class as namespace
Will pass class as a first argument
self
is not required
Dynamic methods:
>>> class MyClass:
... def mymethod(self):
... pass
Static methods:
>>> class MyClass:
... @staticmethod
... def mymethod():
... pass
Class methods:
>>> class MyClass:
... @classmethod
... def mymethod(cls):
... pass
6.8.1. Manifestation¶
>>> 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": "Mark", "lastname": "Watney"}'
>>> result = User.from_json(data)
>>>
>>> print(result)
User(firstname='Mark', lastname='Watney')
>>> 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": "Mark", "lastname": "Watney"}'
>>> result = User.from_json(data)
>>>
>>> print(result)
User(firstname='Mark', lastname='Watney')
>>> 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": "Mark", "lastname": "Watney"}'
>>> result = User.from_json(data)
>>>
>>> print(result)
User(firstname='Mark', lastname='Watney')
6.8.2. Use Case - 0x01¶
Singleton
>>> class Singleton:
... _instance: object
...
... @classmethod
... def get_instance(cls):
... if not hasattr(cls, '_instance'):
... cls._instance = object.__new__(cls)
... return cls._instance
>>>
>>>
>>> class Astronaut(Singleton):
... pass
>>>
>>> class Cosmonaut(Singleton):
... pass
>>>
>>>
>>> astro = Astronaut.get_instance()
>>> cosmo = Cosmonaut.get_instance()
>>>
>>>
>>> print(astro)
<__main__.Astronaut object at 0x102453ee0>
>>>
>>> print(cosmo)
<__main__.Cosmonaut object at 0x102453ee0>
6.8.3. Use Case - 0x02¶
JSONMixin
>>> 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": "Mark", "lastname": "Watney"}'
>>>
>>> Guest.from_json(data)
Guest(firstname='Mark', lastname='Watney')
>>>
>>> Admin.from_json(data)
Admin(firstname='Mark', lastname='Watney')
6.8.4. Use Case - 0x03¶
Interplanetary time
>>> # myapp/time.py
>>> 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'
>>> # myapp/settings.py
>>> from myapp.time import *
>>>
>>> time = MartianTime
>>> # myapp/usage.py
>>> from myapp.settings import time
>>>
>>> UTC = '1969-07-21T02:53:07Z'
>>>
>>> dt = time.parse(UTC)
>>> print(dt.tzname)
Coordinated Mars Time
6.8.5. Use Case - 0x04¶
Interplanetary time
>>> # myapp/time.py
>>> 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'
>>> # myapp/settings.py
>>>
... import myapp.time
... from myapp.time import *
... from os import getenv
...
... time = getattr(myapp.time, getenv('MISSION_TIME'))
>>> # myapp/usage.py
>>>
... from myapp.settings import time
...
... UTC = '1969-07-21T02:53:07Z'
...
... dt = time.parse(UTC)
... print(dt.tzname)
Coordinated Mars Time
6.8.6. Use Case - 0x05¶
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class User:
... firstname: str = None
... lastname: str = None
...
... @classmethod
... def from_json(cls, data):
... import json
... data = json.loads(data)
... return cls(**data)
>>>
>>> class Admin(User):
... pass
>>>
>>> class Guest(User):
... pass
>>>
>>>
>>> data = '{"firstname": "Mark", "lastname": "Watney"}'
>>>
>>> Admin.from_json(data)
Admin(firstname='Mark', lastname='Watney')
>>>
>>> Guest.from_json(data)
Guest(firstname='Mark', lastname='Watney')
>>>
>>> User.from_json(data)
User(firstname='Mark', lastname='Watney')
6.8.7. Assignments¶
"""
* Assignment: OOP MethodClassmethod 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` object 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 obiekt typu `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
# Method `convert()` returns instance of a class, which was given
# as an argument with field set `when: datetime`
class Timezone:
tzname: str
when: datetime
class CET(Timezone):
tzname = 'Central European Time'
class CEST(Timezone):
tzname = 'Central European Summer Time'
"""
* Assignment: OOP MethodClassmethod CSV
* Complexity: easy
* Lines of code: 4 lines
* Time: 5 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')
TODO: dodać test sprawdzający czy linia kończy się newline
"""
class CSVMixin:
def to_csv(self) -> str:
...
def from_csv(cls, line: str):
...