Descriptors generally have to interact with attributes of the managed object,
and this is done by inspecting __dict__ on that object (or calling
getattr/setattr, but the problem is the same), and finding the key under
the specific name.

For this rea­so­n, the des­crip­tor wi­ll ha­ve to know the na­me of the key to look
­fo­r, whi­ch is re­lated to the na­me of the attri­bu­te is ma­na­gin­g.

What we re­qui­re is to che­ck that the na­me is pa­ss­ed to the des­crip­tor pro­per­l­y, ­ba­si­ca­ll­y:

assert Managed.descriptor.name == 'descriptor'

But we don’t want to pass the string 'descriptor' as a parameter when
constructing it, because it’s repetitive. Instead, we want this to be
configured automatically. Let’s see some options.

A class decorator

Wi­th a cla­ss de­co­ra­to­r, we could de­fi­ne all de­co­ra­tors for the cla­ss as
­pa­ra­me­ters of the de­co­ra­to­r, and make the as­sig­n­ment of the na­me in it as we­ll.

So­me­thing like this:

classconfigure_descriptors:def__init__(self,**kwargs):self.descs={dname:dcls(dname)fordname,dclsinkwargs.items()}def__call__(self,class_):fordname,descriptorinself.descs.items():setattr(class_,dname,descriptor)returnclass_@configure_descriptors(descriptor=LoggedAttr)classDecoratedManaged:"""The descriptor is provided by the decorator"""

The con­di­tion is pre­ser­ve­d:

assert DecoratedManaged.descriptor.name == 'descriptor'

In this decorator, we provide the name and the class of the descriptor to be
created, and the decorator instantiates the class with this name. We could also
have created the instance directly in the descriptor, and then update the value
with setattr(descriptor, 'name', dname), which is more general, in
case you want to create descriptors that take multiple arguments on their
__init__ method, but for this case it’s just fine.

Then we set the new des­crip­tor (the one that has the na­me al­ready up­dated on
i­t), to the wra­pped cla­ss.

Ho­we­ve­r, it sti­ll see­ms a bit un­fa­mi­liar or coun­te­r-in­tui­ti­ve that we’­re
­de­fi­ning the des­crip­tor not in the body of the cla­ss, but as a pa­ra­me­ter of a ­de­co­ra­to­r.

The­re must be ano­ther wa­y.

A meta-class

Imagine we flag the class by adding a __set_name = True attribute on it,
in order to hint the meta-class that this is going to be one of the attributes
that need its name changed. Then the meta-class would look something like:

One detail is that the __init__ of the descriptor accepts the name to be
nullable so this works. Another option would have been defining only the
descriptor assigned to the class, and then, re-mapping the attribute with the
instance, passing the name when it’s being constructed on the meta-class. Both
options are the same, and the example was made with simplicity in mind.

This wo­rks but it has a cou­ple of is­sues. First we ha­ve to so­me­how iden­ti­fy
when the cla­ss attri­bu­te nee­ds to be up­dated (in this ca­se, a flag was added to­
i­t, but other al­ter­na­ti­ves are no be­tter at all). The se­cond pro­blem should be­
­ra­ther ob­vious: it’s not a good use of me­ta-­cla­sses, and this is ove­rki­ll (to­
s­ay the leas­t) for what should be a sim­ple ta­sk.

The­re must be a be­tter wa­y.

__set_name__

And there is. At least for Python 3.6 and higher. The __set_name__ method
was included, which is automatically called when the class is being created,
and it receives two parameters: the class and the name of the attribute as it
appears defined in the class.

Wi­th this, the pro­blem is re­du­ced to just sim­pl­y:

classLoggedAttr:...def__set_name__(self,owner,name):self.name=name

And tha­t’s it, no other co­de is nee­de­d. The so­lu­tion is mu­ch sim­ple­r, and it
en­tails le­ss pro­ble­ms.

Actually, I deliberately named the flag __set_name, to get an idea of
what’s coming, and to hint that with __set_name__, Python must be doing
something similar to the example, but in this case we shouldn’t worry about it.

Conclusion

Even thou­gh it’s fi­ne to just know about the last me­tho­d, and we could sim­pl­y
u­se tha­t, it’s sti­ll im­por­tant to ha­ve fo­llo­wed this pa­th, thi­nking about ho­w
­things we­re do­ne pre­vious­l­y, be­cau­se it’s not fair to just as­su­me things we­re
a­lwa­ys good, and take that for grante­d. Othe­rwi­se, we would miss the evo­lu­tio­n
of the lan­gua­ge, and as­su­me the­re we­re ne­ver is­sues, pro­ble­ms or things tha­t
­nee­ded re­vi­sio­n.

And more importantly, there still are. Python still has lots of other areas for
improvement. Just as in this example __set_name__ seems to solve a small,
yet annoying problem, there are many other scenarios on which things are not
crystal clear in Python, so the language still needs to evolve.