2013-04-26 3 views
24

Мой класс имеет Dict, например:Python: как реализовать __getattr __()?

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, я точно не знаю, как его реализовать.

+5

В идеале, не совсем. ;-) – delnan

+0

Вы, вероятно, на самом деле не хотите делать это на практике. Если ваши данные принадлежат к dict, используйте dict; если ваши данные принадлежат объекту, используйте объект. В любом случае, ['namedtuple'] (http://docs.python.org/3.3/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields), который работает вроде как легкий объект, может делать то, что вы хотите. –

+0

Помните, что '__getattr__' используется только для поиска отсутствующих атрибутов. – Kos

ответ

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 ** readonly? – TieDad

+0

@EvanLi да, если под этим вы подразумеваете 'ob.data ['a']'. Вы все равно можете установить 'ob.a = 1', но вместо этого будет устанавливать' ob .__ dict __ ['a'] '(словарь экземпляра, а не ваш!). Тогда он не будет вызывать '__getattr__' при доступе к' ob.a', так как '__getattr__' обходит, когда атрибут уже существует в экземпляре – jamylak

+0

Есть ли способ предотвратить выполнение * ob.a = 1 *? Может быть, реализовать \ __ setattr__ и повысить исключение? – TieDad

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

+1

И вы можете указать значение по умолчанию для возврата, если ключ не был найден. '.get (attr," not found ")' –

+0

@limelights "not found" +1 –

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

Я думал, что это сделал Алекс Мартелли, но я, вероятно, ошибаюсь, я нашел его здесь http://stackoverflow.com/a/14620633 Я считаю, что это происходит под именем 'AttrDict' – jamylak

+0

@jamylak Он намного старше. Я только что нашел https://github.com/brickZA/jsobject, который последний раз был изменен 3 года назад и заимствовал его у http://www.jiaaro.com/making-python-objects-that-act-like-javascrip/. Они используют имя 'JsObject'. – glglgl

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.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 
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] 

Примечание: во второй строке (выше), более питоновский способ был бы (has_key по-видимому, была даже удален в Python 3):

if attrName not in self.__dict__: 

Другой ресурс (http://farmdev.com/src/secrets/magicmethod/#introducing-getattr) объясняет, что __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  
Смежные вопросы