【问题标题】:Python | Is accessing a variable from parent class slower than accessing the same variable from child class, using a child class object?蟒蛇 |使用子类对象从父类访问变量是否比从子类访问相同变量慢?
【发布时间】:2020-04-27 13:57:27
【问题描述】:
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+(包括数组和数据模型对象),并且每个变量都在子类中使用,是否建议在每个子类中添加这些变量并从父类中删除更好的性能? (当我写这篇文章时听起来很愚蠢,因为这与整个继承概念相矛盾)

【问题讨论】:

  • 它不应该对访问值产生有意义的影响。唯一的区别在于初始化。
  • 当您可以通过基准测试证明差异很重要时,请担心它。 (你永远不会这样做。)在那之前,写出最易读、逻辑上你想要的和最容易维护的东西。
  • 顺便说一句,Dog.__init__ 什么都不做,可以省略。
  • @ezvine--如果两个 addfunctions 是实例方法,那么它们需要 self 作为参数。
  • @deceze 删除了 Dog 类本身,这两个类足以传达问题。实际上它不是__init__,而是一个单独的函数,变量被初始化,正如我在问题中提到的那样。

标签: python python-3.x class oop inheritance


【解决方案1】:

让我们对其进行基准测试并找出答案。为了测试这样的事情,我喜欢使用我编写的模块: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

所以,不,它们之间似乎没有任何显着差异。

【讨论】:

  • Timerit 模块看起来很有趣,所以我期待将来尝试它。
  • 这是证明。谢谢。
【解决方案2】:

实例属性存储在实例中,因此继承在这里完全无关紧要。请记住,当在子实例上调用父类方法时,该方法作为第一个参数 (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>
>>> 

将它们作为局部变量存储在子类函数中,然后访问它们会更好吗? (以防多次使用)

仅适用于紧密循环中使用的属性(属性解析比局部变量解析慢一点)AND 分析显示此时存在瓶颈。但也不要指望大幅加速......

实际上,如果您不得不担心这种微优化,真正的解决方案是重新考虑您的设计和实现,或者用 C 重新实现代码的关键部分。

在代码中,变量没有在 init 方法中初始化。这会使程序变慢吗

不是真的,除了调用obj.add() 的开销。 但是

这样做是因为,并非所有操作都需要类的所有属性。因此,在需要时,它们会被初始化和使用。

错误的设计。如果您想延迟初始化某些属性,use a property or some custom descriptor。这将确保您的对象无论发生什么都将始终具有预期的属性。

如果父类中有很多变量 10+(包括数组和数据模型对象)

类中的“很多”属性通常是一种设计气味。这里没有硬性规定——有些情况确实需要相当多的属性——但是你的类可能有太多的职责,最好重构为一组最小的类,每个类都有一个单一的、明确定义的职责。

不,将父类的属性初始化复制粘贴到子类无论如何都不会加速您的代码 - 它只会使维护变得更加困难,并增加在更改父类或子类时引入错误的风险。我个人认为这是一个完整的 WTF,但我并不以我与生俱来的外交意识而闻名 ;-)

编辑

实际上我在这里没有提到的一件事,我在 init 之外创建变量的另一个主要原因是因为我在我的代码中使用了工厂设计模式。我正在使用

动态创建类
def _create(pkg): 
    exec('import api.' + pkg + 'Helper as Creator'); 
    return eval('Creator' + '.' + pkg + 'Helper()' )

Err...您可能有兴趣了解 Python 的一些功能,例如 importlib.import_modulegetattr。作为一般规则,每当您考虑使用evalexec 时,请确保有更好的(=> 更安全、更明确且更易于维护)的解决方案。我已经使用 Python 20 多年了(用于个人和专业项目),我仍然需要找到一个我有正当理由使用 evalexec 的案例。

此外,您没有发布实际上“动态创建类”但动态创建类不会施加任何限制或解决方法(与“静态”定义相比)的代码部分 - 您仍然可以为您的类适当的初始化程序、属性或任何其他自定义描述符等。如果您还使用 exec̀̀ / eval 来构建您的类,那么这里还有更好的方法。

我的 2 美分...

【讨论】:

  • 实际上我没有在这里提到的一件事,我在 init 之外创建变量的另一个主要原因是因为我在我的代码中使用了工厂设计模式。我正在使用def _create(pkg): exec('import api.' + pkg + 'Helper as Creator'); return eval('Creator' + '.' + pkg + 'Helper()' ) 动态创建类,然后调用所需的函数来初始化变量(此处为add 函数)。我认为这一点与我提出的问题无关。但既然你提到使用property,我想我会提到这一点。
【解决方案3】:

实例属性都将在实例对象本身上创建。父子关系在那里无关紧要。

b.add()

这调用:

def add(self):
    ...

self 这里将是b。现在调用super.add(),又是:

def add(self):
    ...

self 仍然是b。所有属性都直接添加到同一个对象中。

可以说解决super().add() 调用将是一个轻微的开销,但它绝对可以忽略不计。如果存在的话,访问属性会有什么不同。

【讨论】:

    猜你喜欢
    • 2021-05-05
    • 1970-01-01
    • 1970-01-01
    • 2011-04-08
    • 2013-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多