A namespace is a strange word that you only hear in esoteric social circles such as those created by computer science professors, developers and anyone hiding away from the real world to generate the next helpful software.
When I first heard that word, I was in my C++ college class I had to take for my mathematics degree. It may as well had been in French.
The code for the basic Hello World program was demonstrated to us:
#include <iostream> // This header defines the cout
using namespace std; // Everything after now refers to the std namespace
int main()
{
cout << "Hello World";
return 0;
}
I’m glad it makes sense now. I haven’t defined the namespace term yet, but I have mentioned in the title they are containers.
A Brief C++ Digression
A namespace is a container that holds identifiers – variables, functions and classes. Outside of this container, they are out-of-scope and don’t exist.
The std namespace holds standard identifiers: They are used and called on often.
One of these identifiers is cout
, which means “character output”. It’s an object that, together with the <<
operator defined in the iostream
header file, outputs to the console.
You can now see why I thought I’d have to visit France to make sense of this class (no pun intended). The word cout in French means cost. But we can differentiate the fact it’s part of the C++ standard library by referring to std
, or the standard namespace!
If we didn’t include the using namespace std
line, we’d have to still specify it’s part of the standard library:
#include <iostream>
int main()
{
std::cout << "Hello World"; // This cout only refers to the std namespace
return 0;
}
Otherwise, cout
isn’t defined and the compiler wouldn’t know where to find it. Another issue would have arisen if we had defined our own cout
variable within a class. Then it would have conflicted with the name used in the iostream
header file definition.
By specifying std::cout
, we are commanding the compiler to refer to the standard namespace when looking for the cout
object.
Namespaces in Python Are More Intuitive
In Python, a namespace is a space, or container, for names contained within a class of objects. Outside of this class, they are inaccessible because they don’t exist.
Consider the detail that subclasses inherit properties and abilities from their superclasses.
- If the properties – or attributes – are not re-defined in the subclass but they are initialized with
super().__init__()
, then the interpreter passes them down from the superclass. - If
super().__init()__
is not called, then the superclass’s namespace is not passed down, and properties and abilities won’t be inherited.
Namespaces for objects contain all of the attributes and abilities that were defined in its class (and possibly superclass). An interesting behavior due to Python’s manner in defining objects in memory is that you can instantiate member variables (as C++ developers would say) outside of the class:
class MyClass:
def __init__(self):
self.x = 1
if __name__ = '__main__':
self.y = 2
Accessing the Current File Namespace
Two objects of the same type can have different values for the same attribute. Each object has its own namespace, but inherits the namespace of the class it was created from:
class MyVariable():
...
first = MyVariable()
second = MyVariable()
# The globals() function returns the current module namespace.
# This program is the current module.
# Therefore, globals() returns the namespace of this program.
# Ref: https://docs.python.org/3/library/functions.html#globals
print(globals())
# Output
'''
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f33c9c19700>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/main.py', '__cached__': None, 'MyVariable': <class '__main__.MyVariable'>, 'first': <__main__.MyVariable object at 0x7f33c9c1ab10>, 'second': <__main__.MyVariable object at 0x7f33c9c19a90>}
'''
Notice the one class namespace and two object namespaces that exist at the end of the output. One is an entry for the class object MyVariable
and the other two are dictionary entries for instances of that class, first
and second
, with each object having its own memory address to store their own values.
The dir()
Function and the __dict__
Attribute for Objects
Accessing the namespace of each object can be done with the dir()
function for objects and the __dict__
attribute of objects.
Keep in mind the following about dir()
and __dict__
:
dir()
returns the list of names including attributes, methods and functions;__dict__
only includes writable attributes specific to the object.- Although
__dict__
is writable, it is not included in the object’s__dict__
because that would create a reference to itself. By design, it excludes itself.
- Although
dir()
returns the namespace as a list, whereas__dict__
returns the namespace as a dictionary- This makes sense because the
dir
command in Linux displays the contents of the current working directory, and__dict__
is short for the dictionary data structure.
- This makes sense because the
dir()
of an object equals thedir()
of its class plus the extra attributes defined in the object’sself()
method- It also includes any instance attributes allocated to memory after object creation (such as
first.z
in the example below)
- It also includes any instance attributes allocated to memory after object creation (such as
Let’s add some attributes and functions to the MyVariable
class. Then, we’ll use dir()
and __dict__
, and study its output:
class MyVariable():
x = 3
def __init__(self):
self.x = 4
self.y = 5
def foo(self):
x = 6
return x
first = MyVariable() # Line A
first.z = 7
print(dir(MyVariable)) # Line 1
'''
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'foo', 'x']
'''
print(MyVariable.__dict__) # Line 2
'''
{'__module__': '__main__', 'x': 3, '__init__': <function MyVariable.__init__ at 0x7fca4680cc20>, 'foo': <function MyVariable.foo at 0x7fca4680ce00>, '__dict__': <attribute '__dict__' of 'MyVariable' objects>, '__weakref__': <attribute '__weakref__' of 'MyVariable' objects>, '__doc__': None}
'''
print(dir(first)) # Line 3
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'foo', 'x', 'y', 'z']
print(first.__dict__) # Line 4
{'x': 4, 'y': 5, 'z': 7}
print(first.x) # Line 5
4
Line 1
This returns the namespace including built-in and user-defined attributes, methods and functions of the class MyVariable
.
Line 2
This includes writable attributes for MyVariable
. Notice that the attributes defined in the methods and functions are not included. Those do not exist unless the respective function is called: An implicit creation occurs in Line A for first.y, and an explicit creation for x
in MyVariable().foo()
.
Line 3
This is the same output as Line 1 but with instance attributes added. This is the expected behavior as described in the previous list of bullet points regarding dir()
. The object inherits the class namespace. The x
in this namespace refers to self.x
(see line 5 for more details).
Line 4
These are the writable attributes for the first
object of type MyVariable
. Methods such as foo
are not included because all instances share them, unless explicitly overridden for that particular object.
Line 5
When first.x
is called, it could be referring to the MyVariable.x
or self.x
. But which x
it refers to depends on its locality.
Because the object constructor is nested within the class, then x
equals 4
. It does not use the x
inside MyVariable().foo()
either because, although it’s most nested in the class, it would be local to the function.
Namespace Access for Modules
With modules (i.e. import my_module
), only one module object is created for the file. If we had two module objects, then they would have to be two different modules altogether; otherwise, there would be a naming conflict.
Now suppose I am updating the file of a module by adding functions. I would have to exit the Python interpreter and re-open it, then re-import the module. Python supplies a reload()
function for modules to resolve this issue.
If we had to do this with objects, it would be most impractical. We would have to set, then re-set the attribute… It would be like a group of people sharing an object.
Python is designed for every use-case to have its own object with its own attribute namespace to promote modularity.
Access Modifiers for Class Attributes
C++ has a concept known as public
, protected
and private
variables. Public variables are object attributes accessible from outside of the class. Private variables cannot be accessed outside of the class’s namespace.
Public attributes are OK when demonstrating simple examples or working with simple scripts. By default, all attributes are public in Python.
Keeping attributes public is what helps Python remain intuitive, easy-to-use and popular. However, you may want to restrict the attribute so that it keeps its original value and prevent its re-assignment.
By prefixing the name of the attribute with a double underscore (__), the interpreter recognizes that the attribute is not meant to be accessed outside of the class through a process known as name mangling:
class C1():
def __init__(self):
self.w = '3' # This is a normal attribute. Nothing interesting happens.
self._w = '4' # No special behavior occurs, but humans see the single underscore
self.__w = '5' # The interpreter recognizes the double underscore and 'mangles' the name
class C2(C1):
def __init__(self):
super().__init__()
if __name__ == '__main__':
c1 = C1()
print(c1.w) # Prints 3
print(c1._w) # Prints 4
print(c1.__w) # Attribute error: The name doesn't exist.
print(c1._C1__w) # This is the new name of the attribute.
c2 = C2() # All attributes are inherited due to the use of the super() constructor
print(c2.w) # Prints 3
print(c2._w) # Prints 4
print(c2.__w) # Attribute error: The name doesn't exist (just as above).
print(c2._C1__w) # This is the new name of the attribute (just as above). Prints 5.
To access the c1.__w
attribute outside of the class, you’d have to follow the complex object._Class__attribute
format with those double underscores at the beginning, and no one does that by accident.
There is another naming convention of using single underscores (c1._w
): As mentioned in the documentation linked above, these attributes can be accessed from anywhere like normal attributes, but are not meant to be.
An example is creating read-only attributes. An attribute is writable if it can be changed to a different value. Consider the following program:
class MyClass:
def __init__(self):
self._read_only_attr = 10 # Leading underscore implies protected access
@property # An encapsulation mechanism for functions
def read_only_attr(self):
return self._read_only_attr # No setter defined, making it read-only
obj = MyClass()
print(obj.read_only_attr) # Output: 10
# Attempting to modify this will raise an AttributeError
# obj.read_only_attr = 20 # This line would cause an error
# obj._read_only_attr = 20 # This line is OK
The @property
decorator is a built-in decorator, a feature of Python that modifies functions and adds extra behavior to them. In the above example we create a a property and define its getter method so that the attribute _read_only_attr
becomes read-only.
In the last line of the above example, you can still overwrite this attribute because it is only implicit-protected. It’s not like C++ where the protected
keyword is added before a class member variable.
We’re all consenting adults here.
Guido Van Rossum, author of Python
A Python developer is not going to modify or over-write attributes with a leading underscore because, despite the design of the language allowing this modification, the community of Python developers as a whole consented, or agreed, that this behavior is not acceptable, and places the trust on the developer to write the right way and do the right thing.
Leave a Reply