Как написать эффективную функцию перегрузки __dict__? - PullRequest
2 голосов
/ 16 мая 2019

Я хочу реализовать функцию to_dict, которая ведет себя аналогично встроенному атрибуту __dict__, но позволяет мне иметь собственную логику. (Он используется для построения DataFrame панд. См. Пример ниже.)

Однако я обнаружил, что моя to_dict функция примерно на 25% медленнее, чем __dict__ даже когда они делают то же самое. Как я могу улучшить свой код?

class Foo:
    def __init__(self, a,b,c,d):
        self.a = a
        self.b = b
        self.c = c
        self.d = d

    def to_dict(self):
        return {
            'a':self.a,
            'b':self.b,
            'c':self.c,
            'd':self.d,
        }

list_test = [Foo(i,i,i,i)for i in range(100000)]

%%timeit
pd.DataFrame(t.to_dict() for t in list_test)
# Output: 199 ms ± 4.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
pd.DataFrame(t.__dict__ for t in list_test)
# Output: 156 ms ± 948 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Отступление к этому вопросу, но связанное с моей конечной целью: каков наиболее эффективный способ создания панды DataFrame из списка пользовательских объектов? Мой текущий подход взят из https://stackoverflow.com/a/54975755/1087924

1 Ответ

3 голосов
/ 16 мая 2019

__dict__ не «конвертирует» объект в dict (в отличие от __int__, __str__ и т. Д.), Именно там хранятся атрибуты (доступные для записи) объекта.

Я думаю, что ваша реализация достаточно эффективна. Рассмотрим этот упрощенный пример:

import dis

class Foo:
    def __init__(self, a):
        self.a = a
    def to_dict(self):
        return {'a': self.a}

foo = Foo(1)

dis.dis(foo.to_dict)
dis.dis('foo.__dict__')

Мы можем видеть, что Python просматривает атрибуты и каждый раз создает новый dict (плюс вам нужно позвонить на .to_dict, не показанный здесь):

  7           0 LOAD_CONST               1 ('a')
              2 LOAD_FAST                0 (self)
              4 LOAD_ATTR                0 (a)
              6 BUILD_MAP                1
              8 RETURN_VALUE

при доступе к существующему атрибуту намного проще:

  1           0 LOAD_NAME                0 (foo)
              2 LOAD_ATTR                1 (__dict__)
              4 RETURN_VALUE

Однако вы можете сохранить свое пользовательское представление в экземпляре, получив тот же точный байт-код, что и с __dict__, но тогда вам нужно будет корректно обновить его при всех изменениях до Foo (что будет стоить некоторой скорости и памяти). Если в вашем случае использования обновления встречаются редко, это может быть приемлемым компромиссом.

В вашем примере, простой вариант - переопределить __getattribute__, но я предполагаю, что Foo имеет другие атрибуты, поэтому иметь сеттеры, вероятно, будет удобнее:

class Foo:
    def __init__(self, a):
        self.dict = {}
        self.a = a

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = value
        self.dict['a'] = value

foo = Foo(1)
print(foo.dict)  # {'a': 1}
foo.a = 10
print(foo.dict)  # {'a': 10}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...