Use cases for property vs. descriptor vs. __getattribute__

試著忘記壹切 提交于 2019-12-03 09:06:44

问题


The question refers to which one is preferable to be used in which use case, not about the technical background.

In python, you can control the access of attributes via a property, a descriptor, or magic methods. Which one is most pythonic in which use case? All of them seem to have the same effect (see the examples below).

I am looking for an answer like:

  • Property: Should be used in case of …
  • Descriptor: In the case of … it should be used instead of a property.
  • Magic method: Only use if ….

Example

A use case would be an attribute that might not be able to be set in the __init__ method, for example because the object is not present in the database yet, but at a later time. Each time the attribute is accessed, it should be tried to be set and returned.

As an example that works with Copy&Paste in the Python shell, there is a class that wants to present its attribute only the second time it is asked for it. So, which one is the best way, or are there different situations one of them is preferable? Here are the three ways to implement it:

With Property::

class ContactBook(object):
    intents = 0

    def __init__(self):
        self.__first_person = None

    def get_first_person(self):
        ContactBook.intents += 1
        if self.__first_person is None:
            if ContactBook.intents > 1:
                value = 'Mr. First'
                self.__first_person = value
            else:
                return None
        return self.__first_person

    def set_first_person(self, value):
        self.__first_person = value

    first_person = property(get_first_person, set_first_person)

With __getattribute__::

class ContactBook(object):
    intents = 0

    def __init__(self):
        self.first_person = None

    def __getattribute__(self, name):
        if name == 'first_person' \
                and object.__getattribute__(self, name) is None:
            ContactBook.intents += 1
            if ContactBook.intents > 1:
                value = 'Mr. First'
                self.first_person = value
            else:
                value = None
        else:
            value = object.__getattribute__(self, name)
        return value

Descriptor::

class FirstPerson(object):
    def __init__(self, value=None):
        self.value = None

    def __get__(self, instance, owner):
        if self.value is None:
            ContactBook.intents += 1
            if ContactBook.intents > 1:
                self.value = 'Mr. First'
            else:
                return None
        return self.value


class ContactBook(object):
    intents = 0
    first_person = FirstPerson()

Each one of it has this behavior::

book = ContactBook()
print(book.first_person)
# >>None
print(book.first_person)
# >>Mr. First

回答1:


Basically, use the simplest one you can. Roughly speaking, the order of complexity/heavy-duty-ness goes: regular attribute, property, __getattr__, __getattribute__/descriptor. (__getattribute__ and custom descriptors are both things you probably won't need to do very often.) This leads to some simple rules of thumb:

  • Don't use a property if a normal attribute will work.
  • Don't write your own descriptor if a property will work.
  • Don't use __getattr__ if a property will work.
  • Don't use __getattribute__ if __getattr__ will work.

Stated a bit more specifically: use a property to customize handling of one or a small set of attributes; use __getattr__ to customize handling of all attributes, or all except a small set; use __getattribute__ if you were hoping to use __getattr__ but it doesn't quite work; write your own descriptor class if you are doing something very complicated.

You use a property when you have one or a small set of attributes whose getting/setting you want to hook into. That is, you want things like obj.prop and obj.prop = 2 to secretly call a function that you write to customize what happens.

You would use __getattr__ when you want to do that for so many attributes that you don't actually want to define them all individually, but rather want to customize the whole attribute-access process as a whole. In other words, instead of hooking into obj.prop1, and obj.prop2, etc., you have so many that you want to be able to hook into obj.<anything>, and handle that in general.

However, __getattr__ still won't let you override what happens for attributes that really do exist, it just lets you hook in with a blanket handling for any use of attributes that would otherwise raise an AttributeError. Using __getattribute__ lets you hook in to handle everything, even normal attributes that would have worked without messing with __getattribute__. Because of this, using __getattribute__ has the potential to break fairly basic behavior, so you should only use it if you considered using __getattr__ and it wasn't enough. It also can have a noticeable performance impact. You might for instance need to use __getattribute__ if you're wrapping a class that defines some attributes, and you want to be able to wrap those attributes in a custom way, so that they work as usual in some situations but get custom behavior in other situations.

Finally, I would say writing your own descriptor is a fairly advanced task. property is a descriptor, and for probably 95% of cases it's the only one you'll need. A good simple example of why you might write your own descriptor is given here: basically, you might do it if you would otherwise have to write several propertys with similar behavior; a descriptor lets you factor out the common behavior to avoid code repetition. Custom descriptors are used, for instance, to drive systems like like Django and SQLAlchemy. If you find yourself writing something at that level of complexity you might need to write a custom descriptor.

In your example, property would be the best choice. It is usually (not always) a red flag if you're doing if name == 'somespecificname' inside __getattribute__. If you only need to specially handle one specific name, you can probably do it without stooping to the level of __getattribute__. Likewise, it doesn't make sense to write your own descriptor if all you write for its __get__ is something you could have written in a property's getter method.




回答2:


__getattribute__ is the hook that enables property (and other descriptors) to work in the first place and is called for all attribute access on an object. Consider it a lower-level API when a property or even a custom descriptor is not enough for your needs. __getattr__ is called by __getattribute__ when no attribute has been located through other means, as a fallback.

Use property for dynamic attributes with a fixed name, __getattr__ for attributes of a more dynamic nature (e.g. a series of attributes that map to values in an algorithmic manner).

Descriptors are used when you need to bind arbitrary objects to an instance. When you need to replace method objects with something more advanced for example; a recent example is a class-based decorator wrapping methods that needed to support additional attributes and methods on the method object. Generally, when you are still thinking in terms of scalar attributes, you don't need descriptors.



来源:https://stackoverflow.com/questions/22616559/use-cases-for-property-vs-descriptor-vs-getattribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!