Data descriptors

Now, let's look at the difference of using a data descriptor. For this, we are going to create another simple descriptor that implements the __set__ method:

class DataDescriptor:

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

def __set__(self, instance, value):
logger.debug("setting %s.descriptor to %s", instance, value)
instance.__dict__["descriptor"] = value


class ClientClass:
descriptor = DataDescriptor()

 Let's see what the value of the descriptor returns:

>>> client = ClientClass()
>>> client.descriptor
42

Now, let's try to change this value to something else, and see what it returns instead:

>>> client.descriptor = 99
>>> client.descriptor
42

The value returned by the descriptor didn't change. But when we assign a different value to it, it must be set to the dictionary of the object (as it was previously):

>>> vars(client)
{'descriptor': 99}

>>> client.__dict__["descriptor"]
99

So, the __set__() method was called, and indeed it did set the value to the dictionary of the object, only this time, when we request this attribute, instead of using the __dict__ attribute of the dictionary, the descriptor takes precedence (because it's an overriding descriptor).

One more thing—deleting the attribute will not work anymore:

>>> del client.descriptor
Traceback (most recent call last):
...
AttributeError: __delete__

The reason is as follows—given that now, the descriptor always takes place, calling del on an object doesn't try to delete the attribute from the dictionary (__dict__) of the object, but instead it tries to call the __delete__() method of the descriptor (which is not implemented in this example, hence the attribute error).

This is the difference between data and non-data descriptors. If the descriptor implements __set__(), then it will always take precedence, no matter what attributes are present in the dictionary of the object. If this method is not implemented, then the dictionary will be looked up first, and then the descriptor will run.

An interesting observation you might have noticed is this line on the set method:

instance.__dict__["descriptor"] = value

There are a lot of things to question about that line, but let's break it down into parts.

First, why is it altering just the name of a "descriptor" attribute? This is just a simplification for this example, but, as it transpires when working with descriptors, it doesn't know at this point the name of the parameter it was assigned to, so we just used the one from the example, knowing that it was going to be "descriptor".

In a real example, you would do one of two things—either receive the name as a parameter and store it internally in the init method, so that this one will just use the internal attribute, or, even better, use the __set_name__ method.

Why is it accessing the __dict__ attribute of the instance directly? Another good question, which also has at least two explanations. First, you might be thinking why not just do the following:

setattr(instance, "descriptor", value)

Remember that this method (__set__) is called when we try to assign something to the attribute that is a descriptor. So, using setattr() will call this descriptor again, which, in turn, will call it again, and so on and so forth. This will end up in an infinite recursion.

Do not use setattr() or the assignment expression directly on the descriptor inside the __set__ method because that will trigger an infinite recursion.

Why, then, is the descriptor not able to book-keep the values of the properties for all of its objects?

The client class already has a reference to the descriptor. If we add a reference from the descriptor to the client object, we are creating circular dependencies, and these objects will never be garbage-collected. Since they are pointing at each other, their reference counts will never drop below the threshold for removal.

A possible alternative here is to use weak references, with the weakref module, and create a weak reference key dictionary if we want to do that. This implementation is explained later on in this chapter, but for the implementations within this book, we prefer to use this idiom, since it is fairly common and accepted when writing descriptors.

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

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