Подтвердить что ты не робот

Python: как реализовать __getattr __()?

Мой класс имеет dict, например:

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

4b9b3361

Ответ 1

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 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'

Ответ 2

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

Мне нравится это делать.

Я взял его откуда-то, но я не помню, где.

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

Поздно к партии, но нашел два действительно хороших ресурса, которые объясняют это лучше (IMHO).

Как объяснено в 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]

Примечание: во второй строке (выше) более Pythonic-путь был бы (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     

Ответ 5

Я вычислил расширение для ответа @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])

Ответ 6

Вы можете инициализировать словарь классов через конструктор:

    def __init__(self,**data):

И назовите его следующим образом:

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

Все атрибуты экземпляра , которые обращаются к (read) в __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

Я думаю, что это приложение более холодное

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