The proper way of completely overriding attribute access in Python?

时光总嘲笑我的痴心妄想 提交于 2019-12-04 19:29:39

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.

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