类属性与方法参数的性能?蟒蛇

Tsu*_*una 1 python parameters methods performance class

请忽略我的计算,只是想知道在使用类属性或方法参数之间是否存在任何性能偏好,因为它们的工作方式几乎相同,只是可以在类内部的任何位置调用类属性,而方法参数仅保留在自己的位置范围

class Circle():
    def __init__(self, radius=1):
        self.pi = 3.14
        self.radius = 1

    # use class attribute pi
    def get_circum_self(self):
        return self.pi * self.radius * 2

    # use param for pi
    def get_circum_pi(self, pi, radius):
        return pi * radius * 2

nc = Circle()
print(nc.pi)
print(nc.radius)
print(nc.get_circum_self())  # use class attribute pi
print(nc.get_circum_pi(111, 1))  # use param for pi
Run Code Online (Sandbox Code Playgroud)

预先感谢您的任何解释

aba*_*ert 5

性能差异在这里非常重要。

但是这些是非常不同的接口,它们执行不同的操作,并且几乎可以肯定会很重要。

因此,这就是决定如何编写的方式:是要问一个圆的圆周,还是要问一个圆来计算一个完全不同的圆的圆周?


但是,如果您确实关心性能,那么获得答案的唯一方法就是对其进行测试。Python附带有一个timeit专门用于对这样的代码片段进行基准测试的模块。如果您使用IPython / Jupyter,它周围有一个更好的包装器,称为%timeit

这是%timeit我的机器上运行64位python.org CPython 3.7的示例数据:

In [417]: %timeit nc.get_circum_self()
323 ns ± 10.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [418]: %timeit nc.get_circum_pi(111, 1)
258 ns ± 6.55 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Run Code Online (Sandbox Code Playgroud)

这是有道理的。仅仅传递整数并不是完全免费的(它们必须被压入栈并从堆栈中弹出,并且在CPython中,它们的引用计数必须被缠绕),但这是非常快的。通过名称查找对象中的属性还需要做更多的工作。显然,这大约需要70纳秒的额外工作。


但是请考虑如何以更现实的方式使用它。如果您只想在源代码中使用硬编码值计算一个周长,那显然只会发生一次,那么谁在乎它是323ns还是258ns?如果要计算不计其数的值,那么这些值可能来自某个变量,对吗?因此,让我们比较一下:

In [419]: pi, rad = 111, 1
In [420]: %timeit nc.get_circum_pi(pi, rad)
319 ns ± 15.19 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Run Code Online (Sandbox Code Playgroud)

看起来查找一对全局变量与查找一对属性一样昂贵。同样,这也不是太令人惊讶-无论哪种方式,我们都在命名空间中查找名称(哈希值已经预先计算出来的字符串),这对于全局变量来说只是一个普通的老字典。并针对您编写的普通类),因此所需的工作量几乎相同。


还要指出的是,它对,,,,,get_circum_pi都不做任何事情self。因此,如果您真的想挤出最后几纳秒的时间,为什么要强迫自己将方法视为属性?为什么不仅仅使其具有功能?

In [423]: def get_circum_pi(pi, radius):
     ...:     return pi * radius * 2
In [424]: %timeit get_circum_pi(111, 1)
180 ns ± 4.54 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Run Code Online (Sandbox Code Playgroud)

这为我们节省了更多时间。同样,这是有道理的,但前提是您对方法的工作方式有了更多的了解。查找方法需要查找函数,而不是在对象自己的字典中找到它,而是返回到类的字典,然后__get__在函数上调用描述符以将其绑定为方法。这是很多工作。

好吧,它的工作量为78纳秒,仍然不是很多。


了解所有这些功能的功能,所需的时间以及替代方案是值得的。例如,如果要计算成千上万的周长,则可以将bound方法存储在变量中,而不必一遍又一遍地查找它。您可以将整个循环移到一个函数中,这样绑定方法和全局变量都将变为局部变量(速度更快)。等等。

做这些事情很少值得,但“很少”并非“从不”。有关真实示例,请参阅文档unique_everseen中的食谱中itertools的功能- seen_add = seen.add之所以存在该功能,是因为事实证明,它确实确实在某些使用此食谱的真实程序中有所作为。