Python @ DjangoSpin

Python: Double Underscore Methods and Attributes

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon
Reading Time: 3 minutes

Double Underscore Methods and Attributes in Python

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.


See also:

Buffer this pageShare on FacebookPrint this pageTweet about this on TwitterShare on Google+Share on LinkedInShare on StumbleUpon

Leave a Reply