Sunday, April 3, 2016

Private variables in Python (single and double underscore)

Private variables and methods do not exist in Python. There is no way to strictly restrict access to variables or methods from outside the classes where they were defined. If you come from another object-oriented language such as Java or C++ this may sound strange, to say the least. However, this behavior is intended and there are good reasons for it which, for the sake of brevity, this post will not address.

There are, however, ways to emulate private variables and methods in Python. You can hamper access from outside their classes by applying the following conventions outlined in PEP 8:

Single leading underscore "_" 

Prefixing a variable or method name with "_" (single underscore) indicates its intended for internal use and should not be accessed from outside the class in which it was defined.

class MyClass(variable):

    def __init__(self):
        self._meditation = 'zazen'  # Notice the single leading underscore

This is just a convention and will not actually stop us from accessing the variable or method from outside. However, it's advisable to follow this convention as ignoring it can cause a lot of problems.

>>> obj = MyClass()
>>> obj._meditation
'zazen'

Notice that the _meditation attribute is accessible because it was only hidden by convention.

Caveats:
  • Functions that provide internationalization and localization, used to prefix strings that need translation, are frequently named "_". Avoid confusing this with the "private" variables discussed in this post.

Double leading underscore "__"

Python has a feature called name mangling. When variables are prefixed with "__" (double underscore), their names are automatically changed into _<className><variableName>, so they are not easily accessible from outside their classes.

class MyClass(variable):
    def __init__(self):
        self.__meditation = 'zazen'  # Notice the double leading underscore

Now let's try to access it from outside the class:

>>> obj = MyClass()
>>> obj.__meditation
AttributeError: 'MyClass' variable has no attribute '__meditation'

t.__meditation was not found because its name was automatically changed (mangled) to _MyClass__meditation. We could still access it with the syntax below:

>>> obj._MyClass__meditation
'zazen'

The example above is used to illustrate how "private" variables or methods can be accessed outside their classes. This does not mean we should do it as it will most likely wreak all sorts of havoc.

Another example:

class Glass:
    def __init__(self):
        self.__status = None  # Notice the double leading underscore

    def fill(self):
        self.__status = 'filled'

    def empty(self):
        self.__status = 'empty'

    def print(self):
        print(self.__status)

In the example above, the __status attribute of the Glass class can only be set by the empty() and fill() methods. Let's test it:

>>> g = Glass()
>>> g.print()
None
>>> g.fill()
>>> g.print()
filled
>>> g.empty()
>>> g.print()
empty

In real code, "private" variables like these are used mostly to avoid clashes of names defined within classes with names defined within their subclasses.

Caveats:
  • Python's "magic" variables or attributes are also prefixed with double underscore (e.g., __init__, __import__ or __file__). In this context, the double underscore prefix has no "privatizing" effect. Avoid confusing this with the "private" variables discussed in this post.
Thank you for reading.

No comments:

Post a Comment