Understanding __get__ and __set__ and Python descriptors

后端 未结 7 1301
隐瞒了意图╮
隐瞒了意图╮ 2020-11-22 06:15

I am trying to understand what Python\'s descriptors are and what they are useful for. I understand how they work, but here are my doubts. Consider the following co

7条回答
  •  感情败类
    2020-11-22 06:27

    Why do I need the descriptor class?

    It gives you extra control over how attributes work. If you're used to getters and setters in Java, for example, then it's Python's way of doing that. One advantage is that it looks to users just like an attribute (there's no change in syntax). So you can start with an ordinary attribute and then, when you need to do something fancy, switch to a descriptor.

    An attribute is just a mutable value. A descriptor lets you execute arbitrary code when reading or setting (or deleting) a value. So you could imagine using it to map an attribute to a field in a database, for example – a kind of ORM.

    Another use might be refusing to accept a new value by throwing an exception in __set__ – effectively making the "attribute" read only.

    What is instance and owner here? (in __get__). What is the purpose of these parameters?

    This is pretty subtle (and the reason I am writing a new answer here - I found this question while wondering the same thing and didn't find the existing answer that great).

    A descriptor is defined on a class, but is typically called from an instance. When it's called from an instance both instance and owner are set (and you can work out owner from instance so it seems kinda pointless). But when called from a class, only owner is set – which is why it's there.

    This is only needed for __get__ because it's the only one that can be called on a class. If you set the class value you set the descriptor itself. Similarly for deletion. Which is why the owner isn't needed there.

    How would I call/use this example?

    Well, here's a cool trick using similar classes:

    class Celsius:
    
        def __get__(self, instance, owner):
            return 5 * (instance.fahrenheit - 32) / 9
    
        def __set__(self, instance, value):
            instance.fahrenheit = 32 + 9 * value / 5
    
    
    class Temperature:
    
        celsius = Celsius()
    
        def __init__(self, initial_f):
            self.fahrenheit = initial_f
    
    
    t = Temperature(212)
    print(t.celsius)
    t.celsius = 0
    print(t.fahrenheit)
    

    (I'm using Python 3; for python 2 you need to make sure those divisions are / 5.0 and / 9.0). That gives:

    100.0
    32.0
    

    Now there are other, arguably better ways to achieve the same effect in python (e.g. if celsius were a property, which is the same basic mechanism but places all the source inside the Temperature class), but that shows what can be done...

提交回复
热议问题