__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.name would result in calling the
__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
The main advantage of this is that you can dynamically calculate the attribute’s value at runtime.
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
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)
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)
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.
Join my free webinar “How to Build Your High-Income Skill Python” and watch how I grew my coding business online and how you can, too—from the comfort of your own home.
While working as a researcher in distributed systems, Dr. Christian Mayer found his love for teaching computer science students.
To help students reach higher levels of Python success, he founded the programming education website Finxter.com. He’s author of the popular programming book Python One-Liners (NoStarch 2020), coauthor of the Coffee Break Python series of self-published books, computer science enthusiast, freelancer, and owner of one of the top 10 largest Python blogs worldwide.
His passions are writing, reading, and coding. But his greatest passion is to serve aspiring coders through Finxter and help them to boost their skills. You can join his free email academy here.