Python класс с фабрикой атрибутов по умолчанию - PullRequest
0 голосов
/ 12 апреля 2020

Я хочу создать класс python, который выполняет определенный метод при каждом обращении к атрибуту, который не существует. Конечная цель - создать объект, который загружает атрибуты из файловой системы, если они еще не загружены.

Моя текущая идея - заменить атрибут __dict__ на defaultdict и переопределить __getattr__ метод с методом доступа к атрибуту __dict__. Это выглядит так:

class MyClass():
    def __init__(self):
        self.__dict__ = defaultdict(lambda: "Now we would load and return the object...", 
                                    self.__dict__)
        self.attribute = "An attribute"

    def __getattr__(self, *args, **kwargs):
        return self.__dict__.__getitem__(*args, **kwargs)

Желаемое поведение выглядит следующим образом:

>>> a = MyClass()
>>> a.attribute
'An attribute'
>>> a.non_existent_attribute
'Now we would load and return the object...'

Насколько я могу судить, это работает. Теперь мне интересно, какова скрытая стоимость этой процедуры и существует ли более элегантный способ ее реализации.

  • Можно ли ожидать, что переопределение __getattr__ окажет значительное влияние на производительность вычислений?
  • Существует ли возможная реализация, которая включается только в случае возникновения ошибки, поскольку атрибут не существует? (Тогда объект не будет вести себя по-другому при доступе к существующим атрибутам.)
  • Будет ли теперь учитываться defaultdict при каждом обращении к атрибуту или есть другие способы доступа к атрибутам, которые я пропустил? (Будет ли эта реализация вызывать ошибки, если она заменяет обычный объект?)
  • Существуют ли риски, связанные с этим подходом, которые я мог упустить?

1 Ответ

1 голос
/ 13 апреля 2020

Более простое определение, которое не требует изменения значения self.__dict__:

class MyClass():    
    def _load_attribute(self, name):
        """Load value for attribute <name> from disk"""

    def __getattr__(self, name):
        value = self._load_attribute(name)
        setattr(self, name, value)
        return value


m = MyClass()
print(m.foo)  # Calls m._load_attribute("foo") from m.__getattr_ to set m.foo
print(m.foo)  # Accesses m.foo directly, without calling m.__getattr__

__getattr__, вызывается только для атрибутов, которые не найдены с помощью обычного алгоритма поиска; после вызова setattr атрибут будет найден нормально, поэтому __getattr__ будет вызываться только один раз для каждого атрибута.

...