Magic Methods are a broad and general term that refers to “special” methods in a Python class. There is no single definition for all of them, as their use is diverse. For example, a few common and well-known magic methods include:
__init__ that serves as the object initializer (sometimes incorrectly referred to as constructor)__str__ that provides a “string representation” of your object__add__ that allows you to “overload” the + operator.What do all these methods have in common? Well, obviously they all start and end with double underscores (__). But aside from that, what makes them “magic methods” is that they’re invoked somehow “specially”. We don’t manually invoke these methods; Python is the one doing it. For example, we don’t do obj.__str__(), we do str(obj).
There are many magic methods, but as we will focus on __getattr__ and __getattribute__ in this post.
Let’s start with __getattr__. This method will allow you to “catch” references to attributes that don’t exist in your object. Let’s see a simple example to understand how it works:
class Dummy(object):
pass
d = Dummy()
d.does_not_exist # Fails with AttributeError
In this example, the attribute access fails (with an AttributeError) because the attribute does_not_exist doesn’t exist.
But using the __getattr__ magic method, we can intercept that inexistent attribute lookup and do something so it doesn’t fail:
class Dummy(object):
def __getattr__(self, attr):
return attr.upper()
d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one # 'WHAT_ABOUT_THIS_ONE'
But if the attribute does exist, __getattr__ won’t be invoked:
class Dummy(object):
def __getattr__(self, attr):
return attr.upper()
d = Dummy()
d.value = "Python"
print(d.value) # "Python"
__getattribute__ is similar to __getattr__, with the important difference that __getattribute__ will intercept EVERY attribute lookup, doesn’t matter if the attribute exists or not. Let me show you a simple example:
class Dummy(object):
def __getattribute__(self, attr):
return 'YOU SEE ME?'
d = Dummy()
d.value = "Python"
print(d.value) # "YOU SEE ME?"
In that example, the d object already has an attribute value. But when we try to access it, we don’t get the original expected value (“Python”); we’re just getting whatever __getattribute__ returned. It means that we’ve virtually lost the value attribute; it has become “unreachable”.
If you ever need to use __getattribute__ to simulate something similar to __getattr__ you’ll have to do some more advanced Python handling:
class Dummy(object):
def __getattribute__(self, attr):
__dict__ = super(Dummy, self).__getattribute__('__dict__')
if attr in __dict__:
return super(Dummy, self).__getattribute__(attr)
return attr.upper()
d = Dummy()
d.value = "Python"
print(d.value) # "Python"
print(d.does_not_exist) # "DOES_NOT_EXIST"
It’s not common to just randomly catch every attribute lookup. We generally use __getattr__ with a clear objective in mind: whenever we want to provide some dynamic access to our objects. A good example could be extending the base Python tuple to add it some Scala flavor to it. In Scala, tuples are created really similarly to Python:
val a_tuple = ("z", 3, "Python", -1)
But they’re accessed in a different way:
println(a_tuple._1) // “z”
println(a_tuple._3) // “Python”
Each element in the tuple is accessed as an attribute, with the first element being the attribute _1, the second _2, and so on.
In that example, you can see how we’re catching missing attributes with __getattr__, but if that attribute is not in the form of _n (where n is an integer), we just raise the AttributeError manually.
We can easily extend our common Python tuple to match this behavior, the code is really simple:
class Tuple(tuple):
def __getattr__(self, name):
def _int(val):
try:
return int(val)
except ValueError:
return False
if not name.startswith('_') or not _int(name[1:]):
raise AttributeError("'tuple' object has no attribute '%s'" % name)
index = _int(name[1:]) - 1
return self[index]
t = Tuple(['z', 3, 'Python', -1])
print(t._1) # 'z'
print(t._2) # 3
print(t._3) # 'Python'
t = Tuple(['z', 3, 'Python', -1])
assert t._1 == 'z'
assert t._2 == 3
assert t._3 == 'Python'
assert t._4 == -1
getattr (object, name[, default])is one of Python’s built-in functions, its role is to get the properties of the object.
Example:
class Foo:
def __init__(self, x):
self.x = x
f = Foo(10)
getattr(f, 'x')
f.x # 10
getattr(f, 'y', 'bar') # 'bar'
object. __getattr__(self, name)Is an object method that is called if the object’s properties are not found.
This method should return the property value or throw AttributeError.
Note that if the object property can be found through the normal mechanism, it will not be called.__getattr__method.
Example:
class Frob:
def __init__(self, bamf):
self.bamf = bamf
def __getattr__(self, name):
return 'Frob does not have `{}` attribute.'.format(str(name))
f = Frob("bamf")
f.bar # 'Frob does not have `bar` attribute.'
f.bamf # f'bamf'
This method is called unconditionally when accessing the properties of an object. This method only works for new classes.
The new class is a class that integrates from object or type.
If the class is also defined at the same time__getattr__(), it will not be called__getattr__() unless__getattribute__() shows the call__getattr__()Or thrown AttributeError. The method should return the property value or throw AttributeError. To avoid infinite recursion in methods, you should always use the methods of the base class to get the properties:
object.__getattribute__(self, name).
grammar:object. __getattribute__(self, name)
Example:
class Frob(object):
def __getattribute__(self, name):
print (f"getting `{str(name)}`")
return object.__getattribute__(self, name)
f = Frob()
f.bamf = 10
f.bamf # getting `bamf`10
__get__()The method is one of the descriptor methods. Descriptors are used to transform access object properties into call descriptor methods.
Example:
class Descriptor(object):
def __get__(self, obj, objtype):
print(f"get value={self.val}")
return self.val
def __set__(self, obj, val):
self.val = val
class Stu(object):
age = Descriptor()
stu = Stu()
stu.age = 12
Magic Methods are a great mechanism to extend the basic features of Python classes and objects and provide more intuitive interfaces. You can provide dynamic attribute lookups with __getattr__ for those missing attributes that you want to intercept. But be careful with __getattribute__, because it might be tricky to implement correctly without losing attributes in the Python void.
3 Comments
man, you have typos in the code cells! after class definition, there should be a new line!
like here:
“`
class Dummy(object):
pass
d = Dummy()
d.does_not_exist # Fails with AttributeError
“`
Thank you for your feedback. The typos were because of some plugins that I’ve recently changed. I corrected the typos.
Nice article, I used this knowledge to write a dynamic configuration class that is backed by ConfigParser