'How to alias a custom descriptor in Python?

This is how my module looks like:

class Singleton:
    class Descriptor:
        def __get__(self, obj: Singleton, objtype=None):
            return obj.some_attr

    a_descriptor = Descriptor()

    def load(self):
        # some_attr doesn't have a value unless this function is called
        self.some_attr = ""

_default_singleton = Singleton()

def load():
    _default_singleton.load()

a_descriptor = _default_singleton.a_descriptor   # raises AttributeError

I think my only option is to make the module-level a_descriptor a property which wraps the descriptor, but is there a way to alias a property without making its getter getting called?



Solution 1:[1]

You can access Singleton.__dict__ directly: a_descriptor = vars(Singleton)['a_descriptor']

Solution 2:[2]

You are not calling your load() function to set the required attribute value prior to trying to read it.

This would work:

class Singleton:
    class Descriptor:
        def __get__(self, obj: Singleton, objtype=None):
            return obj.some_attr

    a_descriptor = Descriptor()

    def load(self):
        # some_attr doesn't have a value unless this function is called
        self.some_attr = ""

_default_singleton = Singleton()

def load():
    _default_singleton.load()

load()
a_descriptor = _default_singleton.a_descriptor


Re-reading your question, under the light of the comment bellow, it looks like you were expecting a way to keep the lazy evaluation.

Without another object from which to retrieve your alias, that is not possible - and it is plain clear it is not possible: the existence of "." is the point where any code can be run so that the descriptor code can be evaluated. If the descriptor is bound to a top-level variable, what would be the trigger for Python to actually evaluate it?

If you agree that your alias can be called, like a function, to actually run the value, then the solution is wrap the descriptor.__get__ call in a lambda or partial call:


from functools import partial
a_descriptor =  partial(Singleton.a_decriptor.__get__, _default_singleton, Singleton)

(*) if your descriptor will trigger when being read from the class, you have to retrieve it as a value directly from the cls.__dict__, since cls.descriptor_name will also trigger it. In this case if you have a class hierarchy, you are on your own to retrieve the descriptor from the superclasses. This is done iterating on the cls.__mro__ .

And the descriptor code can be run, and its value retrieved by calling a_descriptor(). There is obviously no way to trigger the descriptor evaluation by simply "mentioning" the alias name

You will need something to trigger the code execution. It could instead be any operator, instead of having to call it. So, let's suppose there wherever you are using the value of your alias, the operator + is a choice: You have to change the partial above for an object featuring the __add__ method. In the limite, the value of the object returned by the descriptor will be "consumed" at some point during the execution: either being converted to a string to be presented for the user/rendered as output, or used in a mathematical operation, so a "lazy" object can be created that would implement all possible ways to "consume" the value, and your alias could be a "transparent lazy proxy" which would run the __get__ code when the value is used. Repeating that for the "unaliazed" descriptor, the "." fills this role triggering the call to get.

It is also possible to convert your module itself to a special object, that would have descriptors of its own, and have your alias be a descriptor on the module that will wrap the descriptor on the class: however it then would have to be retrieved as module.a_descriptor, for the same reasons.

A short example of a wrapper class that could render your descriptor value as a string:

class Wrapper:
    def __init__(self, instance, descriptor_name):
          self.descriptor_name = descriptor_name
          self.instance = instance

    def _exec(self):
          return getattr(self.instance, self.descriptor_name)

    def __str__(self):
          return str(self._exec())
    # repeat for other wanted dunder methods

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 timgeb
Solution 2