Python: Creating Instance Properties
Tags: python, property
Published: 2019-05-13 12:59:55

---
title: Python: Creating Instance Properties
published: true
description: Want a Python class with an @property attribute that works on an instance (aka self) not just the class (aka cls)?
tags: python, property
---


The Python @property descriptor is used by developers as a neat way to create special class attributes. If you don’t know about Pythons descriptor protocol you should read up on it, there's plenty of great resources out there to describe how it works (especially the official Python docs). The goal of this article is to cover a slightly more "advanced" usage of @property descriptors which allows developers to create instance attributes that are derrived from a Python property class.

Remember that Python descriptors work on classes not instances! Therefore, all instances - of a specific Python class which contain "Python descriptor attributes" - will contain the same class attributes (aka the properties) that all share the same value. In other words, when you define an attribute for a class which is derrived from an @property descriptor, you are defining a class variable. 

The following code snippet defines a (simplest) Python descriptor class:

```python
class MyDescriptor:
    
    def __init__(self, value):
        self._value = value
        
    def __get__(self, owner_instance, owner_type):
        return self._value
    
    def __set__(self, owner_instance, value):
        self._value = value
```

You may think usage for this simple descriptor might look something like:

```python
class MyClass:

    bar = MyDescriptor("bar")
    
    def __init__(self):
        self.foo = MyDescriptor("foo")
```

Lets start with the class variable `MyClass.bar`:

```python
first_instance = MyClass()
second_instance = MyClass()

print(f"first_instance.bar = {first_instance.bar}")
# prints "bar"

first_instance.bar = "bar new"

print(f"second_instance.bar = {first_instance.bar}")
# prints "bar new"
```

This is exactly what we should expect - considering `MyClass.bar` was defined on the class (so all instances share the same value for that attribute). 

However, looking into the instance attribute `MyClass.foo` may not yeild the expected result, for example:

```python
first_instance = MyClass()
second_instance = MyClass()

print(f"first_instance.foo = {first_instance.foo}")
# prints "<__main__.MyDescriptor object at 0x7f87492899e8>"

print(f"second_instance.foo = {second_instance.foo}")
# prints "<__main__.MyDescriptor object at 0x7f87492897a9>"
```

Note: You may have expected to see the value held by the descriptor `MyClass.foo` be the value printed. However - as I mentioned before - Python descriptors work on classes not instances! So what does the descriptor protocol do to get/set this value? Well, that's simple... it calls the `__get__` and `__set__` methods defined on your descriptor class! Armed with this knowledge you can simply "manually" implement this functionality yourself. How? By creating a custom base class:

```python
class Base:
    """
    A base class
    """

    def __setattr__(self, attr, value):
        try:
            # Try invoking the descriptor protocol __set__ "manually"
            got_attr = super().__getattribute__(attr)
            got_attr.__set__(self, value)
        except AttributeError:
            # Attribute is not a descriptor, just set it:
            super().__setattr__(attr, value)

    def __getattribute__(self, attr):
        # If the attribute does not exist, super().__getattribute__()
        # will raise an AttributeError
        got_attr = super().__getattribute__(attr)
        try:
            # Try "manually" invoking the descriptor protocol __get__()
            return got_attr.__get__(self, type(self))
        except AttributeError:
            # Attribute is not a descriptor, just return it:
            return got_attr
```

This base class will override the standard `__setattr__` and `__getattribute__` methods of the class and tries to manually invoke the descriptor protocol to get/set the attribute value, if that fails it treats the attribute as a "normal" attribute by letting the super class (in this case, `object`) deal with the request (as normal).

Finally, the `MyClass` needs to be modified to inherit from `Base`:

```python
class MyClass(Base):

    bar = MyDescriptor("bar")
    
    def __init__(self):
        self.foo = MyDescriptor("foo")
```

And using the class to create some instances and get values held by instance owned descriptor attributes:

```python
first_instance = MyClass()
second_instance = MyClass()

print(f"first_instance.foo = {first_instance.foo}")
# prints "foo"

print(f"second_instance.foo = {second_instance.foo}")
# prints "foo"

first_instance.foo = "foo new"

print(f"first_instance.foo = {first_instance.foo}")
# prints "foo new"

print(f"second_instance.foo = {second_instance.foo}")
# prints "foo"
```

And there you have it! A clean & simple solution if you want Python descriptor attributes to work on a Python instance, not just the class.