There can be alot of mystery involved in Python when it comes to the dunder methods. The most famous dunder method most python developer are most familar with is the __name__ method as follows.
def main():
print 'Ran the main method'
if __name__ == '__main__':
main()
Many python developers just accept this as one of the many truism that are in programming and memorize this and move on with their lives. In this screencast will be pulling back the curtains on these dunder method and see what is possible and what things you should watch out for!
Name is the variable which contains the name of the caller of the module for example if I call a function from the commandline the __name__ has __main__ as the name. Other wise it will contain the name of the module that it is contained in.
The main difference between these two is:
one controls the creation of the instance (__new__) and returns an instance.
and the other deal with the initialization of the new instance (__init__) and returns nothing
__new__ is mainly used for subclassing immutable types like the example below.
class PositiveInteger(int):
def __new__(cls, number):
if number < 0:
raise ValueError('The number has to be positive integer')
else:
return int.__new__(cls, number)
def __init__(self, number):
self.number = number
def get_number(self):
return self.number
p = PositiveInteger(1)
p.get_number()
PositiveInteger(-2)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-93-50009b309c8e> in <module>() 1 p = PositiveInteger(1) 2 p.get_number() ----> 3 PositiveInteger(-2) <ipython-input-92-d00fbea09aab> in __new__(cls, number) 2 def __new__(cls, number): 3 if number < 0: ----> 4 raise ValueError('The number has to be positive integer') 5 else: 6 return int.__new__(cls, number) ValueError: The number has to be positive integer
These dunder functions allow you to create context managers like the ones below which can be pretty cool when have to do something on entrance and exit. Lets look at the following context manager you maybe familiar with.
with open('numbers.txt') as numbers:
list_of_numbers = numbers.read()
print list_of_numbers.split()
print numbers
print numbers
['5', '6', '7', '8', '9', '10', '29', '49', '58', '27'] <open file 'numbers.txt', mode 'r' at 0x107069420> <closed file 'numbers.txt', mode 'r' at 0x107069420>
Now for example let's say we want to create a redis connection do some stuff in redis and always be sure to close the connection when I am done like a good programmer context managers will come in very handy. Like so:
class Redis(object):
def __init__(self, rdb):
self.connection = CreateConnection(rdb)
def __enter__(self):
return self.connection
def __exit__(self):
self.connection.close()
with Redis('redis://localhost:6379') as redis:
# do things with new redis connection.
redis.set('neck', 'beard')
These two are often confused most developers just have one call the other or just worry about implementing __str__
The difference between the two is mostly semantic but it is useful to know and can be extremely helpful. If you implement a __repr__ and no __str__. __str__ == __repr__
class Person(object):
def __init__(self, name='', age=10):
self.name = name
self.age = age
def __repr__(self):
return "Person(name=%r, age=%r)" % (self.name, self.age)
def __str__(self):
return 'A person name %s at age %d' % (self.name, self.age)
p = Person(name='Mahdi', age=50)
print str(p)
print repr(p)
p2 = Person(name='Mahdi', age=50)
print p2
A person name Mahdi at age 50 Person(name='Mahdi', age=50) A person name Mahdi at age 50
from functools import total_ordering
@total_ordering
class Name(object):
def __init__(self, name):
self.name = name
def __eq__(self, other):
if isinstance(other, Name):
return len(self.name) == len(other.name)
return False
def __lt__(self, other):
if isinstance(other, Name):
return len(self.name) < len(other.name)
return False
mahdi = Name('Mahdi')
mikey = Name('Mikey')
kelsey = Name('Kelsey')
print mahdi == mikey
print mahdi <= mikey
print kelsey > mahdi
True True True
These two method are quite useful if you want to control how things are set and retrieved. For example, if we want to raise an error whenever someone asks for a variable that isn't there. We can also limit certain assignments to attributes as well.
class Friend(object):
def __init__(self, name, nickname):
self.name = name
self.nickname = nickname
def __getattr__(self, name):
raise AttributeError('The attribute you asked for %s for does not exist, clean it up sloppy'% name)
def __setattr__(self, name, value):
if value == "Mahdi the Potty" and name == 'nickname':
raise ValueError("No one calls me Mahdi the Potty")
else:
self.__dict__[name] = value
f = Friend('Mahdi', 'Mahdi the Bahdi')
# f.first_name
f.nickname = "Mahdi the Ladidai"
Which stands for method resolution order, this can come in handy when dealing with multiple inheritance to clarify which version of the method will be run. This will return a list of classes in order for which the methods will be resolved. For example:
class First(object):
pass
class Fifth(object):
pass
class Second(First):
pass
class Third(Fifth):
pass
class Fourth(Third, Second):
def __init__(self):
pass
print Fourth.__mro__
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.Fifth'>, <class '__main__.Second'>, <class '__main__.First'>, <type 'object'>)
This is mainly done for performance reasons, for example if you are going to have many and I mean many instances of the same class they each have a dictionary object associated with them. This can take up alot of memory unnecessarily. This is where __slots__ comes in saves the day, when doing this there are few things that you need to be aware of for example.
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
class Point1(object):
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(5, 6)
p1 = Point1(5, 6)
p.z = 9
print p.z
#p1.z = 8
p1.y = 10
p1.x = 9
print p1.y
print p1.x
p.__dict__
#p1.__dict__
#p1.x = 9
#p1.__dict__
9 10 9
{'x': 5, 'y': 6, 'z': 9}
p1.z = 5