Oleg's Web Log

Stochastic records on Information Security and IT in general

Private attributes in Python

Category: Software Development

Written on

Python does not natively support complete hiding of class attributes similar to that provided by the private keyword in languages like C# or C++. The whole idea of hiding attributes goes against Python's philosophy of being responsible and avoiding messing with attributes marked as private or internal with an underscore prefix.

Python trusts you to stay away from attributes that begin with an underscore and even provides a limited support for attribute hiding for attributes whose name start with two underscores.

Attribute names starting with double underscore are mangled into some not-so-readable names so that you can't access them with the name they were referred to in the class definition (still, Python documentation notes that it is there to avoid name clashes with names defined by subclasses, not for hiding). But knowing standard mangling rules it is still possible to access those attributes with mangled names:

class Person():
     def __init__(self, age):
             self.__age = age
     def getAge(self):
             return self.__age

p = Person(20)
p.getAge()     # => 20
p.__age        # => AttributeError: 'Person' object has no attribute '__age'   
dir(p)         # => ['_Person__age', ...] <= Peeking the mangled name
p._Person__age # => 20

With the above said there is at least one way to create private attributes completely inaccessible from outside. And, as you might have guessed, it goes against Python's best practices.

The approach I describe below was gleaned from a wonderful book by Nicholas C. Zakas The Principles of Object-Oriented JavaScript. Yes, many patterns stay the same across different programming languages =). In the book the author leverages what in JavaScript is called immediately invoked function expression (IIFE) and closures.

Consider the following Python class declaration:

class Person:
    def __init__(self, age):
        self.age = age;

    def growOlder(self):
        self.age = self.age + 1;

    def getAge(self):
        return self.age;

As it is now, it will be possible to directly read and modify the age attribute on a class instance. But let's assume that you want to conceal the age attribute so that it can only be read with getAge() method, or incremented with growOlder() method.

The whole trick can be achieved by moving class definition inside of a function, declaring class "private" attributes as function local variables and returning a new instance of the class as a result of the function:

def Person(age):
    _age = [0];

    class Person:
        def __init__(self, age):
            _age[0] = age;

        def growOlder(self):
            _age[0] = _age[0] + 1;

        def getAge(self):
            return _age[0];

    return Person(age);

You can then use the above function as a class constructor:

p1 = Person(10)
p1.getAge()    # => 10
p1.growOlder()
p1.getAge()    # => 11
p1._age        # => AttributeError: 'Person' object has no attribute '_age'

Because what's declared in the function stays local to that function (not visible or accessible from outside) and because of how closures work, you get the sought for functionality.

Note that Python list was used to hold the private attribute. This is because of how scoping rules in Python work. In Python, you can read an immutable variable defined in an outer scope, but an attempt to assign a new value to it will result in creation of a new variable in the current scope. To overcome this issue one has to use mutable objects, of which the list is one.

One another mutable object usable for this purpose is dictionary. You could define your private attributes as elements of a local dictionary variable (e.g. private = {"age": 0}) and then access them through the class definition (e.g. private["age"] = private["age"] + 1).

My favorite way though is to define an empty class (which is a reference mutable type) and then use it as private attributes holder:

def Person(age):
    class _: pass

    class Person:
        def __init__(self, age):
            _.age = age;

        def growOlder(self):
            _.age = _.age + 1;

        def getAge(self):
            return _.age;

    return Person(age);

And one more variation on the theme that uses the nonlocal keyword to write to outer scope immutable variables. It is a bit verbose and only works for Python 3:

# Python 3 only
def Person(age):
    _age = 0;

    class Person:

        def __init__(self, age):
            nonlocal _age;
            _age = age;

        def growOlder(self):
            nonlocal _age;
            _age = _age + 1;

        def getAge(self):
            nonlocal _age;
            return _age;

    return Person(age);
comments powered by Disqus