How to Define Your own Datatype in Python using state of art Descriptors and Meta Class | Advance Python programming
Hello! I’m Soumil Nitin Shah, a Software and Hardware Developer based in New York City. I have completed by Bachelor in Electronic Engineering and my Double master’s in Computer and Electrical Engineering. I Develop Python Based Cross Platform Desktop Application , Webpages , Software, REST API, Database and much more I have more than 2 Years of Experience in Python
Do you want to write your own Datatype ? How amazing it would be if you can write code like this ?
class Stock(Structure): _fields = ['name', 'shares', 'price', "number", "fullname"] name = String('name') shares = Integer('shares') price = Float('price') number = PositiveInterger('number') fullname = Sized('fullname', maxlen=8) def methodA(self): print("Total Shares {}".format(self.shares))
Well you can define your own Datatypes using Descriptors and Metaclasses and also perform validation. for example Datatype positiveInterger will not accept any negative value and similarly the sized datatype does length check How do I implement something like this.
Let me show you entire Code
__Author__ = "Soumil Nitin SHah " __Version__ = "0.0.1" __Email__ = ['soushah@my.bridgeport.edu',"shahsoumil519@gmail.com"] __Github__ = "https://github.com/soumilshah1995" Credits = """ Special Thanks to David Beazley for his Tutorials """ from inspect import Parameter, Signature import datetime class Descriptor(object): def __init__(self, name): self.name = name def __get__(self, instance, owner): print(" __get__ ") return instance.__dict__[self.name] # This will return Value # if instance is not None: # return self # else: # return instance.__dict__[self.name] def __set__(self, instance, value): print(" __set__ ") instance.__dict__[self.name] = value def __delete__(self, instance): print(" __delete__ ") del instance.__dict__[self.name] def __repr__(self): return "Object Descriptor : {}".format(self.name) class Typed(Descriptor): ty = object def __set__(self, instance, value): if not isinstance(value, self.ty): raise TypeError('Expected %s' % self.ty) super().__set__(instance, value) class PositiveInterger(Descriptor): def __set__(self, instance, value): if value < 0: raise ValueError ("Value cannot be less than 0") class Sized(Descriptor): def __init__(self, *args, maxlen, **kwargs): self.maxlen = maxlen super().__init__(*args, **kwargs) def __set__(self, instance, value): if len(value) > self.maxlen: raise ValueError('Too big') super().__set__(instance, value) def make_signature(names): return Signature( Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names) class Integer(Typed): ty = int class Float(Typed): ty = float class String(Typed): ty = str class StructMeta(type): def __new__(cls, clsname, base, clsdict): clsobj = super().__new__(cls, clsname, base, clsdict) sig = make_signature(clsobj._fields) # print("Sig", sig) setattr(clsobj, '__signature__', sig) return clsobj class Structure(metaclass=StructMeta): _fields = [] def __init__(self, *args, **kwargs): # print("**kwargs", kwargs) # print("*Args", args) bound = self.__signature__.bind(*args, **kwargs) # print("bound", bound) for name, val in bound.arguments.items(): # print(name, val) setattr(self, name, val) class Stock(Structure): _fields = ['name', 'shares', 'price', "number", "fullname"] name = String('name') shares = Integer('shares') price = Float('price') number = PositiveInterger('number') fullname = Sized('fullname', maxlen=8) def methodA(self): print("Total Shares {}".format(self.shares)) if __name__ == "__main__": obj = Stock(name="Soumil", shares=12, price=12.2, number =2, fullname='aaaaaa') print("="*55) print(obj.methodA())
Well thats how its Implemented I am using Metaclasses and Descriptors to implement my own Datatype. Further I can add Design Pattern into it. But I think that will be to much for this blog.
Explanation :
- class Descriptors Implement Descriptor logic Dunder Method __get__ , __set__, __delete__
- class Type does Datatype checking
- class Integer, Float , String Inherits from. Type so it can validate the datatype passed by user
- class StructureMeta inherits from type which is a meta class
- class Structure inherits from Metaclasses its job is to set attributes
- finally you have class Stock class which inherits from Structure class
Stock > --- Structure >--- StructMeta>---Type
Typed, Sized Integer, Float etc > --- Descriptors
This is how classes are linked
Thanks for reading this
Special Thanks to David Beazley for his tutorial