Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Subclassing, Composition, Python, and You

Subclassing, Composition, Python, and You

Hynek Schlawack

October 02, 2024
Tweet

More Decks by Hynek Schlawack

Other Decks in Programming

Transcript

  1. @dataclass class Base: x: int class Sub(Base): y = 0

    def compute(self): self.y += self.x * 2
  2. @dataclass class Base: x: int def cool_new_feature(self): self.y = "LOL"

    class Sub(Base): y = 0 def compute(self): self.y += self.x * 2
  3. @dataclass class A: b: B y: int = 0 def

    compute(self): self.y += self.b.x * 2 Composition
  4. @dataclass class A: b: B y: int = 0 def

    compute(self): self.y += self.b.x * 2 Composition
  5. class A: def method(self): ... class B: def method(self): ...

    class C: def method(self): ... class You(A, B, C): def foo(self): self.method()
  6. class A: def method(self): self.bar() class B(A): def bar(self): ...

    class C(B): def bar(self): ... class You(C): def foo(self): self.method()
  7. class A: def method(self): self.bar() class B(A): def bar(self): ...

    class C(B): def bar(self): ... class You(C): def foo(self): self.method()
  8. class AbstractTrackingRepository(abc.ABC): def __init__(self): self.seen = set() @abc.abstractmethod def _add(self,

    product): raise NotImplementedError @abc.abstractmethod def _get(self, sku): raise NotImplementedError
  9. class AbstractTrackingRepository(abc.ABC): def __init__(self): self.seen = set() def add_product(self, product):

    self._add(product) self.seen.add(product) @abc.abstractmethod def _add(self, product): raise NotImplementedError @abc.abstractmethod def _get(self, sku): raise NotImplementedError
  10. class AbstractTrackingRepository(abc.ABC): def __init__(self): self.seen = set() def add_product(self, product):

    self._add(product) self.seen.add(product) def get_by_sku(self, sku): if product := self._get(sku): self.seen.add(product) return product @abc.abstractmethod def _add(self, product): raise NotImplementedError @abc.abstractmethod def _get(self, sku): raise NotImplementedError
  11. class FakeTrackingRepository(AbstractTrackingRepository): def __init__(self, products): super().__init__() self._products = set(products) def

    _add(self, product): self._products.add(product) def _get(self, sku): return next(( p for p in self._products if p.sku == sku), None)
  12. class FakeTrackingRepository(AbstractTrackingRepository): def __init__(self, products): super().__init__() self._products = set(products) def

    _add(self, product): self._products.add(product) def _get(self, sku): return next(( p for p in self._products if p.sku == sku), None)
  13. class AbstractTrackingRepository(abc.ABC): def __init__(self): self.seen = set() def add_product(self, product):

    self._add(product) self.seen.add(product) def get_by_sku(self, sku): if product := self._get(sku): self.seen.add(product) return product @abc.abstractmethod def _add(self, product): raise NotImplementedError @abc.abstractmethod def _get(self, sku): raise NotImplementedError
  14. class AbstractTrackingRepository(abc.ABC): def __init__(self): self.seen = set() def add_product(self, product):

    self._add(product) self.seen.add(product) def get_by_sku(self, sku): if product := self._get(sku): self.seen.add(product) return product @abc.abstractmethod def _add(self, product): raise NotImplementedError @abc.abstractmethod def _get(self, sku): raise NotImplementedError
  15. class AbstractRepository(abc.ABC): @abc.abstractmethod def add(self, product: Product) -> None: ...

    @abc.abstractmethod def get(self, sku: str) -> Product | None: ...
  16. class AbstractRepository(abc.ABC): @abc.abstractmethod def add(self, product: Product) -> None: ...

    @abc.abstractmethod def get(self, sku: str) -> Product | None: ... Nominal subtyping
  17. class AbstractRepository(abc.ABC): @abc.abstractmethod def add(self, product: Product) -> None: ...

    @abc.abstractmethod def get(self, sku: str) -> Product | None: ... Nominal subtyping (metaclass=abc.ABCMeta):
  18. @typing.runtime_checkable class Repository(typing.Protocol): def add(self, product: Product) -> None: ...

    def get(self, sku: str) -> Product | None: ... Structural subtyping
  19. class FakeRepository: def __init__(self, products): self._products = set(products) def add(self,

    product: Product ) -> None: self._products.add(product) def get(self, sku: str) -> Product | None: return next(( p for p in self._products if p.sku == sku), None)
  20. class FakeRepository: def __init__(self, products): self._products = set(products) def add(self,

    product: Product ) -> None: self._products.add(product) def get(self, sku: str) -> Product | None: return next(( p for p in self._products if p.sku == sku), None)
  21. class FakeRepository(AbstractRepository): def __init__(self, products): self._products = set(products) def add(self,

    product: Product ) -> None: self._products.add(product) def get(self, sku: str) -> Product | None: return next(( p for p in self._products if p.sku == sku), None)
  22. class FakeRepository: def __init__(self, products): self._products = set(products) def add(self,

    product: Product ) -> None: self._products.add(product) def get(self, sku: str) -> Product | None: return next(( p for p in self._products if p.sku == sku), None) AbstractRepository.register(FakeRepository)
  23. @AbstractRepository.register class FakeRepository: def __init__(self, products): self._products = set(products) def

    add(self, product: Product ) -> None: self._products.add(product) def get(self, sku: str) -> Product | None: return next(( p for p in self._products if p.sku == sku), None)
  24. @dataclass class TrackingRepository: _repo: Repository seen: set[Product] = field(default_factory=set) def

    add_product(self, product: Product) -> None: self._repo.add(product) self.seen.add(product)
  25. @dataclass class TrackingRepository: _repo: Repository seen: set[Product] = field(default_factory=set) def

    add_product(self, product: Product) -> None: self._repo.add(product) self.seen.add(product) def get_by_sku(self, sku: str) -> Product | None: if product := self._repo.get(sku): self.seen.add(product) return product
  26. @dataclass class TrackingRepository: _repo: Repository seen: set[Product] = field(default_factory=set) def

    add_product(self, product): self._repo.add(product) self.seen.add(product) def get_by_sku(self, sku): if product := self._repo.get(sku): self.seen.add(product) return product class FakeRepository: def __init__(self, products): self._products = set(products) def add(self, product): self._products.add(product) def get(self, sku): return next( ( p for p in self._products if p.sku == sku ), None )
  27. Namespaces are one honking great idea – let’s do more

    of those! — Uncle Timmy, Zen of Python, PEP 20
  28. def decorator(cls): cls.a = 42 return cls @decorator class C:

    pass assert hasattr(C, "a") == C = decorator(_C)
  29. ['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',

    '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__match_args__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'pub_date', 'question_text'] dir(Question("Hi!", today())) – dataclass Edition
  30. ['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',

    '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__match_args__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'pub_date', 'question_text'] dir(Question("Hi!", today())) – dataclass Edition 32
  31. ['DoesNotExist', 'MultipleObjectsReturned', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',

    '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_check_column_name_clashes', '_check_constraints', '_check_default_pk', '_check_field_name_clashes', '_check_fields', '_check_id_field', '_check_index_together', '_check_indexes', '_check_local_fields', '_check_long_column_names', '_check_m2m_through_same_relationship', '_check_managers', '_check_model', '_check_model_name_db_lookup_clashes', '_check_ordering', '_check_property_name_related_field_accessor_clashes', '_check_single_primary_key', '_check_swappable', '_check_unique_together', '_do_insert', '_do_update', '_get_FIELD_display', '_get_expr_references', '_get_field_value_map', '_get_next_or_previous_by_FIELD', '_get_next_or_previous_in_order', '_get_pk_val', '_get_unique_checks', '_meta', '_perform_date_checks', '_perform_unique_checks', '_prepare_related_fields_for_save', '_save_parents', '_save_table', '_set_pk_val', '_state', 'check', 'choice_set', 'clean', 'clean_fields', 'date_error_message', 'delete', 'from_db', 'full_clean', 'get_constraints', 'get_deferred_fields', 'get_next_by_pub_date', 'get_previous_by_pub_date', 'id', 'objects', 'pk', 'prepare_database_save', 'pub_date', 'question_text', 'refresh_from_db', 'save', 'save_base', 'serializable_value', 'unique_error_message', 'validate_constraints', 'validate_unique'] dir(Question("Hi!", today())) – ORM Edition
  32. ['DoesNotExist', 'MultipleObjectsReturned', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',

    '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_check_column_name_clashes', '_check_constraints', '_check_default_pk', '_check_field_name_clashes', '_check_fields', '_check_id_field', '_check_index_together', '_check_indexes', '_check_local_fields', '_check_long_column_names', '_check_m2m_through_same_relationship', '_check_managers', '_check_model', '_check_model_name_db_lookup_clashes', '_check_ordering', '_check_property_name_related_field_accessor_clashes', '_check_single_primary_key', '_check_swappable', '_check_unique_together', '_do_insert', '_do_update', '_get_FIELD_display', '_get_expr_references', '_get_field_value_map', '_get_next_or_previous_by_FIELD', '_get_next_or_previous_in_order', '_get_pk_val', '_get_unique_checks', '_meta', '_perform_date_checks', '_perform_unique_checks', '_prepare_related_fields_for_save', '_save_parents', '_save_table', '_set_pk_val', '_state', 'check', 'choice_set', 'clean', 'clean_fields', 'date_error_message', 'delete', 'from_db', 'full_clean', 'get_constraints', 'get_deferred_fields', 'get_next_by_pub_date', 'get_previous_by_pub_date', 'id', 'objects', 'pk', 'prepare_database_save', 'pub_date', 'question_text', 'refresh_from_db', 'save', 'save_base', 'serializable_value', 'unique_error_message', 'validate_constraints', 'validate_unique'] dir(Question("Hi!", today())) – ORM Edition 91
  33. class QuestionsRepository: conn: sqlalchemy.engine.Connection def get(self, id: int) -> Question:

    row := self.conn.execute( select( tables.questions.c.pub_date ).where( tables.questions.c.id == id ) ).one() return Question(id=id, pub_date=row.pub_date)
  34. class QuestionsRepository: conn: sqlalchemy.engine.Connection def get(self, id: int) -> Question:

    row := self.conn.execute( select( tables.questions.c.pub_date ).where( tables.questions.c.id == id ) ).one() return Question(id=id, pub_date=row.pub_date)
  35. class QuestionsRepository: conn: sqlalchemy.engine.Connection def get(self, id: int) -> Question:

    row := self.conn.execute( select( tables.questions.c.pub_date ).where( tables.questions.c.id == id ) ).one() return Question(id=id, pub_date=row.pub_date)
  36. ['DoesNotExist', 'MultipleObjectsReturned', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',

    '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_check_column_name_clashes', '_check_constraints', '_check_default_pk', '_check_field_name_clashes', '_check_fields', '_check_id_field', '_check_index_together', '_check_indexes', '_check_local_fields', '_check_long_column_names', '_check_m2m_through_same_relationship', '_check_managers', '_check_model', '_check_model_name_db_lookup_clashes', '_check_ordering', '_check_property_name_related_field_accessor_clashes', '_check_single_primary_key', '_check_swappable', '_check_unique_together', '_do_insert', '_do_update', '_get_FIELD_display', '_get_expr_references', '_get_field_value_map', '_get_next_or_previous_by_FIELD', '_get_next_or_previous_in_order', '_get_pk_val', '_get_unique_checks', '_meta', '_perform_date_checks', '_perform_unique_checks', '_prepare_related_fields_for_save', '_save_parents', '_save_table', '_set_pk_val', '_state', 'check', 'choice_set', 'clean', 'clean_fields', 'date_error_message', 'delete', 'from_db', 'full_clean', 'get_constraints', 'get_deferred_fields', 'get_next_by_pub_date', 'get_previous_by_pub_date', 'id', 'objects', 'pk', 'prepare_database_save', 'pub_date', 'question_text', 'refresh_from_db', 'save', 'save_base', 'serializable_value', 'unique_error_message', 'validate_constraints', 'validate_unique']
  37. Programs are meant to be read by humans and only

    incidentally for computers to execute. — Abelson & Sussman, SICP Seth Michael Larson Working on urllib3 full-time for one week
  38. ? class A: b: B def other_method(): ... def method(self):

    self.other_method() self.b.even_other_method() class B: a: A def even_other_method(): ... def method(self): self.a.other_method() self.even_other_method() or
  39. class A: b: B def other_method(): ... def method(self): self.other_method()

    self.b.even_other_method() class B: a: A def even_other_method(): ... def method(self): self.a.other_method() self.even_other_method() or def function(a, b): a.other_method() b.even_other_method()
  40. class Mailbox: id: UUID addr: str pwd: str class Forwarder:

    id: UUID addr: str targets: list[str] E-Mail Addresses
  41. class Mailbox: id: UUID addr: str pwd: str class Forwarder:

    id: UUID addr: str targets: list[str] E-Mail Addresses EmailAddr = Forwarder | Mailbox
  42. class Mailbox: id: UUID addr: str pwd: str class Forwarder:

    id: UUID addr: str targets: list[str] E-Mail Addresses EmailAddr = Forwarder | Mailbox
  43. class AddrType(enum.Enum): MAILBOX = "mailbox" FORWARDER = "forwarder" class EmailAddr:

    type: AddrType id: UUID addr: str Create one class, make fields optional
  44. class AddrType(enum.Enum): MAILBOX = "mailbox" FORWARDER = "forwarder" class EmailAddr:

    type: AddrType id: UUID addr: str # Only useful if type == AddrType.MAILBOX pwd: str | None # Only useful if type == AddrType.FORWARDER target: list[str] | None Create one class, make fields optional
  45. class AddrType(enum.Enum): MAILBOX = "mailbox" FORWARDER = "forwarder" class EmailAddr:

    type: AddrType id: UUID addr: str # Only useful if type == AddrType.MAILBOX pwd: str | None # Only useful if type == AddrType.FORWARDER target: list[str] | None Create one class, make fields optional
  46. class EmailAddr: id: UUID addr: str class Mailbox: email: EmailAddr

    pwd: str class Forwarder: email: EmailAddr targets: list[str] Composition
  47. class EmailAddr: id: UUID addr: str class Mailbox: email: EmailAddr

    pwd: str class Forwarder: email: EmailAddr targets: list[str] Composition
  48. class EmailAddr: id: UUID addr: str class Mailbox: email: EmailAddr

    pwd: str class Forwarder: email: EmailAddr targets: list[str] Composition
  49. class EmailAddr: id: UUID addr: str class Mailbox(EmailAddr): pwd: str

    class Forwarder(EmailAddr): targets: list[str] Create a Common Base Class, Then Specialize
  50. class EmailAddr: id: UUID addr: str class Mailbox(EmailAddr): pwd: str

    class Forwarder(EmailAddr): targets: list[str] Create a Common Base Class, Then Specialize
  51. type EmailAddr struct { addr string } type Mailbox struct

    { EmailAddr pwd string } func main() { mbox := Mailbox{EmailAddr{"[email protected]"}, "a hash"} fmt.Println(mbox.addr) }
  52. type EmailAddr struct { addr string } type Mailbox struct

    { EmailAddr pwd string } func main() { mbox := Mailbox{EmailAddr{"[email protected]"}, "a hash"} fmt.Println(mbox.addr) }
  53. I never allow myself to have an opinion on anything

    that I don’t know the other side’s argument better than they do. — Charlie Munger