Skip to main content

private-attrs

PyPI PyPI - Downloads PyPI - Python Version PyPI - Status

GitHub code size in bytes GitHub last commit Build Status Maintenance

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

PyPI - License

This library is licensed under the GNU General Public License v3 or later (GPLv3+)

Metadata

Release files for private-attrs 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for private-attrs 1.0.1
File Size Uploaded
private-attrs-1.0.1.tar.gz 18.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for private-attrs 1.0.1
File Interpreter ABI Platform
private_attrs-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 36.7 kB

Release files / private-attrs-1.0.1.tar.gz

Download URL private-attrs-1.0.1.tar.gz
Size 18.1 kB
Tags Source
SHA-256 checksum
How to use checksums
617be45f9900fed9830d1c4a9c976d6694864cec17c1efe13af1ff7f134959c4
BLAKE2b-256 checksum
How to use checksums
dc3a9d8f55ed61215c3454174942d73614c68fc5aacf7c3bd1d87e4a4614351a
Upload date
Uploaded using Trusted Publishing?
What is 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

Release files / private_attrs-1.0.1-py3-none-any.whl

Download URL private_attrs-1.0.1-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
624be3ae4974371b17fef009b37d3f842246dc977a66e9a18adb7e851800b42e
BLAKE2b-256 checksum
How to use checksums
b187df296f7a0b671395a471b28604f534c8199dba8c4fbd080ca2f073056cd8
Upload date
Uploaded using Trusted Publishing?
What is 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

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page