Почти все время не следует использовать self.__dict__
.
Если вы обращаетесь к атрибуту, например self.client
, т.е. имя атрибута известно и фиксировано, то единственная разница между это и self.__dict__['client']
означает, что последний не будет искать атрибут в классе, если он отсутствует в экземпляре. Существует очень редко какая-либо причина, чтобы сделать это, но разница продемонстрирована ниже:
>>> class A:
... b = 3 # class attribute, not an instance attribute
...
>>> A.b # the class has this attribute
3
>>> a = A()
>>> a.b # the instance doesn't have this attribute, fallback to the class
3
>>> a.__dict__['b'] # the instance doesn't have this attribute, but no fallback
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'b'
Основной вариант использования для self.__dict__
- это когда вы не хотите получить доступ фиксированное, известное имя атрибута. Почти во всем коде вы всегда знаете, к какому атрибуту вы хотите получить доступ; и если вам нужно что-то динамически искать, используя неизвестную строку, вы должны создать словарь самостоятельно и написать self.that_dict[key]
вместо self.__dict__[key]
.
Так что единственные случаи, когда вы действительно должны использовать __dict__
когда вы пишете код, который должен работать независимо от того, какие атрибуты может иметь экземпляр; то есть вам нужен код, который будет работать, даже если вы измените структуру класса или имена его атрибутов, или код, который будет работать для нескольких классов с разными структурами. Ниже я приведу один пример.
Метод __repr__
Метод __repr__
предназначен для возврата строки, представляющей экземпляр, для удобства программиста при использовании REPL. В целях отладки / тестирования эта строка обычно содержит информацию о состоянии объекта. Вот обычный способ его реализации:
class Foo:
def __init__(self, foo, bar, baz):
self.foo = foo
self.bar = bar
self.baz = baz
def __repr__(self):
return 'Foo({!r}, {!r}, {!r})'.format(self.foo, self.bar, self.baz)
Это означает, что если вы напишите obj = Foo(1, 'y', True)
для создания экземпляра, тогда repr(obj)
будет строкой "Foo(1, 'y', True)"
, что удобно, так как она показывает экземпляр все состояние, а также сама строка - это Python код, который создает экземпляр с таким же состоянием.
Но с вышеприведенной реализацией есть несколько проблем: мы должны изменить это, если атрибуты класса изменятся, это не даст полезных результатов для экземпляров подклассов, и нам придется написать много одинакового кода для разных классов с разными атрибутами. Если вместо этого мы используем __dict__
, мы сможем решить все эти проблемы:
def __repr__(self):
return '{}({})'.format(
self.__class__.__name__,
', '.join('{}={!r}'.format(k, v) for k, v in self.__dict__.items())
)
Теперь repr(obj)
будет Foo(foo=1, bar='y', baz=True)
, что также показывает все состояние экземпляра и также является исполняемым Python код. Этот обобщенный метод __repr__
все равно будет работать, если структура Foo
изменится, он может быть разделен между несколькими классами посредством наследования и возвращает исполняемый код Python для любого класса, атрибуты которого принимаются в качестве аргументов ключевых слов __init__
.