Когда и зачем использовать self .__ dict__ вместо self.variable - PullRequest
1 голос
/ 07 февраля 2020

Я пытаюсь понять некоторую структуру кода, которая использует этот класс ниже:

class Base(object):

    def __init__(self, **kwargs):
        self.client = kwargs.get('client')
        self.request = kwargs.get('request')
    ...

    def to_dict(self):
        data = dict()

        for key in iter(self.__dict__): # <------------------------ this
            if key in ('client', 'request'):
                continue

            value = self.__dict__[key]
            if value is not None:
                if hasattr(value, 'to_dict'):
                    data[key] = value.to_dict()
                else:
                    data[key] = value
        return data

Хорошо, поэтому я понимаю, что он получает ключевые аргументы значения, передаваемые в базовый класс, например, Base(client="foo", request="bar") .

Моя путаница заключается в том, почему он использует self.__dict__, который превращает переменные внутри __init__ в диктовку (например, {"client": "foo", "request": "bar"}) вместо того, чтобы просто вызывать их с помощью self.client & self.request внутри других методов, Я делаю что-то неправильно? Когда и почему я должен использовать self.__dict__ вместо этого?

И, пожалуйста, где я могу найти надежное руководство по использованию / пониманию этих более сложных методов?

Заранее спасибо.

Ответы [ 2 ]

2 голосов
/ 07 февраля 2020

Почти все время не следует использовать 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__ .

1 голос
/ 07 февраля 2020

__dict__ содержит все переменных в классе. Возьмите следующий класс:

class A():
    def __init__(self, foo):
        self.foo = foo

    def new_var(self, bar):
        self.bar = bar

Тогда, в этом случае, обратите внимание:

a = A('var1')
print(a.__dict__) # {'foo': 'var1'}

b = A('var1')
b.new_var('var2')
b.foobar = 'var3'
print(b.__dict__) # {'foo': 'var1', 'bar': 'var2', 'foobar': 'var3'}

В вашем случае вы можете сделать либо или. __dict__ - это отличный способ получить все переменные, которые являются частью этого класса в текущем экземпляре, в котором он вызывается. Вы можете ознакомиться с документацией по __dict__ здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...