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