Sometimes Python is magic
Sometimes Python is magic
Join the DZone community and get the full member experience.Join For Free
Learn how error monitoring with Sentry closes the gap between the product team and your customers. With Sentry, you can focus on what you do best: building and scaling software that makes your users’ lives better.
Python's magic methods have probably inspired other languages such as PHP in providing special functionality that ties into method with standard names, with a very low probability of conflicting with existing methods and no need for implementing interfaces (where they exist). All these methods have a name that starts and ends with two underscores, such as __method__, and is reserved for magic usage.
Magic methods are not always clean as they are called automatically in particular situations or due to operator overloading. They act as syntactic sugar, binding < or a field access to some lines of code
The standard ones
The most famous magic method you have for sure called is the constructor of an object. The arguments passed to the class callable during instantiation are passed to __init__ automatically:
class Car: def __init__(self, maximumSpeed): self.maximumSpeed = maximumSpeed
__str__ and __repr__ instead can be defined to provide a standard string representation of an object. __str__ is a readable representation, while __repr__ is more of a unambiguous dump:
def __repr__(self): return "Car(maximumSpeed=%r)" % (self.maximumSpeed, ) def __str__(self): return "Car%s" % (self.maximumSpeed, )
The mathematical ones
You may call them algebrical operators, as they allow the overloading of operators also in the case of non-numerical classes like those representing Value Objects.
Probably the most famous operators in this category are the equality and comparison ones:
def __eq__(self, another): return self.maximumSpeed == other.maximumSpeed def __lt__(self, another): return self.maximumSpeed < other.maximumSpeed
There are similar magic methods for comparisons different than <: __le__, __ne__, __gt__, __ge__. __cmp__ is deprecated and won't be supported in Python 3 instead.
Along with __eq__, if only for the Java flavor, we should cite __hash__, a method that should provide the same value for each equal objects. Thus, like in the Java case, returning always 0 is a valid choice; different values will instead improve performance by reducing collision.
The attribute ones
__getattr__ and __setattr__ allow for the interception of object properties access and modification:
def __getattr__(self, name): return self.dict[name] def __setattr__(self, name, value): self.dict[name] = value
__delattr__ allows also for the removal of an attribute via the del operator.
Note that the default behavior for object attributes is to put and get them from the instance's own dictionary, so the implementation you have just read won't add anything to an object.
The container ones
You can implement a container type in Python by extending a dictionary or a list, but a Plain Old Python Object can conform to the same syntax (and so, to the same interface) by defining a few magic methods. For example, you could wrap a dictionary:
def __len__(self): return len(self.wrappedDictionary) def __getitem__(self, key): return self.wrappedDictionary[key] def __setitem__(self, key, value): self.wrappedDictionary[key] = value def __contains(self, key): return key in self.wrappedDictionary
This set of methods results in the len() function, the object notation and the in operator working as on the dictionary while called on the wrapping object.
The numerical ones
While the algebric operators are not stirctly part of the arithmetic domain, you can override all the other operators via numerical magic methods:
def __add__(self, another): return Car(self.maximumSpeed + another.maximumSpeed)
You can now sum Car objects, which is probably more dangerous than beneficial. Stick to types defined in the numerical domains (like complex numbers or vectors) when overriding + and the other operators.
The most generic one
__call__ allows you to handle calls to the object itself, when used like a functions or any other callable:
def __call__(self, args): return "You have just called self(args, args)
args is a list of parameters, and can be of variable length.
def __getattr(self, name): return getattr(self.wrappedObject, name)
You can know call object.methodX() as an equivalent to object.wrappedObject.methodX(); the method itself is a callable object resolved before the call is performed.
Opinions expressed by DZone contributors are their own.