Package: compclasses
CompclassesLikedataclasses, 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, bydelegatingthe desired methods of a class to its attributes.Documentation|Source CodeTable of ContentCompclassesTable of ContentAlpha NoticeInstallationGetting Startedcompclass (decorator)CompclassMeta (metaclass)Advanced usageWhy Composition (TL;DR)FeedbacksContributingInspirationLicenceAlpha NoticeThis 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 theissue pageof the repo.Installationcompclassesis published as a Python package onpypi, and it can be installed with pip, ideally by using a virtual environment.From a terminal it is possible to install it with:python-mpipinstallcompclassesGetting StartedLet's suppose we have the following 3 classes,Foo,BarandBaz:FooandBarare independent from one another;Bazget initialized with two class attributes (_foo,_bar) which are instances of the other two classes.classFoo:"""Foo class"""def__init__(self,value:int):"""foo init"""self._value=valuedefget_value(self):"""get value attribute"""returnself._valuedefhello(self,name:str)->str:"""Method with argument"""returnf"Hello{name}, this is Foo!"classBar:"""Bar class"""b:int=1def__len__(self)->int:"""Custom len method"""return42classBaz:"""Baz class"""def__init__(self,foo:Foo,bar:Bar):self._foo=fooself._bar=barNow 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()# -> 123baz._foo.hello("GitHub")# -> "Hello GitHub, this is Foo!"baz._bar.__len__()# -> 42len(baz)# -> TypeError: object of type 'Baz' has no len()compclass (decorator)Using thecompclassdecorator we canforwardthe methods that we want to theBazclass from its attributes at definition time:fromcompclassesimportcompclassdelegates={"_foo":("get_value","hello"),"_bar":("__len__",)}@compclass(delegates=delegates)classBaz:"""Baz class"""def__init__(self,foo:Foo,bar:Bar):self._foo=fooself._bar=barbaz=Baz(foo,bar)baz.get_value()# -> 123baz.hello("GitHub")# -> "Hello GitHub, this is Foo!"len(baz)# -> 42We can see how now we can access the methods directly.Remark that in thedelegatesdictionary, we have that:the keys correspond to the attribute names in theBazclass;each value should be an iterable of string corresponding to methods/attributes present in the class instance associated to the key-attribute.Thecompclassdecorator adds each attribute and method as aproperty attribute, callable asself.<attr_name>instead ofself.<delegatee_cls>.<attr_name>CompclassMeta (metaclass)The equivalent, but alternative, way of doing it is by using theCompclassMetametaclass when you define the class.fromcompclassesimportCompclassMetadelegates={"_foo":("get_value","hello"),"_bar":("__len__",)}classBaz(metaclass=CompclassMeta,delegates=delegates):"""Baz class"""def__init__(self,foo:Foo,bar:Bar):self._foo=fooself._bar=barbaz=Baz(foo,bar)baz.get_value()# -> 123baz.hello("GitHub")# -> "Hello GitHub, this is Foo!"len(baz)# -> 42As you can see the syntax is nearly one-to-one with thecompclassdecorator, and the resulting behaviour is exactly the same!Advanced usageInstead of using an iterable in thedelegatesdictionary, we suggest to use adelegateeinstance as a value.This will yield more flexibility and features when decide to forward class attributes and methods.Check the dedicateddocumentation pageto get a better understanding and see more examples on howdelegateecan 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 thedocumentation page.FeedbacksAny feedback, improvement/enhancement or issue is welcome in theissue pageof the repo.ContributingPlease refer to thecontributing guidelinesin the documentation site.InspirationThis projects is inspired by theforwardablelibrary, 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 toBeyond the basicspage to see example usages.LicenceThe project has aMIT Licence