The issue of global shared state

As we have already mentioned, descriptors need to be set as class attributes to work. This should not be a problem most of the time, but it does come with some warnings that need to be taken into consideration.

The problem with class attributes is that they are shared across all instances of that class. Descriptors are not an exception here, so if we try to keep data in a descriptor object, keep in mind that all of them will have access to the same value.

Let's see what happens when we incorrectly define a descriptor that keeps the data itself, instead of storing it in each object:

class SharedDataDescriptor:
def __init__(self, initial_value):
self.value = initial_value

def __get__(self, instance, owner):
if instance is None:
return self
return self.value

def __set__(self, instance, value):
self.value = value

class ClientClass:
descriptor = SharedDataDescriptor("first value")

In this example, the descriptor object stores the data itself. This carries with it the inconvenience that when we modify the value for an instance all other instances of the same classes are also modified with this value as well. The following code listing puts that theory in action:

>>> client1 = ClientClass()
>>> client1.descriptor
'first value'

>>> client2 = ClientClass()
>>> client2.descriptor
'first value'

>>> client2.descriptor = "value for client 2"
>>> client2.descriptor
'value for client 2'

>>> client1.descriptor
'value for client 2'

Notice how we change one object, and suddenly all of them are from the same class, and we can see that this value is reflected. This is because ClientClass.descriptor is unique; it's the same object for all of them.

In some cases, this might be what we actually want (for instance, if we were to create a sort of Borg pattern implementation, on which we want to share state across all objects from a class), but in general, that is not the case, and we need to differentiate between objects. Such pattern is discussed with more detail in Chapter 9, Common Design Patterns.

To achieve this, the descriptor needs to know the value for each instance and return it accordingly. That is the reason we have been operating with the dictionary (__dict__) of each instance and setting and retrieving the values from there.

This is the most common approach. We have already covered why we cannot use getattr() and setattr() on those methods, so modifying the __dict__ attribute is the last standing option, and, in this case, is acceptable.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.194.251