代码之家  ›  专栏  ›  技术社区  ›  Tsuna

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

  •  1
  • Tsuna  · 技术社区  · 6 年前

    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
    

    提前谢谢你的解释

    1 回复  |  直到 6 年前
        1
  •  5
  •   abarnert    6 年前

    在这里,很少会有表演上的差异。

    但是这些是非常不同的接口,做不同的事情,几乎可以肯定 很重要。

    所以,这就是你应该决定写哪一个:你是想问一个圆的周长,还是想问一个圆来计算一个完全不同的圆的周长?


    但是如果你 关心性能,唯一能得到答案的方法就是测试它。Python附带一个 timeit 专门为这样的代码片段设计的模块。如果使用IPython/Jupyter,它有一个更好的包装,叫做 %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)
    

    这是有道理的。仅仅传递整数并不是很自由(它们必须从堆栈中被推送和弹出,而且在CPython中,它们的refcounts必须被旋转),但是它非常快。按名称查找对象中的属性需要做更多的工作。显然,这是大约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)
    


    值得注意的是 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)
    

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


    对所有这些事情都做了些什么,花了多长时间,还有其他的选择是什么,有一种感觉是值得的。例如,如果要计算无数个周长,则可以将绑定方法存储在变量中,而不是反复查找。您可以将整个循环移动到一个函数中,这样绑定方法和全局变量都会变成局部变量(速度会快一点)。等等。

    很少值得做这些事情,但“很少”不是“从不”。有关实际示例,请参见 unique_everseen recipes in the itertools docs seen_add = seen.add 是不是因为事实证明它确实在一些使用这个配方的真实程序中起到了作用。