In the previous episodes
- basic stuff, syntax
- functions (wrote our cool min function) + functional stuff
- scopes, PEP-8
- strings, bytes
- collections
- classes basics
- decorators, functools
- exceptions, context managers
- iterators, generators, itertools
🏛 Classes again
👇 Class
>>> class Counter:
"""I count. That is all."""
def __init__(self, initial=0): # not a constructor! initializer!
self.value = initial
def increment(self):
self.value += 1
def get(self):
return self.value
>>> c = Counter(42)
>>> c.increment()
>>> c.get()
43
🏛 Classes basics wrap-up
- all attributes are stored in dictionaries
- properties are functions that can be called like attributes
- do not overcomplexify the inheritance
- https://docs.python.org/3/reference/datamodel.html#special-method-names
- decorators can be used too, but later about it
Class decorator
from time import sleep, time
class Timer:
def __init__(self, func):
self.function = func
def __call__(self, *args, **kwargs):
start = time()
result = self.function(*args, **kwargs)
end = time()
print(f"Execution took {end - start}")
return result
>>> @Timer
def some_function(delay):
sleep(delay)
>>> some_function(3)
Execution took 3.003122091293335 seconds
Class and Object Attributes reminder
>>> class Fruit:
... color = 'red'
...
>>> blueberry = Fruit()
>>> Fruit.color
'red'
>>> blueberry.color
'red'
Inheritance reminder
>>> class Car():
... pass
...
>>> class Yugo(Car):
... pass
...
>>> issubclass(Yugo, Car)
True
>>> class Car():
... def exclaim(self):
... print("I'm a Car!")
...
>>> class Yugo(Car):
... pass
Method override (inheritance reminder)
>>> give_me_a_car = Car()
>>> give_me_a_yugo = Yugo()
>>> give_me_a_car.exclaim()
I'm a Car!
>>> give_me_a_yugo.exclaim()
I'm a Car!
That’s it
>>> class Car():
... def exclaim(self):
... print("I'm a Car!")
...
>>> class Yugo(Car):
... def exclaim(self):
... print("I'm a Yugo! Much like a Car.")
...
>>> give_me_a_yugo.exclaim()
I'm a Yugo! Much like a Car.
base stuff class inheritance
class LoggingDict(dict): # ???
...
The right way:
class LoggingDict(UserDict):
def get(self, key, default=None):
if key not in self.data:
logging.info(f"{key} not found!")
return super().get(key, default)
property reminder
>>> class BigDataModel:
_params = []
@property
def params(self):
return self._params
@params.setter
def params(self, new_params):
assert all(map(lambda p: p > 0, new_params))
self._params = new_params
@params.deleter
def params(self):
del self._params
Class methods
- instance
- class
- static
Class methods
>>> class A():
... count = 0
... def __init__(self):
... A.count += 1
... def exclaim(self):
... print("I'm an A!")
... @classmethod
... def kids(cls):
... print("A has", cls.count, "little objects.")
...
>>> easy_a = A()
>>> breezy_a = A()
>>> wheezy_a = A()
>>> A.kids()
A has 3 little objects.
Static methods
>>> class CoyoteWeapon():
... @staticmethod
... def commercial():
... print('This CoyoteWeapon has been brought to you by Acme')
...
>>>
>>> CoyoteWeapon.commercial()
This CoyoteWeapon has been brought to you by Acme
How? The answer is descriptors
>>> class Descr:
... def __get__(self, instance, owner):
... print(instance, owner)
... def __set__(self, instance, value):
... print(instance, value)
... def __delete__(self, instance):
... print(instance)
...
>>> class A:
... attr = Descr()
>>> A.attr
None <class '__main__.A>
>>> A().attr
<__main__.A object at [...]> <class '__main__.A>
>>> instance = A()
>>> instance.attr = 42
<__main__.A object at [...]> 42
Primer
>>> class staticmethod:
... def __init__(self, method):
... self._method = method
...
... def __get__(self, instance, owner):
... return self._method
...
🪄 Magic methods
🤔 equals?
>>> class Word():
... def __init__(self, text):
... self.text = text
...
... def equals(self, word2):
... return self.text.lower() == word2.text.lower()
...
>>> first = Word('ha')
>>> second = Word('HA')
>>> third = Word('eh')
>>> first.equals(second)
True
>>> first.equals(third)
False
🧐 __eq__
>>> class Word():
... def __init__(self, text):
... self.text = text
... def __eq__(self, word2):
... return self.text.lower() == word2.text.lower()
...
https://docs.python.org/3/reference/datamodel.html#special-method-names
🐍 NamedTuple
namedtuple reminder
>>> from collections import namedtuple
>>> Duck = namedtuple('Duck', 'bill tail')
>>> duck = Duck('wide orange', 'long')
>>> duck
Duck(bill='wide orange', tail='long')
>>> duck.bill
'wide orange'
>>> duck.tail
'long'
and from dictionary
>>> parts = {'bill': 'wide orange', 'tail': 'long'}
>>> duck2 = Duck(**parts)
>>> duck2
Duck(bill='wide orange', tail='long')
Why?
>>> duck_dict['color'] = 'green'
>>> duck_dict
{'color': 'green', 'tail': 'long', 'bill': 'wide orange'}
>>> duck.color = 'green'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Duck' object has no attribute 'color'
namedtuple wrap-up
- It looks and acts like an immutable object.
- It is more space and time efficient than objects.
- You can access attributes by using dot notation instead of dictionary-style square brackets.
- You can use it as a dictionary key.
🔥 dataclasses
Typical boilerplate:
>> class TeenyClass():
... def __init__(self, name):
... self.name = name
...
>>> teeny = TeenyClass('itsy')
>>> teeny.name
'itsy'
dataclass helps:
>>> from dataclasses import dataclass
>>> @dataclass
... class TeenyDataClass:
... name: str # types are must-have
...
>>> teeny = TeenyDataClass('bitsy')
>>> teeny.name
'bitsy'
👀 typing!
def concat(a, b):
...
def concat(a: int, b: int) -> str:
return str(a) + str(b)
from typing import List, Tuple
def process(data: List[Tuple[int, str]]) -> None:
do_stuff(data)
More dataclasses…
>>> from dataclasses import dataclass
>>> @dataclass
... class AnimalClass:
... name: str
... habitat: str
... teeth: int = 0
...
>>> snowman = AnimalClass('yeti', 'Himalayas', 46)
>>> duck = AnimalClass(habitat='lake', name='duck')
>>> snowman
AnimalClass(name='yeti', habitat='Himalayas', teeth=46)
>>> duck
AnimalClass(name='duck', habitat='lake', teeth=0)
dataclass deeper: fields
>>> @dataclass
... class Book:
... author: str = field()
... title: str = field()
... isbn: int = field(compare=False)
... renters: List[str] = field(default_factory=list, metadata={"max": 5}, repr=False)
...
... def rent(self, name):
... if len(self.renters) >= 5:
... raise ValueError("5 People Already Rent This Book")
... self.renters.append(name)
...
... def unrent(self, name):
... self.renters.remove(name)
more: https://www.python.org/dev/peps/pep-0557/
🍰 Metaclasses
>>> class Foo:
... pass
...
>>> x = Foo()
>>> type(x)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'>
>>> type(type)
<class 'type'>
>>> class Meta(type):
... def __new__(cls, name, bases, dct):
... x = super().__new__(cls, name, bases, dct)
... x.attr = 100
... return x
>>> class Foo(metaclass=Meta):
... pass
>>> Foo.attr
100
more: https://realpython.com/python-metaclasses/