Find the package here: https://pypi.org/project/pytrait/
Do you like Python, but think that multiple inheritance is a bit too flexible? Are you looking for a more constrained way to define interfaces and re-use code?
Try using PyTraits!
We provide three metaclasses that aid writing code for shared behavior separately from
concrete types. For the most part,
Traits define interfaces,
Structs define state,
Impls define implementation.
Traits must be defined before any
implement them, and
Impls must be defined before the
Structs that use them.
See examples under
Traits are abstract base classes (ABCs). There's really not much else to say, except
that these ABCs are always implemented in
Impl classes, which themselves have no
abstract methods, but are not concrete classes; instead an
Impl is associated with
another type that it bestows implementation upon. This would be either a concrete class
Struct) or all such concrete classes implementing a given
from pytrait import Trait, abstractmethod class MyTrait(metaclass=Trait): def my_method(self) -> str: pass
Python has dataclasses, and they're great. We're using them internally for our
Structs, so whenever you see
metaclass=Struct, the class is also a dataclass.
Don't get confused with the existing Python module
struct -- that one is lower-case.
from pytrait import Struct class MyStruct(metaclass=Struct): my_message: str = "this is a dataclass" def __post_init__(self): assert my_message == "this is a dataclass"
Impls bring together
Structs. They represent the implementation details
that satisfy one particular interface.
Why isn't the implementation just all together under the
mostly. Also, "blanket"
Impls can provide implementation for any
Impls allow for greater code re-use.
Impls have to indicate which
Structs they bestow implementation upon. You can
follow a strict naming convention, like
ImplMyTraitForMyStruct. This is sufficient.
Or, you can use any name you want so long as you also provide a keyword argument
target="StructName" alongside the
from pytrait import Impl class MyImpl(MyTrait, metaclass=Impl, target="MyStruct"): ...
from pytrait import Impl class ImplMyTraitForMyStruct(MyTrait, metaclass=Impl): ...
This is used to automate the list of implementations for
MyStruct; you don't need to
explicitly list any superclasses of
MyStruct, just based on the
Impl name it will
inherit from all relevant
That is not a question, but you have indeed figured me out. This way of organizing Python code was heavily inspired by the Rust programming language. But beyond being an imitation, it's a testament to how powerful Python is. My philosophy is that if you're not using the flexibility of Python to limit yourself, you're not making use of the full flexibility of Python.
A Struct can't have traits with overlapping method names. Rust can solve this with its "fully qualified syntax", or by type constraints, but Python will by default only resolve to the method from the first listed superclass (see Python's "Method Resolution Order").
I don't think there's any easy way around this, because in Python there's no clear way
to choose which implementation to use based on type annotation. If you really want to
Struct implement two traits that have the same method name, you can always wrap
your class definition in a try block and catch the
you can find a way to make it work.