42

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

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

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

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

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

4

8 回答 8

70
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

如果您不需要设置属性,只需使用命名元组,例如。

>>> 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'
于 2013-04-26T13:28:31.453 回答
6

聚会迟到了,但找到了两个很好的资源来更好地解释这一点(恕我直言)。

正如这里所解释的,您应该使用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__:

另一个资源解释说,仅__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     
于 2017-01-09T14:03:32.507 回答
4
class A(object):
  def __init__(self):
     self.data = {'a': 'v1', 'b': 'v2'}
  def __getattr__(self, attr):
     try:
       return self.data[attr]
     except Exception:
       return "not found"


>>>a = A()
>>>print a.a
v1
>>>print a.c
not found
于 2013-04-26T13:35:00.880 回答
3

因此,我喜欢接受这个。

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

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

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

a = A()
a['a'] = 2
a.b = 5
print a.a, a['b'] # prints 2 5
于 2013-04-26T13:54:13.633 回答
2

我想出了对@glglgl's answer的扩展,它可以处理原始字典中的嵌套字典和字典内部列表:

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])
于 2015-06-11T17:05:17.743 回答
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__适合您的任何方式访问这些示例中的内容。

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

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

>>> 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-mode proxy 只需要一点“引导”来在初始化时访问它自己范围内的 var,然后再设置它的任何 var。初始化后,代理没有理由为自己创建更多的变量,因此通过将所有属性调用推迟到它的包装对象,它会很好。

代理本身拥有的任何属性仍然可供其自身和其他调用者访问,因为只有在无法立即在对象上找到属性时才会调用魔术属性函数。

希望这种方法对任何喜欢直接解决__get/set/attr__() __init__()挫折的人都有好处。

于 2020-03-06T08:40:14.930 回答
0

我觉得这个工具更酷

class MyClass(object):
    def __init__(self):
        self.data = {'a': 'v1', 'b': 'v2'}
    def __getattr__(self,key):
        return self.data.get(key,None)
于 2016-10-12T08:18:38.170 回答
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
于 2016-06-01T07:33:58.237 回答