The proper way of completely overriding attribute access in Python?

房东的猫 提交于 2019-12-06 13:03:38

问题


This naive class attempts to mimic the attribute access of basic python objects. dict and cls explicitly stores the attributes and the class. The effect is that accessing .x of an instance will return dict[x], or if that fails, cls.x. Just like normal objects.

class Instance(object):
    __slots__ = ["dict", "cls"]
    def __getattribute__(self, key):
        try:
            return self.dict[key]
        except KeyError:
            return getattr(self.cls, key)
    def __setattr__(self, key, value):
        if key == "__class__":
            self.cls = value
        else:
            self.dict[key] = value

But it's nowhere near as simple as that. One obvious issue is the complete disregard for descriptors. Just imagine that cls has properties. Doing Instance.some_property = 10 should access the property as defined in cls, but will instead happily set some_property as an attribute in dict.

Then there is the issue of binding methods of cls to instances of Instance, and possibly more that I don't even know.

There seem to be a lot of details to get the above class to function as close to python objects as possible, and the docs for descriptors I've read so far hasn't made it clear how to get, simply put, everything right.

What I am asking for is a reference for implementing a complete replacement for python's attribute access. That is, the above class, but correct.


回答1:


Well, I needed this answer so I had to do the research. The below code covers the following:

  • data-descriptors are given precedence both when setting and getting attributes.
  • non-data descriptors are properly called in __getattribute__

There may be typos in the code below as I had to translate it from an internal project. And I am not sure if it is 100% like python objects, so if anyone could spot errors that would be great.

_sentinel = object()

def find_classattr(cls, key):
  for base in cls.__mro__: # Using __mro__ for speed.
    try: return base.__dict__[key]
    except KeyError: pass
  return _sentinel

class Instance(object):
  __slots__ = ["dict", "cls"]
  def __init__(self, d, cls):
    object.__setattr__(self, "dict", d)
    object.__setattr__(self, "cls", cls)
  def __getattribute__(self, key):
    d = object.__getattribute__(self, "dict")
    cls = object.__getattribute__(self, "cls")
    if key == "__class__":
      return cls
    # Data descriptors in the class, defined by presence of '__set__',
    # overrides any other kind of attribute access.
    cls_attr = find_classattr(cls, key)
    if hasattr(cls_attr, '__set__'):
      return cls_attr.__get__(self, cls)
    # Next in order of precedence are instance attributes.
    try:
      return d[key]
    except KeyError:
      # Finally class attributes, that may or may not be non-data descriptors.
      if hasattr(cls_attr, "__get__"):
        return cls_attr.__get__(self, cls)
      if cls_attr is not _sentinel:
        return cls_attr

    raise AttributeError("'{}' object has no attribute '{}'".format(
      getattr(cls, '__name__', "?"), key))
  def __setattr__(self, key, value):
    d = object.__getattribute__(self, "dict")
    cls = object.__getattribute__(self, "cls")
    if key == "__class__":
      object.__setattr__(self, "cls", value)
      return

    # Again, data descriptors override instance attributes.
    cls_attr = find_classattr(cls, key)
    if hasattr(cls_attr, '__set__'):
      cls_attr.__set__(self, value)
    else:
      d[key] = value

Funny thing is I realized I had written exactly the same stuff before a couple of years ago, but the descriptor protocol is so arcane I had forgotten it since.

EDIT: Fixed bug where using getattr to find an attribute on the class would call it's descriptors on the class level (i.e. without the instance). Replaced it with a method that looks directly in the __dict__ of the bases.



来源:https://stackoverflow.com/questions/12709194/the-proper-way-of-completely-overriding-attribute-access-in-python

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