2013-04-26 70 views
24

我班有一个快译通,例如:Python:如何实现__getattr __()?

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

然后我想用字典的键与MyClass的实例来访问字典,例如:

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

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

+5

理想的情况下,一点都没有。 ;-) – delnan 2013-04-26 13:28:18

+0

你可能不想在实践中这样做。如果您的数据属于字典,请使用字典;如果你的数据属于一个对象,使用一个对象。无论如何,['namedtuple'](http://docs.python.org/3.3/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields),其工作方式类似于轻量级的对象,可能会做你想做的。 – 2013-04-26 13:30:50

+0

请记住'__getattr__'仅用于缺少属性查找。 – Kos 2013-04-26 13:37:04

回答

43
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' 
+0

如果我只实现\ __ getattr__,是** ob.a **只读? – TieDad 2013-04-26 13:33:34

+0

@EvanLi是的,如果你的意思是'ob.data ['a']'。你仍然可以设置'ob.a = 1',但是这会设置'ob .__ dict __ ['a']'(实例的字典,不是你的!)。那么当你访问'ob.a'时,它不会调用'__getattr__',因为'__getattr__'在实例中已经存在的属性时被绕过 – jamylak 2013-04-26 13:35:23

+0

有没有办法阻止* ob.a = 1 *?也许要实现\ __ setattr__并引发异常? – TieDad 2013-04-26 13:39:05

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 
+0

在这种情况下引发异常可能会更好,而不是返回'None'。无论如何,你的代码可以缩短为'return self.data.get(attr)' – jamylak 2013-04-26 13:36:03

+1

如果找不到密钥,你可以指定一个默认值。 '.get(attr,“not found”)' – 2013-04-26 13:39:52

+0

@limelights“找不到”+1 – 2013-04-26 14:03:57

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 
+0

我认为这是由Alex Martelli所做的,但我可能是错的,我在这里找到它http://stackoverflow.com/a/14620633我相信它的名字'AttrDict' – jamylak 2013-04-26 14:03:21

+0

@jamylak它是更古老。我刚刚找到了3年前最后更改的https://github.com/brickZA/jsobject,并从http://www.jiaaro.com/making-python-objects-that-act-like-javascrip/中借用它。他们使用名称'JsObject'。 – glglgl 2013-11-15 08:22:35

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]) 
0

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

def __init__(self,**data): 

,并调用它,如下所示:

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

所有实例的属性被访问(读)在__setattr__,需要利用其母公司(超)方法是宣布,只一次:

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

或者

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

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

self.x = 1 

self.data = data 

属性和实例属性不被访问在__setattr__,可以以通常的方式被声明

重写的__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 
0

我想,这是实现冷却器

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

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

http://western-skies.blogspot.com.br/2008/02/complete-example-of-getattr-in-python.html中所述,您应该使用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] 

注意:在第二行(上图),一个更Python方法是(has_key显然在Python 3甚至移除):

if attrName not in self.__dict__: 

另一个资源(http://farmdev.com/src/secrets/magicmethod/#introducing-getattr)解释说__getattr__只有在该对象中没有找到该属性时才会被调用,并且hasattr总是返回True如果存在的实现。它提供了下面的例子,来证明:

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