Double Underscore Methods and Attributes in Python
The builtin dir() method, when applied on a string, or a function, class, dict, tuple etc. reveals a host of double underscore methods(or functions), attributes (or variables), classes and other objects. These are called dunder objects, short for double underscore in their names. We'll talk about dunder methods and attributes today. These are also known as magic methods and magic attributes. Let's have a quick glance at what we will be "tackling" today.
>>> class anyClass: pass >>> dir(anyClass) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] >>> def anyFunction(): pass >>> dir(anyFunction) ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] >>> anyString = 'Ethan' >>> dir(anyString) ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] >>> dir([1,2,3]) # list ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] >>> dir((1,2,3)) # tuple ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] >>> dir( {1:1, 2:2} ) # dict ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
Dunder Methods
Let's begin with dunder methods, we will look at dunder attributes later. Consider a string variable.
>>> anyString = 'Ethan' >>> dunderElementsOfaString = [dunder for dunder in dir( anyString ) if dunder.startswith('__')] >>> for element in dunderElementsOfaString: print(element) __add__ __class__ __contains__ __delattr__ __dir__ __doc__ __eq__ __format__ __ge__ __getattribute__ __getitem__ __getnewargs__ __gt__ __hash__ __init__ __iter__ __le__ __len__ __lt__ __mod__ __mul__ __ne__ __new__ __reduce__ __reduce_ex__ __repr__ __rmod__ __rmul__ __setattr__ __sizeof__ __str__ __subclasshook__
Barring __doc__, each element in the above list is a dunder method. So, what is a dunder method? A dunder method is an implicit function, that is being called behind the scenes of an explicit operation or a function. For example, when you are comparing one string to another string, ( e.g. >>> 'stringOne' == 'stringTwo' # False ), then the __eq__ method is being called. Let's get some help from Python on this method.
>>> help(str.__eq__) Help on wrapper_descriptor: __eq__(self, value, /) Return self==value. ## so >>> 'stringOne' == 'stringTwo' IS IN FACT 'stringOne'.__eq__('stringTwo') >>> 'stringOne'.__eq__('stringTwo') False
Similarly, >>> 'stringOne' != 'stringTwo' is calling the __ne__ method in the background.
>>> 'stringOne' != 'stringTwo' True >>> 'stringOne'.__ne__('stringTwo') True
Dunder Atributes
Dunder attributes vary from object to object, much like dunder methods. There is no fixed list of mandatory dunder attributes associated with an object, but a few are more prominent than others. For example, the __name__ attribute of a module gives us the name of the module itself, __doc__ attribute of a module gives a brief description of a module if it is defined, the __file__ attribute of a module gives the location of the module in your system, and so on. Since we were on strings when we left off, let's wrap that up first. The only dunder attribute associated with a string is the __doc__ attribute.
>>> 'Ethan'.__doc__ "str(object='') -> str\nstr(bytes_or_buffer[, encoding[, errors]]) -> str\n\nCreate a new string object from the given object. If encoding or\nerrors is specified, then the object must expose a data buffer\nthat will be decoded using the given encoding and error handler.\nOtherwise, returns the result of object.__str__() (if defined)\nor repr(object).\nencoding defaults to sys.getdefaultencoding().\nerrors defaults to 'strict'." # Let's get this in a readable form. >>> print( 'Ethan'.__doc__) str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'. # So, the __doc__, much like __doc__ in functions, is a docstring(i.e. it is a brief description that someone added for documentation purposes), apart from the fact this __doc__ is assigned the above string, and cannot be changed from variable to variable. >>> str(object = 'Ethan') 'Ethan' >>> 'Ethan'.__doc__ = "My name is Ethan" Traceback (most recent call last): # Traceback info 'Ethan'.__doc__ = "My name is Ethan" AttributeError: 'str' object attribute '__doc__' is read-only
So that wasn't of much help. Let's have a look at the dunder attributes of modules. To avoid confusion, we will keep a narrow field of view by concentrating on string dunder attributes, and leave other objects aside.
>>> [dunder for dunder in dir( __builtins__ ) if dunder.startswith('__')] ['__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__'] >>> print( __builtins__.__doc__ ) Built-in functions, exceptions, and other objects. Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices. >>> __builtins__.__name__ 'builtins' >>> import math >>> [dunder for dunder in dir( math ) if dunder.startswith('__')] ['__doc__', '__loader__', '__name__', '__package__', '__spec__'] >>> print(math.__doc__) This module is always available. It provides access to the mathematical functions defined by the C standard. >>> import os >>> [dunder for dunder in dir( os ) if dunder.startswith('__')] ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] >>> print( os.__file__ ) C:\Python34\lib\os.py >>> print( os.__doc__ ) OS routines for NT or Posix depending on what system we're on. This exports: - all functions from posix, nt or ce, e.g. unlink, stat, etc. - os.path is either posixpath or ntpath - os.name is either 'posix', 'nt' or 'ce'. - os.curdir is a string representing the current directory ('.' or ':') - os.pardir is a string representing the parent directory ('..' or '::') - os.sep is the (or a most common) pathname separator ('/' or ':' or '\\') - os.extsep is the extension separator (always '.') - os.altsep is the alternate pathname separator (None or '/') - os.pathsep is the component separator used in $PATH etc - os.linesep is the line separator in text files ('\r' or '\n' or '\r\n') - os.defpath is the default search path for executables - os.devnull is the file path of the null device ('/dev/null', etc.) Programs that import and use 'os' stand a better chance of being portable between different platforms. Of course, they must then only use functions that are defined by all platforms (e.g., unlink and opendir), and leave all pathname manipulation to os.path (e.g., split and join). >>> os.__name__ 'os' >>> [dunder for dunder in dir( traceback ) if dunder.startswith('__')] ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
So, the only really useful dunder attributes in modules are __name__, __doc__ & __file__. Note that not all modules might have these set(i.e. the variables will exist, they might not be populated with values. Also, the __builtins__ doesn't have __file__ at all.), especially user-defined modules. That said, Python developers have made a conscious effort to include these in every module they ship with the installation.
You may explore the dunder elements of other objects, using the builtin help() method and a bit of Googling. The list comprehension we used earlier is handy to list down the dunder elements, and then you can evaluate each of them one by one.
Using dunder attributes of a module
__doc__
The __doc__ attribute provides a succinct description of the module. Let's see how we can set it and how someone else, who is importing your module, can use it effectively.
# to-be-imported module: prime.py def primeOrNot(num): if num <= 1: return False for factor in range(2, num): # if there exists any number greater than 2 that if num % factor == 0: # divides the given number evenly(with remainder 0, that is), return False # then the given number is not prime return True __doc__ = 'This module provides a handy method to test whether a number is prime or not. Perform dir(prime) or help(prime) for support.'
Let's import the module.
# One way of importing the module is by going to Run Menu in IDLE window of above script > Python Shell >>> import prime >>> prime.__doc__ 'This module provides a handy method to test whether a number is prime or not. Perform dir(prime) or help(prime) for support.' >>> dir(prime) ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'primeOrNot'] >>> help(prime) Help on module prime: NAME prime - This module provides a handy method to test whether a number is prime or not. Perform dir(prime) to view the function directory. FUNCTIONS primeOrNot(num) FILE c:\users\lakshaya\desktop\prime.py
__file__
The sole purpose of the __file__ attribute is to tell the user about the location of the module. We don't need to set it.
>>> import prime >>> prime.__file__ 'C:\\Users\\lakshaya\\Desktop\\prime.py' >>>
__name__
The __name__ attribute is incredibly useful. You may have encountered something like if __name__ == '__main__'. This if construct is used to execute the top-level code(i.e. at the first indent level) of a script ONLY IF it is being run directly(i.e. by double-clicking the .py file), and not if the module is being imported into another file.
Let's look at this in detail.
In other programming languages such as C++, there is a main() function (it has to named main, or else the code doesn't compile), declared explicitly by the user, from which the execution of a program begins. In Python, the main() function is composed of all the top-level code i.e. all statements written at indentation level 0. That’s it, you don’t have to declare the main() function, Python does it by itself.
The top-level code in a Python script gets executed as soon as it is run via command line (i.e. > python myfile.py) or run directly by executing a .py file.
The __name__ dunder attribute evaluates to the name of the module itself. However, if the module is being run directly by either of the two methods stated above, then the __name__ attribute is set to the string "__main__". This enables the user to check if the script is being run directly (command line or execution of .py file) or it is being imported. If the code is being imported into another module, the function and class definitions will be imported, but the top-level code will not get executed.
Below is a simple Python script that contains a function, a print statement, and the if construct. If the script is being run directly, the function is called with a sample value, and not if the script is being imported as a module.
# prime.py def primeOrNot(num): if num <= 1: print(str(num), "is not a prime number.") return False for factor in range(2, num): # if there exists any number greater than 2 that if num % factor == 0: # divides the given number evenly(with remainder 0, that is), print(str(num), "is not a prime number.") return False # then the given number is not prime print(str(num), "is a prime number.") return True print("Top-level code being executed...") if __name__ == '__main__': primeOrNot(23)
When the above script is run directly via command line or by executing prime.py
Top-level code being executed... 23 is a prime number.
When the above script is being imported as a module
# One way of importing the module is by going to Run Menu in IDLE window of above script > Python Shell >>> import prime Top-level code being executed...
I hope this was useful to you. If you have any questions, please post it in the comment below. Thank you.