Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Descriptors as instance attributes in python

To the question:

Why can't descriptors be instance attributes?

it has been answered that:

descriptor objects needs to live in the class, not in the instance

because that is the way that the __getattribute__ is implemented.

A simple example. Consider a descriptor:

class Prop(object):      def __get__(self, obj, objtype=None):         if obj is None:             return self         return obj._value * obj._multiplier      def __set__(self, obj, value):         if obj is None:             return self         obj._value = value  class Obj(object):      val = Prop()      def __init__(self):         self._value = 1         self._multiplier = 0 

Consider the case in which each obj has multiple Prop: I would need to use unique names to identify the values and multipliers (Like here. Having a per instance descriptor object would allow to store the _multiplier (and the _value) in the descriptor itself, simplifying a few things.

To implement per instance descriptor attributes you need to either:

  1. create a per instance class See here
  2. override __getattribute__ See here

I am aware that similar questions have been raised before, but I have not found a real explanation:

  1. Why Python is designed this way?
  2. What is the suggested way to store information that the descriptor needs but is per instance?
like image 265
Hernan Avatar asked Sep 26 '12 10:09

Hernan


People also ask

What are instance attributes in Python?

An instance attribute is a Python variable belonging to only one object. It is only accessible in the scope of the object and it is defined inside the constructor function of a class. For example, __init__(self,..).

What is descriptor object 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 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.

How does descriptor work in Python?

Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static methods, class methods, and super() . They are used throughout Python itself. Descriptors simplify the underlying C code and offer a flexible set of new tools for everyday Python programs.


1 Answers

This exact question was raised on Python-list earlier this year. I'm just going to quote Ian G. Kelly's response:

The behavior is by design. First, keeping object behavior in the class definition simplifies the implementation and also makes instance checks more meaningful. To borrow your Register example, if the "M" descriptor is defined by some instances rather than by the class, then knowing that the object "reg" is an instance of Register does not tell me anything about whether "reg.M" is a valid attribute or an error. As a result, I'll need to guard virtually every access of "reg.M" with a try-except construct just in case "reg" is the wrong kind of register.

Second, the separation of class from instance also helps you keep object behavior separate from object data. Consider the following class:

class ObjectHolder(object):     def __init__(self, obj):         self.obj = obj 

Don't worry about what this class might be useful for. Just know that it's meant to hold and provide unrestricted access to arbitrary Python objects:

>>> holder = ObjectHolder(42) >>> print(holder.obj) 42 >>> holder.obj = range(5) >>> print(holder.obj) [0, 1, 2, 3, 4] 

Since the class is meant to hold arbitrary objects, it's even valid that somebody might want to store a descriptor object there:

>>> holder.obj = property(lambda x: x.foo) >>> print(holder.obj) <property object at 0x02415AE0> 

Now suppose that Python invoked the descriptor protocol for descriptors stored in instance attributes:

>>> holder = ObjectHolder(None) >>> holder.obj = property(lambda x: x.foo) >>> print(holder.obj) Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'ObjectHolder' object has no attribute 'foo' 

In this case, the ObjectHolder would fail to simply hold the property object as data. The mere act of assigning the property object, a descriptor, to an instance attribute would change the behavior of the ObjectHolder. Instead of treating "holder.obj" as a simple data attribute, it would start invoking the descriptor protocol on accesses to "holder.obj" and ultimately redirect them to the non-existent and meaningless "holder.foo" attribute, which is certainly not what the author of the class intended.

If you want to be able to support multiple instances of a descriptor, just make that descriptor's constructor take a name argument (prefix), and prefix the added attributes with that name. You could even create a namespace object (dictionary) within the class instance to hold all of the new property instances.

like image 183
nneonneo Avatar answered Sep 19 '22 19:09

nneonneo