Python’s __get__()
magic method defines the dynamic return value when accessing a specific instance and class attribute. It is defined in the attribute’s class and not in the class holding the attribute (= the owner class). More specifically, when accessing the attribute through the owner class, Python dynamically executes the attribute’s __get__()
method to obtain the attribute value.
object.__get__(self, instance, owner=None)
For example, if you create a class Person
with an attribute of type Name
(and instances person
and name
), calling person.name
would result in calling the Name
‘s __get__()
method to obtain the result.
💡 Terminology: The name
attribute of type Name
in our example is called a descriptor.
What’s the Purpose of __get__() and Descriptor Attributes
You may ask: Why bothering overriding __get__()
?
The main advantage of this is that you can dynamically calculate the attribute’s value at runtime.
Basic Example
In the following, you can see a minimal example of a descriptor name attribute defining the __get__
magic method to always return the same dummy string 'Alice Python'
.
class Name: def __get__(self, instance, owner=None): return 'Alice Python' class Person: name = Name() alice = Person() print(alice.name) # Alice Python
This example doesn’t make a lot of practical sense. The main advantage of defining a descriptor type Name
is that you can dynamically create new return values, each time you access the attribute via the owner class.
Dynamic Attribute Value Examples
Here’s an interesting scenario, where you ask the user to input the name when accessing it the first time:
class Name: my_name = '' def __get__(self, instance, owner=None): while not self.my_name: self.my_name = input('your name: ') return self.my_name class Person: name = Name() me = Person() me.name for i in range(10): print('Hello', me.name)
Output:
your name: Alice Python Hello Alice Python Hello Alice Python Hello Alice Python Hello Alice Python Hello Alice Python Hello Alice Python Hello Alice Python Hello Alice Python Hello Alice Python Hello Alice Python
Note that in the previous example, the Python program asks the user for the name only the first time you access it. The remaining times, it reuses the information from the first attribute accesses.
In other words, you can maintain state across attribute accesses using the __get__
magic method!
For example, you can also add a counter to track how often the descriptor attribute has been accessed:
class Name: my_name = '' counter = 0 def __get__(self, instance, owner=None): self.counter += 1 print(self.counter) while not self.my_name: self.my_name = input('your name: ') return self.my_name class Person: name = Name() me = Person() me.name for i in range(10): print('Hello', me.name)
Output:
1 your name: Alice Python 2 Hello Alice Python 3 Hello Alice Python 4 Hello Alice Python 5 Hello Alice Python 6 Hello Alice Python 7 Hello Alice Python 8 Hello Alice Python 9 Hello Alice Python 10 Hello Alice Python 11 Hello Alice Python
Another great example is dynamic lookups:
Dynamic Lookup Descriptor
The following example is from the documentation:
import os class DirectorySize: def __get__(self, obj, objtype=None): return len(os.listdir(obj.dirname)) class Directory: size = DirectorySize() # Descriptor instance def __init__(self, dirname): self.dirname = dirname # Regular instance attribute
You define a class Directory
that has one attribute of type DirectorySize
. As the size of a given directory is only known at runtime, you couldn’t use a constant value when defining the directory the first time.
In fact, the concrete size of the directory needs to be recalculated each time you access the directory, right? Because the size may be different each time you access it depending on what files have been added or removed in the meantime.
That’s where the __get__()
method of the DirectorySize
comes into play!
It recomputes the size of the directory dynamically using the os
module—each time you call it at runtime.
You can see how this results in different attribute values at different times in the example from the docs:
>>> s = Directory('songs') >>> g = Directory('games') >>> s.size # The songs directory has twenty files 20 >>> g.size # The games directory has three files 3 >>> os.remove('games/chess') # Delete a game >>> g.size # File count is automatically updated 2
Now, you have an initial intuition. To learn more about how descriptors work, I’d recommend you check out the in-depth official Python tutorial that is a great resource on the topic! 🙂
Where to Go From Here?
Enough theory. Let’s get some practice!
Coders get paid six figures and more because they can solve problems more effectively using machine intelligence and automation.
To become more successful in coding, solve more real problems for real people. That’s how you polish the skills you really need in practice. After all, what’s the use of learning theory that nobody ever needs?
You build high-value coding skills by working on practical coding projects!
Do you want to stop learning with toy projects and focus on practical code projects that earn you money and solve real problems for people?
🚀 If your answer is YES!, consider becoming a Python freelance developer! It’s the best way of approaching the task of improving your Python skills—even if you are a complete beginner.
If you just want to learn about the freelancing opportunity, feel free to watch my free webinar “How to Build Your High-Income Skill Python” and learn how I grew my coding business online and how you can, too—from the comfort of your own home.