Skip to main content

A oxidized trait system for modern Python

Project description

Find the package here: https://pypi.org/project/OxiTrait/

OxiTrait

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 OxiTraits!

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, and Impls define implementation. Traits must be defined before any Impls which implement them, and Impls must be defined before the Structs that use them.

See examples under examples/.

Traits

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 (always a Struct) or all such concrete classes implementing a given Trait.

from oxitrait import Trait, abstractmethod    

class MyTrait(metaclass=Trait):
    @abstractmethod
    def my_method(self) -> str:
        pass

Structs

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 oxitrait 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

Impls bring together Traits and Structs. They represent the implementation details that satisfy one particular interface.

Why isn't the implementation just all together under the Struct? Organization, mostly. Also, "blanket" Impls can provide implementation for any Struct implementing a given Trait, so 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 metaclass argument.

from oxitrait import Impl

class MyImpl(MyTrait, metaclass=Impl, target="MyStruct"):
    ...

or equivalently,

from oxitrait 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 Impls.

FAQ

This reminds me of another programming language

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.

What doesn't work?

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 let a Struct implement two traits that have the same method name, you can always wrap your class definition in a try block and catch the MultipleImplementationError. Maybe you can find a way to make it work.

Acknowledgements

Acknowledgements

oxitrait is derived from the pytrait project by xrudelis, originally licensed under the Apache License 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oxitrait-0.0.2.tar.gz (19.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oxitrait-0.0.2-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file oxitrait-0.0.2.tar.gz.

File metadata

  • Download URL: oxitrait-0.0.2.tar.gz
  • Upload date:
  • Size: 19.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.14

File hashes

Hashes for oxitrait-0.0.2.tar.gz
Algorithm Hash digest
SHA256 5db53a73f4e1b6b7daa6e44ba1da16ec8ae0bea5abb0371fe927ef3084fbb4e5
MD5 a81f686e23a59b31d4965d51ea692cf7
BLAKE2b-256 e7aa5b25e6f145444f88313e9192e39a6a390c6a75741c04670742bea452a159

See more details on using hashes here.

File details

Details for the file oxitrait-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: oxitrait-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 18.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.14

File hashes

Hashes for oxitrait-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7e02342e77540d40f8e3b96d333d9dfa48d3c98824bd8db80c97a7be0a2b21ee
MD5 922c36f1e16bb6503b4e7efb9cf21942
BLAKE2b-256 735d21940d875a15179f257b4848b81e52d4bb909d30ae59c88b52ad96dfa7c1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page