Writing code is challenging and time-consuming. It should be no surprise that there is a lot of value in writing code for other people (Ansys is a software company after all!). When writing Python code for reuse by others it is necessary to think about the experience, the UX, of the end user. One thing to avoid is presenting too much clutter. You may only want the end user to have to use one, or maybe two, classes that you've built. The other classes, functions, and variables, and there almost always are others, should be hidden from the user. This is quite a common concept in other languages with things like public and private interfaces, for example, access modifiers in C#. However in Python, nothing is really private. You can't prevent a user from accessing hidden objects, but you can obfuscate them. There are two levels of obfuscation you can employ: private and super-private naming.
Private naming
The simplest and most common way of obfuscating objects in Python is to make them "private". To do this you simply need to add an underscore to the start of the object's name.
from dataclasses import dataclass
@dataclass
class HatchBack:
make: str
model: str
mass: float
_serial_number: int
For example, in the HatchBack
class from earlier parts I have added a private property _serial_number
. This still has to be instantiated when constructing the class, but it is otherwise hidden from the user.
What does "hidden" mean here? - a "hidden" object does not show up in auto-completion, and is not rolled up into auto-generated documentation. They are also unavailable in namespaces when imported with
*
.
"But if a user has to instantiate the class still then they're still going to have to know the serial number?"
Well, will the user have to instantiate the class? Often we build objects for users to use but it is the package that does the building and not the user. We would likely include some additional code that would generate HatchBack
instances for people to use, sidestepping the issue. If someone did want to create their own instance of HatchBack
we could provide a classmethod
for them to do so that gets the serial number from somewhere, which would further hide the property's existence.
Private naming works with any object in Python that can be named.
Super private or double private naming
When working with classes in Python, a user can "subclass" another class to create a new one, often one created by a third-party. For example we could subclass numpy
's ndarray
to make our own version with its own unique set of properties. This is called "inheritance" and is complex enough that it warrants its own article further along in this series. However, for now it is enough to know that inheritance lets a user create a new class that has all the methods and properties of the previous one plus any they wish to add themselves. Sometimes, you won't want this to happen. Sometimes a method needs to be hidden from classes that inherit from it. In these cases we can use "super" private naming.
To make something super-private we just have to prepend two underscores to the name __
. This works with methods and properties on classes.
from dataclasses import dataclass
@dataclass
class HatchBack:
make: str
model: str
mass: float
_serial_number: int
__mileage: int
For example, in the HatchBack
example I have added a new super-private property __mileage
. Super-private names are actually quite sophisticated. At runtime, Python prepends the super-private name with the class name and a single underscore to make sure the name still gets all the standard private benefits. It looks like this.
In [12]: hb = HatchBack('VW', 'Golf', 1000, 123, 100000)
In [13]: hb._HatchBack__mileage
Out[13]: 100000
In [14]: hb
Out[14]: HatchBack(make='VW', model='Golf', mass=1000, _serial_number=123, _HatchBack__mileage=100000)
Crucially Python uses the class name that the method is defined on. In other words it means that one can not subclass HatchBack
and access __mileage
as it is. They would need to know the initial class name and make the swap themselves for it to work. Python will let a particularly determined developer do this if they want to, but super-private naming can make it as clear as possible to the user that they shouldn't be messing with these objects unless they really know what they're doing.
Dunder methods & Part 7
Dunder methods are a little different still. "Dunder" means "double underscore" and refers to objects in Python that have a double underscore at the start and end of their names. For example, __name__
is a particularly common example you may have come across. These have their own set of rules and properties, which will be covered in a later part. However, for the next part we will be revisiting the @property
decorator with private properties in mind to look into building explicit getters and setters.
For a complete look at the articles in this series, have a look at this overview page: Getting started with classes in Python