Python:如何实现__getattr __()?

时间:2013-04-26 13:26:51

标签: python

我的班级有一个词典,例如:

class MyClass(object):
    def __init__(self):
        self.data = {'a': 'v1', 'b': 'v2'}

然后我想使用dict的密钥和MyClass实例来访问dict,例如:

ob = MyClass()
v = ob.a   # Here I expect ob.a returns 'v1'

我知道这应该由__getattr__实现,但我是Python的新手,我不知道如何实现它。

8 个答案:

答案 0 :(得分:57)

class MyClass(object):

    def __init__(self):
        self.data = {'a': 'v1', 'b': 'v2'}

    def __getattr__(self, attr):
        return self.data[attr]

>>> ob = MyClass()
>>> v = ob.a
>>> v
'v1'

在实施__setattr__时要小心,但您需要做一些修改:

class MyClass(object):

    def __init__(self):
        # prevents infinite recursion from self.data = {'a': 'v1', 'b': 'v2'}
        # as now we have __setattr__, which will call __getattr__ when the line
        # self.data[k] tries to access self.data, won't find it in the instance 
        # dictionary and return self.data[k] will in turn call __getattr__
        # for the same reason and so on.... so we manually set data initially
        super(MyClass, self).__setattr__('data', {'a': 'v1', 'b': 'v2'})

    def __setattr__(self, k, v):
        self.data[k] = v

    def __getattr__(self, k):
        # we don't need a special call to super here because getattr is only 
        # called when an attribute is NOT found in the instance's dictionary
        try:
            return self.data[k]
        except KeyError:
            raise AttributeError

>>> ob = MyClass()
>>> ob.c = 1
>>> ob.c
1

如果您不需要设置属性,只需使用namedtuple 例如

>>> from collections import namedtuple
>>> MyClass = namedtuple("MyClass", ["a", "b"])
>>> ob = MyClass(a=1, b=2)
>>> ob.a
1

如果你想要默认参数,你可以围绕它编写一个包装类:

class MyClass(namedtuple("MyClass", ["a", "b"])):

    def __new__(cls, a="v1", b="v2"):
        return super(MyClass, cls).__new__(cls, a, b)

或者它看起来可能更好:

def MyClass(a="v1", b="v2", cls=namedtuple("MyClass", ["a", "b"])):
    return cls(a, b)

>>> ob = MyClass()
>>> ob.a
'v1'

答案 1 :(得分:5)

晚会,但发现两个非常好的资源可以更好地解释这一点(恕我直言)。

正如here所述,您应该使用self.__dict__来访问__getattr__中的字段,以避免无限递归。提供的示例是:

def __getattr__(self, attrName):
  if not self.__dict__.has_key(attrName):
     value = self.fetchAttr(attrName)    # computes the value
     self.__dict__[attrName] = value
  return self.__dict__[attrName]

注意:在第二行(上图)中,更多的Pythonic方式(has_key显然甚至在Python 3中删除了):

if attrName not in self.__dict__:

other resource解释说,只有在对象中找不到属性时才调用__getattr__,如果有实现,hasattr始终返回True __getattr__。它提供了以下示例,以演示:

class Test(object):
    def __init__(self):
        self.a = 'a'
        self.b = 'b'

    def __getattr__(self, name):
        return 123456

t = Test()
print 'object variables: %r' % t.__dict__.keys()
#=> object variables: ['a', 'b']
print t.a
#=> a
print t.b
#=> b
print t.c
#=> 123456
print getattr(t, 'd')
#=> 123456
print hasattr(t, 'x')
#=> True     

答案 2 :(得分:3)

class A(object):
  def __init__(self):
     self.data = {'a': 'v1', 'b': 'v2'}
  def __getattr__(self, attr):
     try:
       return self.data[attr]
     except:
       return "not found"


>>>a = A()
>>>print a.a
v1
>>>print a.c
not found

答案 3 :(得分:3)

我喜欢这样做。

我从某个地方拿走了它,但我不记得在哪里。

class A(dict):
    def __init__(self, *a, **k):
        super(A, self).__init__(*a, **k)
        self.__dict__ = self

这使得对象的__dict__与其自身相同,因此属性和项目访问映射到同一个dict:

a = A()
a['a'] = 2
a.b = 5
print a.a, a['b'] # prints 2 5

答案 4 :(得分:1)

我想出了对@ glglgl的答案的扩展,它处理嵌套字典和词典中的原始词典中的列表:

class d(dict):
    def __init__(self, *a, **k): 
        super(d, self).__init__(*a, **k)
        self.__dict__ = self
        for k in self.__dict__:
            if isinstance(self.__dict__[k], dict):
                self.__dict__[k] = d(self.__dict__[k])
            elif isinstance(self.__dict__[k], list):
                for i in range(len(self.__dict__[k])):
                    if isinstance(self.__dict__[k][i], dict):
                        self.__dict__[k][i] = d(self.__dict__[k][i])

答案 5 :(得分:1)

一种解决您的__getattr__() / __setattr__()无限递归问题的简单方法

实施这些魔术方法中的一种通常很容易。但是,当同时覆盖它们两者时,将变得更加棘手。这篇文章的示例主要适用于这种较困难的情况。

在实现这两种魔术方法时,经常会遇到在类的__init__()构造函数中制定避免递归的策略的情况。这是因为需要为该对象初始化变量,但是每次尝试读取或写入这些变量都要经过__get/set/attr__(),这可能会在其中包含更多未设置的变量,从而导致更多徒劳的递归调用。

最重要的是要记住的一点是,__getattr__() 仅在无法在对象上找到该属性时才被运行时调用。问题是要获取定义的属性,而又不递归地触发这些功能。

另一点是__setattr__() 无论如何都会被调用。这是两个函数之间的重要区别,这就是为什么实现这两个属性方法可能很棘手的原因。

这是解决问题的一种基本模式。

class AnObjectProxy:
    _initialized = False # *Class* variable 'constant'.

    def __init__(self):
        self._any_var = "Able to access instance vars like usual."
        self._initialized = True # *instance* variable.

    def __getattr__(self, item):
        if self._initialized:
            pass # Provide the caller attributes in whatever ways interest you.
        else:
            try:
                return self.__dict__[item] # Transparent access to instance vars.
            except KeyError:
                raise AttributeError(item)

    def __setattr__(self, key, value):
        if self._initialized:
            pass # Provide caller ways to set attributes in whatever ways.
        else:
            self.__dict__[key] = value # Transparent access.

当类正在初始化并创建其实例变量时,两个属性函数中的代码都允许通过__dict__字典透明地访问对象的属性-__init__()中的代码可以创建和访问实例属性一般。调用属性方法时,它们仅访问已定义的self.__dict__,从而避免了递归调用。

对于self._any_var一旦被分配,__get/set/attr__()就不会被调用来再次找到它

剥去了多余的代码,这是最重要的两个部分。

...     def __getattr__(self, item):
...         try:
...             return self.__dict__[item]
...         except KeyError:
...             raise AttributeError(item)
... 
...     def __setattr__(self, key, value):
...         self.__dict__[key] = value

可以通过访问__dict__字典的这些方法来构建解决方案。为了实现对象代理,在此之前的代码中实现了两种模式:初始化和后初始化-下面是其更详细的示例。

答案中的其他示例在处理递归的所有方面时可能具有不同的有效性。一种有效的方法是直接在__dict__和其他需要尽早访问实例变量的地方访问__init__()。这可行,但可能有点冗长。例如,

self.__dict__['_any_var'] = "Setting..."

将在__init__()中工作。

我的帖子往往会有点冗长..在这之后,这只是多余的。您应该已经在上面的示例中有了这个想法。

在IDE的调试器中可以看到一些其他方法的缺点。当您单步执行代码时,他们可能会过于自省,并产生警告和错误恢复消息。即使使用独立运行良好的解决方案,您也可以看到这种情况的发生。当我说所有方面的递归时,这就是我在说的。

本文中的示例仅使用单个类变量来支持2种操作模式,这非常易于维护。

但是请注意::代理类需要两种操作模式来设置和代理内部对象。您不必具有两种操作模式。

您可以简单地合并代码,以适合您的任何方式访问这些示例中的__dict__

如果您的需求不包括两种操作模式,则您可能根本不需要声明任何类变量。只需采用基本模式并对其进行自定义。

这是现实世界中一个更接近的(但绝不完整)示例,该示例遵循以下模式的两模式代理:

>>> class AnObjectProxy:
...     _initialized = False  # This class var is important. It is always False.
...                           # The instances will override this with their own, 
...                           # set to True.
...     def __init__(self, obj):
...         # Because __getattr__ and __setattr__ access __dict__, we can
...         # Initialize instance vars without infinite recursion, and 
...         # refer to them normally.
...         self._obj         = obj
...         self._foo         = 123
...         self._bar         = 567
...         
...         # This instance var overrides the class var.
...         self._initialized = True
... 
...     def __setattr__(self, key, value):
...         if self._initialized:
...             setattr(self._obj, key, value) # Proxying call to wrapped obj.
...         else:
...             # this block facilitates setting vars in __init__().
...             self.__dict__[key] = value
... 
...     def __getattr__(self, item):
...         if self._initialized:
...             attr = getattr(self._obj, item) # Proxying.
...             return attr
...         else:
...             try:
...                 # this block facilitates getting vars in __init__().
...                 return self.__dict__[item]
...             except KeyError:
...                 raise AttributeError(item)
... 
...     def __call__(self, *args, **kwargs):
...         return self._obj(*args, **kwargs)
... 
...     def __dir__(self):
...         return dir(self._obj) + list(self.__dict__.keys())

在设置其任何变量之前,2-模式代理仅需要一点“引导”即可在初始化时访问其自身作用域中的变量。初始化后,代理没有理由为其自身创建更多的var,因此通过将所有属性调用都推迟到其包装的对象上可以很好地解决问题。

代理自身拥有的任何属性仍可被其自身和其他调用者访问,因为magic属性函数仅在无法立即在对象上找到属性时才被调用。

希望这种方法对任何喜欢直接解决他们的__get/set/attr__() __init__()挫败感的人都有益。

答案 6 :(得分:0)

您可以通过构造函数初始化您的类字典:

    def __init__(self,**data):

并按如下方式调用:

f = MyClass(**{'a': 'v1', 'b': 'v2'})

__setattr__中所有实例属性被访问(读取),需要使用其父(超级)方法声明,只需执行一次:

    super().__setattr__('NewVarName1', InitialValue)

或者

    super().__setattr__('data', dict())

此后,可以通常的方式访问或分配它们:

    self.data = data

__setattr__中的实例属性未被访问,可以通常的方式声明:

    self.x = 1

重写的__setattr__方法现在必须在其自身内部调用父方法,以便声明新的变量:

    super().__setattr__(key,value)

完整的课程如下:

class MyClass(object):
    def __init__(self, **data):
        # The variable self.data is used by method __setattr__
        # inside this class, so we will need to declare it 
        # using the parent __setattr__ method:
        super().__setattr__('data', dict())
        self.data = data            
        # These declarations will jump to
        # super().__setattr__('data', dict())
        # inside method __setattr__ of this class:
        self.x = 1
        self.y = 2

    def __getattr__(self, name):
    # This will callback will never be called for instance variables
    # that have beed declared before being accessed.
        if name in self.data:
            # Return a valid dictionary item:
            return self.data[name]
        else:
            # So when an instance variable is being accessed, and
            # it has not been declared before, nor is it contained
            # in dictionary 'data', an attribute exception needs to
            # be raised.
            raise AttributeError

    def __setattr__(self, key, value):
        if key in self.data:
            # Assign valid dictionary items here:
            self.data[key] = value
        else:
            # Assign anything else as an instance attribute:
            super().__setattr__(key,value)

测试:

f = MyClass(**{'a': 'v1', 'b': 'v2'})
print("f.a = ", f.a)
print("f.b = ", f.b)
print("f.data = ", f.data)
f.a = 'c'
f.d = 'e'
print("f.a = ", f.a)
print("f.b = ", f.b)
print("f.data = ", f.data)
print("f.d = ", f.d)
print("f.x = ", f.x)
print("f.y = ", f.y)
# Should raise attributed Error
print("f.g = ", f.g)

输出:

f.a =  v1
f.b =  v2
f.data =  {'a': 'v1', 'b': 'v2'}
f.a =  c
f.b =  v2
f.data =  {'a': 'c', 'b': 'v2'}
f.d =  e
f.x =  1
f.y =  2
Traceback (most recent call last):
  File "MyClass.py", line 49, in <module>
    print("f.g = ", f.g)
  File "MyClass.py", line 25, in __getattr__
    raise AttributeError
AttributeError

答案 7 :(得分:0)

我觉得这个工具很酷

class MyClass(object):
    def __init__(self):
        self.data = {'a': 'v1', 'b': 'v2'}
    def __getattr__(self,key):
        return self.data.get(key,None)
相关问题