Compclasses
Like dataclasses, but for composition.
As the Gang of Four (probably) said:
favor object composition over class inheritance.
However when we use composition in Python, we cannot access methods directly from the composed class, and we either re-define these methods from scratch, or access them using chaining.
This codebase wants to address such issue and make it easy to do so, by delegating the desired methods of a class to its attributes.
Table of Content
Alpha Notice
This codebase is experimental and is working for my use cases. It is very probable that there are cases not covered and for which everything breaks. If you find them, please feel free to open an issue in the issue page of the repo.
Installation
compclasses is published as a Python package on pypi, and it can be installed with pip, ideally by using a virtual environment.
From a terminal it is possible to install it with:
python -m pip install compclasses
Getting Started
Let's suppose we have the following 3 classes, Foo, Bar and Baz:
FooandBarare independent from one another;Bazget initialized with two class attributes (_foo,_bar) which are instances of the other two classes.
class Foo:
"""Foo class"""
def __init__(self, value: int):
"""foo init"""
self._value = value
def get_value(self):
"""get value attribute"""
return self._value
def hello(self, name: str) -> str:
"""Method with argument"""
return f"Hello {name}, this is Foo!"
class Bar:
"""Bar class"""
b: int = 1
def __len__(self) -> int:
"""Custom len method"""
return 42
class Baz:
"""Baz class"""
def __init__(self, foo: Foo, bar: Bar):
self._foo = foo
self._bar = bar
Now let's instantiate them and try see how we would access the "inner" attributes/methods:
foo = Foo(123)
bar = Bar()
baz = Baz(foo, bar)
baz._foo.get_value() # -> 123
baz._foo.hello("GitHub") # -> "Hello GitHub, this is Foo!"
baz._bar.__len__() # -> 42
len(baz) # -> TypeError: object of type 'Baz' has no len()
compclass (decorator)
Using the compclass decorator we can forward the methods that we want to the Baz class from its attributes at definition time:
from compclasses import compclass
delegates = {
"_foo": ( "get_value", "hello"),
"_bar": ("__len__", )
}
@compclass(delegates=delegates)
class Baz:
"""Baz class"""
def __init__(self, foo: Foo, bar: Bar):
self._foo = foo
self._bar = bar
baz = Baz(foo, bar)
baz.get_value() # -> 123
baz.hello("GitHub") # -> "Hello GitHub, this is Foo!"
len(baz) # -> 42
We can see how now we can access the methods directly.
Remark that in the delegates dictionary, we have that:
- the keys correspond to the attribute names in the
Bazclass; - each value should be an iterable of string corresponding to methods/attributes present in the class instance associated to the key-attribute.
The compclass decorator adds each attribute and method as a property attribute, callable as
self.<attr_name> instead of self.<delegatee_cls>.<attr_name>
CompclassMeta (metaclass)
The equivalent, but alternative, way of doing it is by using the CompclassMeta metaclass when you define the class.
from compclasses import CompclassMeta
delegates = {
"_foo": ( "get_value", "hello"),
"_bar": ("__len__", )
}
class Baz(metaclass=CompclassMeta, delegates=delegates):
"""Baz class"""
def __init__(self, foo: Foo, bar: Bar):
self._foo = foo
self._bar = bar
baz = Baz(foo, bar)
baz.get_value() # -> 123
baz.hello("GitHub") # -> "Hello GitHub, this is Foo!"
len(baz) # -> 42
As you can see the syntax is nearly one-to-one with the compclass decorator, and the resulting behaviour is exactly the same!
Advanced usage
Instead of using an iterable in the delegates dictionary, we suggest to use a delegatee instance as a value.
This will yield more flexibility and features when decide to forward class attributes and methods.
Check the dedicated documentation page to get a better understanding and see more examples on how delegatee can be used, its pros and cons.
Why Composition (TL;DR)
Overall, composition is a more flexible and transparent way to reuse code and design classes in Python. It allows to build classes that are customized to your specific needs, and it promotes code reuse and modularity.
A more detailed explanation is present in the documentation page.
Feedbacks
Any feedback, improvement/enhancement or issue is welcome in the issue page of the repo.
Contributing
Please refer to the contributing guidelines in the documentation site.
Inspiration
This projects is inspired by the forwardable library, a "utility for easy object composition via delegation".
However I was looking for both more flexibility and more features. In particular:
- a clear separation between class definition and method forwarding;
- a validation step to make sure that changing something from the component doesn't break the class;
- the possibility to forward all the methods/attributes of a given component with a single instruction;
- the chance of adding prefix and/or suffix for each component;
Please refer to Beyond the basics page to see example usages.
Licence
The project has a MIT Licence
Metadata
Release files for compclasses 0.4.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| compclasses-0.4.2.tar.gz | 58.5 kB | Without provenance | Details for compclasses-0.4.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| compclasses-0.4.2-py3-none-any.whl | Python 3 | none | any | Details for compclasses-0.4.2-py3-none-any.whl |
Total release size: 70.1 kB
Release files / compclasses-0.4.2.tar.gz
| Download URL | compclasses-0.4.2.tar.gz |
|---|---|
| Size | 58.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
88ba26a75ca686a1d02b7b345a3fad9afa8b3ac143396889ebdd74991ce67162
|
|
BLAKE2b-256 checksum How to use checksums |
d3fc62df3245cd05e4f3f3ae509532b36fc77cfc78f90021bdc7ef5182a5ecea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-httpx/0.25.0
|
Release files / compclasses-0.4.2-py3-none-any.whl
| Download URL | compclasses-0.4.2-py3-none-any.whl |
|---|---|
| Size | 11.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e42f116b1ee7d466b251a5ce2f42e396c646729633f6ae87e361aa781b797358
|
|
BLAKE2b-256 checksum How to use checksums |
5255982e966364fe417d55870b6b6fad35b137ec1a08d00ffb1c691944d91666
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-httpx/0.25.0
|
Security for compclasses 0.4.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.