【问题标题】:Re-assigning Instance Variables to Local Method Variables将实例变量重新分配给局部方法变量
【发布时间】:2018-10-24 09:23:09
【问题描述】:

我已经多次看到以下情况,其中实例变量(例如obj_fooobj_bar)被重新分配为本地方法变量(例如在call 内):

class Example:
    def __init__(self, obj_foo, obj_bar):
        self.obj_foo = obj_foo
        self.obj_bar = obj_bar

    def call(self):
        obj_foo, obj_bar = self.obj_foo, self.obj_bar

        obj_foo.do_something()
        obj_bar.do_something_else()

我不确定这是惯例(易于阅读)还是有更重要的目的?

这是不好的做法吗?

这会影响性能吗?

【问题讨论】:

  • 可能只是为了避免在变量名称前使用self.,因为这两个变量将引用相同的对象
  • 至于好坏,在相当极端的情况下可能有一些微优化的理由。但如果只是为了让它看起来更像 C++ 或避免作家抽筋,那是个坏主意。首先,不清楚您指的是实例对象,其次,如果重新分配它(obj_foo = other_thing),也不清楚作者是否打算将其仅用于本地或是否是错误。这降低了可维护性,恕我直言。

标签: python class namespaces


【解决方案1】:

通常没有理由这样做,但在某些情况下可能是:

  • 更快(因为访问局部变量很快)
  • 更易于阅读(因为它更短)

速度可能是这里更重要的因素。访问成员变量涉及各种机制(请参阅__getattr____getattribute____dict__descriptors),这些机制需要一些时间才能解决。此外,变量的 getter 可能会做一些更昂贵的事情。

另一方面,局部变量在 CPython 中是在编译时优化的,所以实际上没有在 __dict__ 中查找名为 'obj_foo' 的变量,而是解释器只选择第一个局部变量,因为它知道obj_foo 是第一个局部变量,无需搜索名称。

因此,如果一个成员变量在同一个函数中被多次使用,并且分析表明访问该成员变量需要大量时间,则改用局部变量可能会很有用。


通常,这并没有太大的区别,但这里有一个例子来说明这个想法:

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

    def f(self):
        for i in range(100):
            self.x()

class B:
    def __init__(self,x):
        self.x=x

    def f(self):
        x=self.x
        for i in range(100):
            x()

时间差不多,但有一些区别:

>>> timeit.timeit('a.f()', setup='a=A(lambda:None)', globals=locals())
13.119033042000638
>>>
>>> timeit.timeit('b.f()', setup='b=B(lambda:None)', globals=locals())
10.219889547632562

恕我直言,在这种情况下,差异几乎不足以证明添加那一行代码是合理的。

【讨论】:

    【解决方案2】:

    您可以这样做只是为了避免每次都写出self

    但是,这样做也可能有一个更重要的原因:它可以完全改变语义。示例:

    def __init__(self, x):
      self.x = 42
    
    def theMethod(self):
      x = self.x
      self.x = 58
      print(x)
      print(self.x)
    

    在此示例中,xself.x 不可互换,即使您在 theMethod 的第一行中分配了 x = self.x。第一个print 将输出42,第二个print 将输出58。每次将某个成员变量分配给局部变量然后被覆盖时,都会发生这种情况。

    这对性能的影响并不完全明显,因为查找 self.xx 都必须在字典中查找符号:在第一种情况下,self 的成员变量的字典,在第二种情况,在当前范围内。它可能对性能产生正面影响和负面影响,具体取决于每个范围中定义的变量数量和其他变量。在大多数非人为的情况下,它可能对性能产生微小的积极影响。

    编辑:正如@zvone 所指出的,最后一段不一定适用于python 解释器的所有实现。

    【讨论】:

    • 你好安德烈。您能否详细说明为什么这可能适用于解释器的某些实现而不适用于其他实现?
    • 在 CPython 中,这是一种常见的微优化,因为局部变量命名空间被优化为原始数组而不是 dict,并且可以更快地访问。
    • @Scott zvone 的回答很好地解释了这一点:显然,CPython 对局部变量进行了一些特殊的优化,因此在访问局部变量的值时不需要字典查找。这可能比在对象的__dict__ 中查找要快得多。不过,关于更改语义的段落仍然有效。
    • 注意,这是引入__slots__的主要原因。
    • 谢谢安德烈。我想我对不同的“python解释器的实现”部分感到困惑?不是所有的 python 解释器都以相同的方式处理局部变量访问?
    猜你喜欢
    • 2017-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-27
    • 2019-03-20
    • 1970-01-01
    • 2015-11-05
    相关资源
    最近更新 更多