如果在一个实例中,我有self.foo = 1
,这些(或其他更复杂的例子)之间有什么区别:
# 1
for i in range(10):
print(self.foo)
# 2
foo = self.foo
for i in range(10):
print(foo)
我目前正在查看一个代码库,其中所有self
变量都被重新分配给其他内容。只是想知道是否有任何理由这样做,并希望从效率的角度和代码清晰度的角度来听取它们。
答案 0 :(得分:1)
考虑这些可能性:
self
在循环中间反弹。 (对于您已经给出的特定代码,这是不可能的,但可以想象一个不同的循环。)在这种情况下,#1将看到新的self
' s { {1}}属性,而#2不会。当然,您可以轻松地将局部变量foo
重新绑定为局部变量foo
... self
是可变的,self
在循环中间反弹到不同的值。 (这可能更容易发生,例如,另一个线程在同一个对象上运行。)同样,#1将看到self.foo
属性的新值,但#2将不会。foo
本身是可变的,它的值在循环的中间发生变化(例如,它是一个列表,其他一些线程调用self.foo
)。现在#1和#2都会看到新值。如果这些语义差异中的任何一个是相关的,那么当然你想使用任何一个给你正确答案。
同时,每次访问append(2)
时,都需要进行属性查找。在最常见的情况下,这意味着在self.foo
中查找'foo'
,这很快,但不是免费的。并且您可以轻松地创建病态案例,其中它在MRO顺序中经历了23个基类,然后调用self.__dict__
来动态创建值并返回其__getattr__
方法执行某些非平凡变换的描述符。
另一方面,访问__get__
将被编译为仅使用编译索引从帧上的数组中加载值。因此它几乎总是更快,在某些情况下它可以更快。
在大多数现实生活中,这根本不重要。但偶尔也会如此。在这种情况下,将值复制到循环外部的局部是值得的微优化。绑定方法比使用普通值更常见(因为它们总是有一个描述符调用);请参阅the unique_everseen
recipe in the itertools
docs以获取示例。
当然你可以设想一种情况,这种优化实际上会使事情变得更慢 - 例如,使循环变得非常小,但是将整个事物放在外部循环中。现在,每次通过外部循环的额外foo
副本(以及循环中涉及的字节码较长并且可能溢出到另一个高速缓存行的事实)可能比节省的成本高得多。
如果没有重要的语义差异,而且性能差异并不重要,那么它只是澄清问题。
如果表达式比self.foo
复杂得多,则可以更清楚地提取值并为其命名。
但对于这样一个微不足道的案例,使用self.foo
可能更清楚。通过将其复制到局部变量的额外步骤,您发出信号表明您有理由这样做。因此,读者会想知道self.foo
是否可以在不同的线程中反弹,或者这个循环可能是代码中的主要瓶颈,self.foo
访问是性能问题等等,浪费时间处理所有这些无关紧要的事情,而不仅仅是按照预期阅读你的代码。