Python | Доступ к переменной из родительского класса медленнее, чем доступ к той же переменной из дочернего класса с использованием объекта дочернего класса? - PullRequest
0 голосов
/ 27 апреля 2020
class Animal():
    def add(self):
        self.weight = 10
        self.color = 'Black'

class Bird(Animal):
    def add(self):
        self.feather_type = 'Long'
        super().add()


b = Bird()
b.add()
print(b.weight)
print(b.color)
print(b.feather_type) // will this be faster than above 2 statements ?

Является ли доступ к переменным из родительского класса, из объектов дочернего класса более медленным, чем доступ к переменным напрямую из дочернего класса?

Если имеется много переменных 10+ (включая массивы и объекты модели данных) ) в родительском классе, и каждый из них используется в дочернем классе, предлагается ли добавить их в каждый из дочерних классов и удалить из родительского класса для повышения производительности? (Звучит глупо, когда я пишу это, поскольку это противоречит всей концепции наследования)

Лучше ли хранить их как локальные переменные в функциях дочерних классов, а затем обращаться к ним? (Если он используется несколько раз)

Как и в коде, переменные не инициализируются в методе __init__. Это сделает программу медленнее, в этом сценарии? Это делается таким образом, потому что все атрибуты класса не являются необходимыми для всех операций операции. Поэтому, когда и как требуется, они инициализируются и используются. (Примечание: необходимо убедиться, что необходимые атрибуты созданы перед выполнением операции).

Если в родительском классе много переменных 10+ (включая массивы и объекты модели данных), и каждая из них используется в дочернем классе, то предлагается добавить их в каждый дочерний класс и удалить из родительский класс для лучшей производительности? (Звучит глупо, когда я пишу это, поскольку это противоречит всей концепции наследования)

Ответы [ 3 ]

2 голосов
/ 27 апреля 2020

Давайте сделаем тест и узнаем. Для тестирования подобных вещей я хотел бы использовать модуль, который я написал: timerit .

import timerit


class Animal():
    def add(self):
        self.weight = 10
        self.color = 'Black'


class Bird(Animal):
    def add(self):
        self.feather_type = 'Long'
        super().add()


b = Bird()
b.add()

ti = timerit.Timerit(1000000, bestof=100, verbose=2)
for timer in ti.reset('Access weight'):
    with timer:
        b.weight

for timer in ti.reset('Access color'):
    with timer:
        b.color

for timer in ti.reset('Access feather_type'):
    with timer:
        b.feather_type

Это приводит к

Timed Access weight for: 1000000 loops, best of 100
    time per loop: best=347.005 ns, mean=370.222 ± 17.2 ns
Timed Access color for: 1000000 loops, best of 100
    time per loop: best=350.992 ns, mean=367.194 ± 9.5 ns
Timed Access feather_type for: 1000000 loops, best of 100
    time per loop: best=348.984 ns, mean=367.680 ± 11.9 ns

Итак, нет, похоже, между ними нет существенной разницы.

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

Атрибуты экземпляра хранятся в экземпляре, поэтому наследование здесь совершенно не имеет значения. Помните, что когда метод родительского класса вызывается для дочернего экземпляра, метод, который метод получает в качестве первого аргумента (self), является дочерним экземпляром:

>>> class Foo:
...    def __init__(self):
...        print("In Foo.__init__, self is {}".format(self))
... 
>>> class Bar(Foo): pass
... 
>>> b = Bar()
In Foo.__init__, self is <__main__.Bar object at 0x7fd230244f60>
>>> 

Лучше ли хранить их как локальная переменная в дочернем классе функций, а затем получить к ним доступ? (В случае, если он используется несколько раз)

Только для атрибутов, которые используются в ограниченном l oop (разрешение атрибута немного медленнее, чем разрешение локальной переменной) AND Профилирование показывает, что в этой точке есть узкое место. Но также не ожидайте значительного ускорения ...

На практике, если вам приходится беспокоиться о таких микрооптимизациях, скорее всего, реальное решение - либо переосмыслить ваш дизайн и реализацию, либо повторно реализовать критические разделы. кода в C.

Как и в коде, переменные не инициализируются в методе init . Это замедлит работу программы

Не совсем, за исключением накладных расходов на вызов obj.add(). НО :

Это делается таким образом, потому что все атрибуты класса не являются необходимыми для всех операций. Поэтому, когда и как требуется, они инициализируются и используются.

Неправильный дизайн. Если вы хотите отложенную инициализацию для какого-либо атрибута, используйте property или некоторый пользовательский дескриптор . Это гарантирует, что ваши объекты всегда будут иметь ожидаемые атрибуты, что бы ни случилось.

Если в родительском классе

* много переменных 10+ (включая массивы и объекты модели данных) 1038 * «Много» атрибутов в классе часто является запахом дизайна. Здесь нет строгого и быстрого правила - в некоторых случаях требуется немало атрибутов - но есть вероятность, что у вашего класса слишком много ответных возможностей, и его лучше было бы реорганизовать в набор наименьших классов, каждый из которых имеет одну четко определенную ответственность.

И нет, копирование и вставка инициализации атрибутов родительского класса в дочерний класс в любом случае не ускорит ваш код - это просто сделает обслуживание бесполезно сложнее и увеличит риски появления ошибок при смене родителя или детский класс. Я бы лично обозначил это как полный WTF, но я не известен своим врожденным чувством дипломатии; -)

EDIT :

На самом деле одна вещь, которую я здесь не упомянул, другая главная причина, по которой я создаю переменные вне init, заключается в том, что я использую шаблон фабричного дизайна в своем коде. И я динамически создаю классы, используя

def _create(pkg): 
    exec('import api.' + pkg + 'Helper as Creator'); 
    return eval('Creator' + '.' + pkg + 'Helper()' )

Err ... Возможно, вам будет интересно узнать о некоторых функциях Python, таких как importlib.import_module и getattr. Как правило, всякий раз, когда вы рассматриваете возможность использования eval или exec, убедитесь, что есть лучшее (=> более безопасное, более явное и гораздо более удобное в обслуживании) решение. Я использую Python уже более 20 лет (для личных и профессиональных проектов), и мне все еще нужно найти случай, когда у меня была законная причина использовать eval или exec.

Кроме того, вы не опубликовали ту часть кода, которая фактически «динамически создает класс», но динамическое создание классов не налагает никаких ограничений или обходных путей (по сравнению с определениями «stati c») - вы все равно можете дать своему классу правильный инициализатор, свойства или любой другой пользовательский дескриптор et c. Если вы также используете exec̀̀ / eval для создания своего класса, то и здесь есть гораздо лучшие способы.

Мои 2 цента ...

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

Атрибуты экземпляра будут созданы на самом объекте экземпляра. Родительские / дочерние отношения здесь не имеют значения.

b.add()

Это вызывает:

def add(self):
    ...

self здесь будет b. Теперь это вызывает super.add(), что снова:

def add(self):
    ...

self здесь все равно будет b. Все атрибуты добавляются непосредственно к одному и тому же объекту.

Возможно, что разрешение вызова super().add() будет небольшим расходом, но оно абсолютно незначительно. Так было бы любое различие в доступе к атрибутам, если бы оно существовало.

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