Python __get__ Magic Method

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.

Join the free webinar now!