Package: private-attrs
private-attrsThis little library, consisting of a single module, provides support for easy addition oftruly private attributesinside classes, which are totally unreachable from outside the class definition,as in C++ private clause.Table of contentsInstallationUsageA simple exampleAn example with proxy=TrueA word about compilingContributingLicenseInstallationUse the package managerpipto installprivate-attrs.pip3installprivate-attrsUsageThis is a simple schema on how a custom class could use private attributes.fromprivate_attrsimportPrivateAttrsdefMyClass():p=PrivateAttrs()classMyClass:def__init__(self):p.register_instance(self)# From now on, we'll define our public attrs with 'self.attr' syntax as usual,# and private ones with 'p.attr' or 'p.attr_static'.def__del__(self):p.delete(self)MyClass.__qualname__='MyClass'returnMyClassMyClass=MyClass()# override the function definitionAs you can see, we first need to define the class inside a function. Outside that class, but inside the function scope, we
instantiate aPrivateAttrsobject.Now, insideMyClass, if we plan to have private instance attributes, and not just static ones, it's mandatory to
register, in the__init__()method, the instance by calling theregister_instance()function.Finally we returnMyClassand we override the function definition.A simple exampleLet's now dive into a more complete example:fromprivate_attrsimportPrivateAttrsdefPerson():p=PrivateAttrs()classPerson:def__init__(self,name,social_security_number):p.register_instance(self)self.name=namep.ssn=social_security_number@propertydefssn(self):returnp.ssndef__eq__(self,other):returnself.ssn==other.ssndef__hash__(self):returnhash(self.ssn)def__str__(self):returnf"{self.name}-{self.ssn}"def__del__(self):p.delete(self)Person.__qualname__='Person'returnPersonPerson=Person()Although a person can change their name, surname or even their sex, it's really unlikely (not to say impossible) for someone
to change their social security number (SSN).That's why we store the SSN as a private attribute, safe, unmodifiable, and we can rely on it to compare whether two people
are the same person.An example with proxy=TrueIf we are working with the Pythonmultiprocessinglibrary and we want to create a class with private attributes that are
accessible and modifiable from different running processes (we already know that, unlike threads, processes don't share
memory space), we need to instantiate thePrivateAttrsobject with the argumentproxy = True.Let's see an example:fromprivate_attrsimportPrivateAttrsdefPerson():p=PrivateAttrs(proxy=True)classPerson:def__init__(self,name,social_security_number):p.register_instance(self)self.name=namep.cell_phones=p.manager.list()p.ssn=social_security_number@propertydefssn(self):returnp.ssn@propertydefcell_phones(self):returntuple(p.cell_phones)defadd_cell_phone(self,phone):p.cell_phones.append(phone)def__str__(self):returnf"{self.name}-{self.ssn}-{self.cell_phones}"def__del__(self):p.delete(self)def__getstate__(self):state=dict(self.__dict__)state['private']=p.getstate(self)returnstatedef__setstate__(self,state):private=state.pop('private')p.setstate(private,self)self.__dict__=statePerson.__qualname__='Person'returnPersonPerson=Person()By doing this, all the private attributes that we store are automatically available in all processes, and you can modify
them from anyone.Pay particular attention to certain specific attributes that need to be instantiated using theManagerclass, such as
lists or dictionaries. Fortunately, there is an attached manager object in thePrivateAttrsclass to simplify life for
the programmer.Also be aware of the need to define__getstate__()and__setstate__()magic methods as you see them so the class can be
correctly serialized and deserialized with all its private attributes when shared between processes.You should know that, the way we wrote thisPersonclass, it's impossible for other processes to modify the publicnameattribute and make that change visible for the rest. This is because this attribute has not been instantiated withManager.Value()nor inside aManager.Namespace()or similar.One possible workaround if you don't want to use the mentioned methods for storing shared simple attributes likestrorintwould be to make them private and then make a getter (@property) and a setter for each one. So the formerPersonclass would look like this:classPerson:def__init__(self,name,social_security_number):p.register_instance(self)p.name=namep.cell_phones=p.manager.list()p.ssn=social_security_number@propertydefname(self):returnp.name@name.setterdefname(self,name):p.name=name@propertydefssn(self):returnp.ssn@propertydefcell_phones(self):returntuple(p.cell_phones)defadd_cell_phone(self,phone):p.cell_phones.append(phone)def__str__(self):returnf"{self.name}-{self.ssn}-{self.cell_phones}"def__del__(self):p.delete(self)def__getstate__(self):state=dict(self.__dict__)state['private']=p.getstate(self)returnstatedef__setstate__(self,state):private=state.pop('private')p.setstate(self,private)self.__dict__=stateA word about compilingIf you plan to compile your program or library using a tool like Cython, Nuitka or similar, you should know that
private-attrs uses the Pythoninspectmodule to provide the developer with a friendly interface, which does not
behave as usual when the program is compiled to C. All you have to do to avoid compilation problems is to use the
explicit private attribute getter and setter instead of the implicit declarations, only for non-static private attributes.For example, replace this:classPerson:@propertydefname(self):returnp.name@name.setterdefname(self,name):p.name=nameFor thisclassPerson:@propertydefname(self):returnp.get_private_attr(self,'name')@name.setterdefname(self,name):p.set_private_attr(self,'name',name)ContributingPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.LicenseThis library is licensed under theGNU General Public License v3 or later (GPLv3+)