Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adaptable descriptor in Python

I want to create some kind of descriptor on a class that returns a proxy object. The proxy object, when indexed retrieves members of the object and applies the index to them. Then it returns the sum.

E.g.,

class NDArrayProxy:

    def __array__(self, dtype=None):
        retval = self[:]
        if dtype is not None:
            return retval.astype(dtype, copy=False)
        return retval


class ArraySumProxy(NDArrayProxy):

    def __init__(self, arrays):
        self.arrays = arrays

    @property
    def shape(self):
        return self.arrays[0].shape

    def __getitem__(self, indices):
        return np.sum([a[indices]
                       for a in self.arrays],
                      axis=0)

This solution worked fine while I had actual arrays as member variables:

class CompartmentCluster(Cluster):

    """
    Base class for cluster that manages evidence.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.variable_evidence = ArraySumProxy([])

class BasicEvidenceTargetCluster(CompartmentCluster):

    # This class variable creates a Python object named basic_in on the
    # class, which implements the descriptor protocol.

    def __init__(self,
                 *,
                 **kwargs):
        super().__init__(**kwargs)

        self.basic_in = np.zeros(self.size)
        self.variable_evidence.arrays.append(self.basic_in)

class ExplanationTargetCluster(CompartmentCluster):

    """
    These clusters accept explanation evidence.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.explanation_in = np.zeros(self.size)
        self.variable_evidence.arrays.append(self.explanation_in)

class X(BasicEvidenceTargetCluster, ExplanationTargetCluster):
    pass

Now I've changed my arrays into Python descriptors (cluster_signal implements the descriptor protocol returning a numpy array):

class CompartmentCluster(Cluster):

    """
    Base class for cluster that manages evidence.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.variable_evidence = ArraySumProxy([])

class BasicEvidenceTargetCluster(CompartmentCluster):

    # This class variable creates a Python object named basic_in on the
    # class, which implements the descriptor protocol.

    basic_in = cluster_signal(text="Basic (in)",
                              color='bright orange')

    def __init__(self,
                 *,
                 **kwargs):
        super().__init__(**kwargs)

        self.variable_evidence.arrays.append(self.basic_in)

class ExplanationTargetCluster(CompartmentCluster):

    """
    These clusters accept explanation evidence.
    """

    explanation_in = cluster_signal(text="Explanation (in)",
                                    color='bright yellow')

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.variable_evidence.arrays.append(self.explanation_in)

class X(BasicEvidenceTargetCluster, ExplanationTargetCluster):
    pass

This doesn't work because the append statements append the result of the descriptor call. What I need is to append either a bound method or similar proxy. What's the nicest way to modify my solution? In short: The variables basic_in and explanation_in were numpy arrays. They're now descriptors. I would like to develop some version of ArraySumProxy that works with descriptors rather than requiring actual arrays.

like image 387
Neil G Avatar asked Sep 28 '15 00:09

Neil G


People also ask

What are descriptors in Python?

Descriptors are Python objects that implement a method of the descriptor protocol, which gives you the ability to create objects that have special behavior when they're accessed as attributes of other objects.

What are the different types of descriptor?

There are two types of descriptors: data descriptors and non-data ones. If a descriptor implements both 1 __get__() and __set__() , it's called a data descriptor; otherwise is a non-data descriptor.

What is __ set __ in Python?

The __set__() method is invoked when the value is set to the attribute, and unlike the __get__() method, it returns nothing. It has two arguments apart from the descriptor object itself, i.e., the instance which is the same as the __get__() method and the value argument, which is the value you assign to the attribute.

What is the difference between properties and descriptors?

The Cliff's Notes version: descriptors are a low-level mechanism that lets you hook into an object's attributes being accessed. Properties are a high-level application of this; that is, properties are implemented using descriptors.


1 Answers

When you access a descriptor, it is evaluated and you only get the value. Since your descriptor does not always return the same object (I guess you cannot avioid it?), you dont want to access the descriptor when you are initializing your proxy.

The simplest way to avoid accessing it, is to just remember its name, so instead of:

self.variable_evidence.arrays.append(self.basic_in)

you do:

self.variable_evidence.arrays.append((self, 'basic_in'))

Then, of course, variable_evidence has to be aware of that and do getattr(obj, name) to access it.

Another option is to make the descriptor return a proxy object which is evaluated later. I don't know what you are doing, but that might be too many proxies for good taste...

EDIT

Or... you can store the getter:

self.variable_evidence.arrays.append(lambda: self.basic_in)
like image 122
zvone Avatar answered Oct 29 '22 17:10

zvone