* Data Classes
>>> from collections import namedtuple
>>>
>>> InventoryItem = namedtuple("InventoryItem",
... ["name", "unit_price", "quantity"])
...
>>> item = InventoryItem("hammer", 10.49, 12)
>>> total_cost = item.unit_price * item.quantity
>>> total_cost
125.88
>>> from typing import NamedTuple
>>>
>>> class InventoryItem(NamedTuple):
... name: str
... unit_price: float
... quantity: int = 0
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem("hammer", 10.49, 12)
>>> item.total_cost
125.88
...
... def __new__(cls, name, unit_price, quantity=0, related_items=None):
... if related_items is None:
... related_items = []
... return super().__new__(cls, name, unit_price, quantity, related_items)
...
$ python smartnamedtuple.py
Traceback (most recent call last):
File "smartnamedtuple.py", line 4, in <module>
class InventoryItem(NamedTuple):
File "/usr/lib64/python3.6/typing.py", line 2163, in __new__
raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
AttributeError: Cannot overwrite NamedTuple attribute __new__
>>> from typing import NamedTuple, List
>>>
>>> class InventoryItem(NamedTuple):
... name: str
... unit_price: float
... quantity: int = 0
... related_items: List = None
...
... def __real_new__(cls, name, unit_price, quantity=0, related_items=None):
... if related_items is None:
... related_items = []
... return tuple.__new__(cls, [name, unit_price, quantity, related_items])
...
>>> InventoryItem.__new__ = InventoryItem.__real_new__
>>>
>>> item = InventoryItem("hammer", 10.49, 12)
>>> item.related_items
[]
$ pip install dataclasses
>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class InventoryItem:
... name: str
... unit_price: float
... quantity: int = 0
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem("hammers", 10.49, 12)
>>> item.total_cost
125.88
>>> from dataclasses import dataclass, field
>>> from typing import List
>>>
>>> @dataclass
... class InventoryItem:
... name: str
... unit_price: float
... quantity: int = 0
... related_items: List = field(default_factory=list)
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem("hammers", 10.49, 12)
>>> item.related_items
[]
>>> from dataclasses import dataclass, field
>>> from typing import List
>>>
>>> @dataclass
... class InventoryItem:
... name: str
... unit_price: float
... quantity: int = 0
... related_items: List = field(default_factory=list)
... total_cost: float = field(init=False)
...
... def __post_init__(self):
... self.total_cost = self.unit_price * self.quantity
...
>>> item = InventoryItem("hammers", 10.49, 12)
>>> item.total_cost
125.88
>>> from dataclasses import dataclass, field
>>> from typing import List
>>>
>>> @dataclass(frozen=True)
... class InventoryItem:
... name: str
... unit_price: float
... quantity: int = 0
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem("hammers", 10.49, 12)
>>> item.unit_price += 1
Traceback (most recent call last):
...
FrozenInstanceError: cannot assign to field 'unit_price'
>>> from dataclasses import dataclass, field
>>> from typing import List
>>>
>>> @dataclass(frozen=True)
... class InventoryItem:
... name: str
... unit_price: float
... quantity: int = 0
... related_items: List = field(default_factory=list)
... total_cost: float = field(init=False)
...
... def __post_init__(self):
... total_cost = self.unit_price * self.quantity
... object.__setattr__(self, "total_cost", total_cost)
...
>>> item = InventoryItem("hammers", 10.49, 12)
>>> item.total_cost
125.88
>>> from dataclasses import dataclass, field
>>> from typing import List
>>>
>>> @dataclass(unsafe_hash=True)
... class InventoryItem:
... name: str
... unit_price: float
... related_items: List = field(default_factory=list,
... hash=False)
...
>>> item1 = InventoryItem("hammer", 10.4)
>>> item2 = InventoryItem("hammer", 10.4)
>>> item3 = InventoryItem("spanner", 8.9)
>>> {item1, item2, item3}
{InventoryItem(name='hammer', ...),
InventoryItem(name='spanner', ...)}
from dataclasses import dataclass, field
@dataclass
class InventoryItem:
name: str
unit_price: float
quantity: int = 0
related_items: List = field(default_factory=list)
item = InventoryItem("what", "are", "you", "doing?")
wtf.py
$ mypy wtf.py
wtf.py:11: error: Argument 2 to "InventoryItem" has incompatible type "str"; expected "float"
wtf.py:11: error: Argument 3 to "InventoryItem" has incompatible type "str"; expected "int"
wtf.py:11: error: Argument 4 to "InventoryItem" has incompatible type "str"; expected "List[Any]"
>>> from dataclasses import field, make_dataclass
>>> from typing import List
>>>
>>> make_dataclass("InventoryItem",
... [("name", str),
... ("unit_price", float),
... ("quantity", int, 0),
... ("related_items", List,
... field(default_factory=list))])
...
types.InventoryItem
>>> item = _("hammers", 10.49, 12)
>>> item.unit_price
10.49
>>> item.related_items
[]
>>> from dataclasses import asdict, astuple
>>>
>>> item = InventoryItem("hammers", 10.49, 12)
>>>
>>> asdict(item)
{'name': 'hammers', 'unit_price': 10.49, 'quantity': 12,
'related_items': []}
>>>
>>> astuple(item)
('hammers', 10.49, 12, [])
(again)
$ pip install attrs
>>> import attr
>>>
>>> @attr.s
... class InventoryItem:
... name: str = attr.ib()
... unit_price: float = attr.ib()
... quantity: int = attr.ib(default=0)
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem("hammers", 10.49, 12)
>>> item.total_cost
125.88
>>> @attr.s
... class InventoryItem:
... price: float = attr.ib()
...
... @price.validator
... def check(self, attribute, value):
... if value > 9000:
... raise ValueError("Dude? That's too expensive!")
...
>>> @attr.s
... class InventoryItem:
... price: float = attr.ib(
... validators=attr.validators.instance_of(float)
... )
...
>>> @attr.s
... class InventoryItem:
... price: Any = attr.ib(converter=float)
...
>>> InventoryItem("10.49")
InventoryItem(price=10.49)
>>> from pymongo import MongoClient
>>>
>>> client = MongoClient()
>>> database = client.amazing
>>>
>>> item_dict = database.inventory_items.find_one()
>>> item_dict
{'name': 'hammers', 'price': 10.49, 'quantity': 10}
>>> class InventoryItem(dict):
...
... @property
... def total_cost(self) -> float:
... return self["unit_price"] * self['quantity']
...
>>> item = InventoryItem(item_dict)
>>> item.total_cost
125.88
>>> from types import SimpleNamespace
>>>
>>> class InventoryItem(SimpleNamespace):
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem(**item_dict)
>>> item.total_cost
125.88
>>> from copy import copy
>>>
>>> copy(item.__dict__)
{'name': 'hammers', 'price': 10.49, 'quantity': 10}
>>> from box import Box
>>>
>>> class InventoryItem(Box):
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem(item_dict)
>>> item.name
'hammer'
>>> item.total_cost
125.88
>>> item.to_dict()
{'name': 'hammer', 'unit_price': 10.49, 'quantity': 12}
51K instantiations + item access
$ pip install Thingy
>>> class InventoryItem(Thingy):
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> item = InventoryItem(item_dict)
>>> item.total_cost
125.88
>>> item.view()
{'name': 'hammer', 'unit_price': 10.49, 'quantity': 12}
>>> class InventoryItem(Thingy):
...
... @property
... def total_cost(self) -> float:
... return self.unit_price * self.quantity
...
>>> InventoryItem.add_view("with total", include="total_cost")
>>>
>>> item = InventoryItem(item_dict)
>>> item.view("with total")
{'name': 'hammer', 'unit_price': 10.49, 'quantity': 12,
'total_cost': 125.88}
(whatever module they're from)