This module provides support for easy addition of private attributes inside your custom objects, which are totally unreachable from outside the class definition, as in C++ 'private' clause.
Project description
private-attrs
This little library, consisting of a single module, provides support for easy addition of truly private attributes inside classes, which are totally unreachable from outside the class definition, as in C++ private clause.
Table of contents
Installation
Use the package manager pip to install private-attrs.
pip3 install private-attrs
Usage
This is a simple schema on how a custom class could use private attributes.
from private_attrs import PrivateAttrs
def MyClass():
p = PrivateAttrs()
class MyClass:
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'
return MyClass
MyClass = MyClass() # override the function definition
As you can see, we first need to define the class inside a function. Outside that class, but inside the function scope, we
instantiate a PrivateAttrs
object.
Now, inside MyClass
, 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 the register_instance()
function.
Finally we return MyClass
and we override the function definition.
A simple example
Let's now dive into a more complete example:
from private_attrs import PrivateAttrs
def Person():
p = PrivateAttrs()
class Person:
def __init__(self, name, social_security_number):
p.register_instance(self)
self.name = name
p.ssn = social_security_number
@property
def ssn(self):
return p.ssn
def __eq__(self, other):
return self.ssn == other.ssn
def __hash__(self):
return hash(self.ssn)
def __str__(self):
return f"{self.name} - {self.ssn}"
def __del__(self):
p.delete(self)
Person.__qualname__ = 'Person'
return Person
Person = 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=True
If we are working with the Python multiprocessing
library 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 the PrivateAttrs
object with the argument proxy = True
.
Let's see an example:
from private_attrs import PrivateAttrs
def Person():
p = PrivateAttrs(proxy=True)
class Person:
def __init__(self, name, social_security_number):
p.register_instance(self)
self.name = name
p.cell_phones = p.manager.list()
p.ssn = social_security_number
@property
def ssn(self):
return p.ssn
@property
def cell_phones(self):
return tuple(p.cell_phones)
def add_cell_phone(self, phone):
p.cell_phones.append(phone)
def __str__(self):
return f"{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)
return state
def __setstate__(self, state):
private = state.pop('private')
p.setstate(private, self)
self.__dict__ = state
Person.__qualname__ = 'Person'
return Person
Person = 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 the Manager
class, such as
lists or dictionaries. Fortunately, there is an attached manager object in the PrivateAttrs
class 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 this Person
class, it's impossible for other processes to modify the public
name
attribute and make that change visible for the rest. This is because this attribute has not been instantiated with
Manager.Value()
nor inside a Manager.Namespace()
or similar.
One possible workaround if you don't want to use the mentioned methods for storing shared simple attributes like str
or
int
would be to make them private and then make a getter (@property
) and a setter for each one. So the former
Person
class would look like this:
class Person:
def __init__(self, name, social_security_number):
p.register_instance(self)
p.name = name
p.cell_phones = p.manager.list()
p.ssn = social_security_number
@property
def name(self):
return p.name
@name.setter
def name(self, name):
p.name = name
@property
def ssn(self):
return p.ssn
@property
def cell_phones(self):
return tuple(p.cell_phones)
def add_cell_phone(self, phone):
p.cell_phones.append(phone)
def __str__(self):
return f"{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)
return state
def __setstate__(self, state):
private = state.pop('private')
p.setstate(self, private)
self.__dict__ = state
A word about compiling
If you plan to compile your program or library using a tool like Cython, Nuitka or similar, you should know that
private-attrs uses the Python inspect
module 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:
class Person:
@property
def name(self):
return p.name
@name.setter
def name(self, name):
p.name = name
For this
class Person:
@property
def name(self):
return p.get_private_attr(self, 'name')
@name.setter
def name(self, name):
p.set_private_attr(self, 'name', name)
Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
License
This library is licensed under the GNU General Public License v3 or later (GPLv3+)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file private-attrs-1.0.1.tar.gz
.
File metadata
- Download URL: private-attrs-1.0.1.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.62.3 importlib-metadata/4.10.1 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.7.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 617be45f9900fed9830d1c4a9c976d6694864cec17c1efe13af1ff7f134959c4 |
|
MD5 | b26edc1db9ead1ee84057815b47333f6 |
|
BLAKE2b-256 | dc3a9d8f55ed61215c3454174942d73614c68fc5aacf7c3bd1d87e4a4614351a |
File details
Details for the file private_attrs-1.0.1-py3-none-any.whl
.
File metadata
- Download URL: private_attrs-1.0.1-py3-none-any.whl
- Upload date:
- Size: 18.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.62.3 importlib-metadata/4.10.1 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.7.3
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 624be3ae4974371b17fef009b37d3f842246dc977a66e9a18adb7e851800b42e |
|
MD5 | 373f9aec0502f148b3bb444751aa076c |
|
BLAKE2b-256 | b187df296f7a0b671395a471b28604f534c8199dba8c4fbd080ca2f073056cd8 |