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