Package: compose-struct
Yet another namedtuple alternative for Pythoncompose.Structis something like an alternative to namedtuple,attrsand nowdataclassesin Python 3.7.to create a new struct, you simply:classFoo(compose.Struct):bar=...baz='spam'This generates a class like this:classFoo:__slots__='bar','baz'def__init__(self,bar,baz='spam'):self.bar=barself.baz=bazYou can, naturally, implement any other methods you wish.You can also use type annotation syntax for positional arguments:classFoo(compose.Struct):bar:intbaz:str='spam'If thename = ...syntax is used in combination with type annotation
syntax for positional arguments, all positional arguments with
annotations will come before positional arguments without. However, this
should be considered an implementation detail. best practice is to not
mix the two styles. Usetyping.Anyif you are using type
annotations and don’t want one of the arguments to care about type.How’s this different fromattrsanddataclasses? A few ways. Aside
from the use of ellipsis to create positional parameters, another
difference that can be seen here is that everything is based on__slots__, which means your attribute lookup will be faster and your
instances more compact in memory.attrsallows you to use slots, butstructonly uses slots. This means that attributes cannot be
dynamically created. If a class needs private attributes, you may create
additional slots with the usual method of defining__slots__inside
the class body.Another important distinction iscompose.Structdoesn’t define a
bunch of random dunder methods. You get your__init__,__repr__,
andto_dictand that’s it[1]. It is the opinion of the author that
sticking all attributes in a tuple and comparing them usually isnotwhat you want when defining a new type. However, it is still easy to get
more dunder methods, as you will see in the following section.[1]OK, It actually also gives you __getstate__ and __setstate__,
which are required for pickling objects.InterfacesPerhaps the most significant difference between our structs and
alternatives is that we emphasize composition over inheritance. Astructisn’t even able to inherit in the normal way! It’s an
outrage! What about interfaces!? What about polymorphism!? Well, whatcomposeprovides is a simple way to generate pass-through methods to
attributes.fromcomposeimportStruct,ProviderclassListWrapper(Struct):data=Provider('__getitem__','__iter__')metadata=NoneSo this will generate pass-through methods for__getitem__and__iter__to thedataattribute. Certain python keywords and
operators can be used as shorthand for adding dunder methods as well.@structclassListWrapper:data=Provider('[]','for')metadata=NoneHere,[]is shorthand for item access and implements__getitem__,__setitem__and__delitem__.forimplements
the__iter__method. A full list of these abbreviations can be found
below in thePre-Defined Interfacessection.Going even deeper, interfaces can be specified as classes. Wrapper
methods will be created for any method attached to a class which is
given as an argument toProvider. The following code is more or less
equivalent to subclassingcollections.UserList, but no inheritance
is used.fromcollectionsimportabcclassListWrapper(Struct):data=Provider(abc.MutableSequence)metadata=NoneAn instances of this class tested withisinstance(instance, abc.MutableSequence)will returnTruebecause wrapper methods
have been generated onself.datafor all the methods inabc.MutableSequence.Note that ``abc.MutableSequence`` does not
actually provide all of the methods a real list does. If you want ALL
of them, you can use ``Provides(list)``.You cannot implicitly make pass-through methods for__setattr__and__getattribute__by passing in a class that implements them, since
they have some rather strange behaviors. You can, however, pass them
explicitly toProviderto force the issue.  In the case of__setattr__, This invokes special behavior. See__setattr__ hacksfor details.All methods defined with a provider can be overridden in the body of the
class as desired. Methods can also be overridden by other providers.
It’s first-come, first-serve in that case. The Provider you want to
define the methods has to be placedaboveany other interfaces that
implement the same method.Mix-in Classes vs. InheritanceThere is no inheritance with Structs. Because of metaclass magic, a
class that inherits from Struct is not its child. It is always a child
ofobject.Provideris a way to implement pass-through methods
easily. Mix-in classes bind methods from other classes directly to your
class. It doesn’t go through the class hierarchy and rebind everything,
only methods defined directly on the mix-in class. Inheriting from
normal python classes may have unpredictable results.composeprovides one mix-in class:Immutable, which is
implemented like this:classMutablility(Exception):passclassImmutable:def__setattr__(self,attr,value):raiseMutablility("can't set{0}.{1}. type{0}is immutable.".format(self.__class__.__name__,attr,value))It can be used like this:fromcomposeimportStruct,ImmutableclassFoo(Struct,Immutable):bar=...baz=...When an instance ofFoois created, it will not be possible to set
attributes afterwards in the normal way. (Though it is technically
possible if you set it withobject.__setattr__(instance, 'attr', value)). Attempting to dofoo.bar = 7will raise aMutabilityerror.If you need astructto look like a child of another class, I
suggest using theabcmodule to define abstract classes. This allows
classes to look like children for the purposes of type-checking, but
without actually using inheritance.OrderThis is the order of priority for where methods come from:Struct generates a unique__init__method for each class it
creates. This cannot be overriden. Alternative constructors should be
implemented as class methods.methods defined in the body of the struct get next dibs.any attributes defined on your mix-ins will be defined on the class if
they don’t already exist.Only then areProviderattributes allowed to add any methods which
haven’t yet been defined.*argsand**kwargsThough it is not especially recommended, it is possible to implement*argsand**kwargsfor your constructor.>>>fromcomposeimportStruct,args,kwargs>>>classFoo(Struct):...items=args...mapping=kwargs...>>>f=Foo('bar','baz',spam='eggs')>>>fFoo(*items=('bar','baz'),**mapping={'spam':'eggs'})This breaks the principle that the object’s repr can be used to
instantiate an identical instance, but it does at least give the option
and still makes the internal structure of the class transparent. WithProviderparameters, simply pass incompose.argsorcompose.kwargsas arguments the constructor.>>>classMySequence(Struct):...data=Provider('__getitem__','__iter__',args)...>>>s=MySequence('foo','bar','baz')>>>sMySequence(*data=('foo','bar','baz'))>>>foriins:...print(i)...foobarbazCaveatsThis library uses code generation at class-creation time. The intent is
to optimize performance of instances at the cost of slowing class
creation. If you’re dynamically creating huge numbers of classes, usingcompose.Structmight be a bad idea. FYI,namedtupledoes the
same. I haven’t looked at the source forattrstoo much, but I did see
some strings with sourcecode there as well.Pre-Defined InterfacesThis is the code that implements the expansion of interface
abbreviations for dunder methods. Any key in theinterfacesdictionary may be used to implement the corresponding dunder methods on
an attribute with theProvides()constructor.interfaces={'+':'add radd','-':'sub rsub','*':'mul rmul','@':'matmul rmatmul','/':'truediv rtruediv','//':'floordiv rfloordiv','%':'mod rmod','**':'pow rpow','<<':'lshift rlshift','>>':'rshift rrshift','&':'and rand','^':'xor rxor','|':'or ror','~':'invert','==':'eq','!=':'ne','>':'gt','<':'lt','>=':'ge','<=':'le','()':'call','[]':'getitem setitem delitem','.':'get set delete set_name','in':'contains','for':'iter','with':'enter exit','del':'del','await':'await'}interfaces={k:['__%s__'%nforninv.split()]fork,vininterfaces.items()}__setattr__ hacksIf you choose to create an attribute
wrapper for__setattr__, the default will look like this so you
won’t hit a recursion error while accessing pre-defined attributes:def__setattr__(self,attribute,value):try:object.__setattr__(self,attribute,value)exceptAttributeError:setattr(self.wrapped_attribute,attribute,value)If you want to override__setattr__with a more, eh, “exotic”
method, the attributes defined in the class body will be set properly
when the instance is initialized, but will use your method at all other
times,including in other methods, which may break your stuff.