Understanding _Get_ and _Set_ and Python Descriptors

Understanding __get__ and __set__ and Python descriptors

The descriptor is how Python's property type is implemented. A descriptor simply implements __get__, __set__, etc. and is then added to another class in its definition (as you did above with the Temperature class). For example:

temp=Temperature()
temp.celsius #calls celsius.__get__

Accessing the property you assigned the descriptor to (celsius in the above example) calls the appropriate descriptor method.

instance in __get__ is the instance of the class (so above, __get__ would receive temp, while owner is the class with the descriptor (so it would be Temperature).

You need to use a descriptor class to encapsulate the logic that powers it. That way, if the descriptor is used to cache some expensive operation (for example), it could store the value on itself and not its class.

An article about descriptors can be found here.

EDIT: As jchl pointed out in the comments, if you simply try Temperature.celsius, instance will be None.

How to use __get__ and __set__ (python descriptors)

From the docs:

object.__set__(self, instance, value)

Called to set the attribute on an instance instance of the owner class to a new value, value.

In your code, Foo.bar = 5 is setting the class attribute, not an instance attribute. If you do use an instance (without first setting Foo.bar = 5, which overrides your descriptor), then you get an exception as expected:

>>> f = Foo()
>>> f.bar
10
>>> f.bar = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __set__
Exception

If you want the __set__ behaviour to apply when the class attribute is set, then the class itself needs to be an instance of a metaclass which uses the descriptor:

class FooMeta(type):
class Bar:
def __get__(self,instance, owner):
return 10
def __set__(self,instance,value):
raise Exception
bar = Bar()

class Foo(metaclass=FooMeta):
pass

Testing:

>>> Foo.bar
10
>>> Foo.bar = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __set__
Exception

How does a descriptor with __set__ but without __get__ work?

The descriptor you've given in the example is a data descriptor.

Upon setting the attribute, as any other data descriptor, it takes the highest priority and is called like so:

type(myinst).__dict__["attr"].__set__(myinst, 1234)

This in turn, adds attr to the instance dictionary according to your __set__ method.

Upon attribute access, the descriptor is checked for having the __get__ method but fails, causing for the search to be redirected to the instance's dictionary like so:

myinst.__dict__["attr"]

If it is not found in the instance dictionary, the descriptor itself is returned.

This behavior is shortly documented in the data model like so:

If it does not define __get__(), then accessing the attribute will
return the descriptor object itself unless there is a value in the
object’s instance dictionary.

Common usecases include avoiding {instance: value} dictionaries inside the descriptors, and caching values in an efficient way.


In Python 3.6, __set_name__ was added to the descriptor protocol thus eliminating the need for specifying the name inside the descriptor. This way, your descriptor can be written like so:

class Desc:
def __set_name__(self, owner, name):
self.name = name
def __set__(self, inst, value):
inst.__dict__[self.name] = value
print("set", self.name)

class Test:
attr = Desc()

Why does __get__ take an owner while __set__ and __delete__ do not?

owner mostly exists for getting the attribute on the class itself, rather than an instance. When you're retrieving the attribute on an instance, the owner argument is redundant, since it's just type(instance).

__set__ doesn't apply to setting the attribute on the class itself, so it has no use for owner.

Why __get__ method of a descriptor in Python is called inside of hassattr()?

If you look at the help for hasattr (or the documentation:

Help on built-in function hasattr in module builtins:

hasattr(obj, name, /)
Return whether the object has an attribute with the given name.

This is done by calling getattr(obj, name) and catching AttributeError.

So no, it doesn't just "check dictionaries", it couldn't do that because not all objects have dictionaries as namespaces to begin with, e.g. built-in objects, or user-defined objects with __slots__.

getattr(obj, name) will correctly invoke the equivalent machinery as:

obj.name

Which would call the descriptor's __get__

Python descriptors (__get__, __set__) on function parameters

Functions actually are first class objects in Python, but you are correct in saying that the syntax you describe would not work as you want. You could potentially do something like this with a decorator that inspects the passed attributes for characteristics that would enable this sort of functionality though. However, you'd probably be better off implementing a callable object, then attaching descriptors to that and creating instances of the callable rather than functions.

Python - __get__ is not called for my class

You can use your decriptor class as follows:

class B:
a = Argument("my_ret_value", argHelp="some help text")

b = B()
b.a
# __get__
# 'my_ret_value'

When set as class attribute on another class and accessed via an instance of said class, then the __get__ method of the descriptor is called. See the Descriptor How-To Guide.



Related Topics



Leave a reply



Submit