Python: Сколько стоит создать маленький список много раз? - PullRequest
9 голосов
/ 09 марта 2010

Я снова и снова сталкиваюсь со следующей маленькой раздражающей дилеммой в Python:

Вариант 1:

чище, но медленнее (?), Если вызывается много раз, так как a_list воссоздается для каждого вызова do_something ()

def do_something():    
  a_list = ["any", "think", "whatever"]    
  # read something from a_list

Вариант 2:

Уродливее, но эффективнее (избавьтесь от создания a_list заново)

a_list = ["any", "think", "whatever"]    
def do_something():    
  # read something from a_list

Что вы думаете?

Ответы [ 8 ]

16 голосов
/ 09 марта 2010

Что страшного в этом?

Содержимое списка всегда постоянное, как в вашем примере? Если это так, последние версии Python (начиная с 2.4) будут оптимизировать это, оценивая константное выражение и сохраняя результат, но только если это кортеж. Таким образом, вы можете изменить его, чтобы быть кортежем. Или вы можете перестать беспокоиться о таких мелочах.

Вот список констант и кортеж констант:

>>> def afunc():
...    a = ['foo', 'bar', 'zot']
...    b = ('oof', 'rab', 'toz')
...    return
...
>>> import dis; dis.dis(afunc)
  2           0 LOAD_CONST               1 ('foo')
              3 LOAD_CONST               2 ('bar')
              6 LOAD_CONST               3 ('zot')
              9 BUILD_LIST               3
             12 STORE_FAST               0 (a)

  3          15 LOAD_CONST               7 (('oof', 'rab', 'toz'))
             18 STORE_FAST               1 (b)

  4          21 LOAD_CONST               0 (None)
             24 RETURN_VALUE
>>>
4 голосов
/ 09 марта 2010

Вариант 3:

def do_something(a_list = ("any", "think", "whatever")):
    read something from a_list

Вариант 3 по сравнению с Вариантом 1:

На мой взгляд, оба одинаково читаемы (хотя некоторые, похоже, думают по-разному в комментариях! :-)). Вы могли бы даже написать вариант 3, как это

def do_something(
    a_list = ("any", "think", "whatever")):
    read something from a_list

, который действительно сводит к минимуму разницу с точки зрения читаемости. Однако, в отличие от варианта 1, вариант 3 определяет a_list только один раз - в то время, когда определено do_something. Это именно то, что мы хотим.

Вариант 3 по сравнению с Вариантом 2:

Избегайте глобальных переменных, если это возможно. Вариант 3 позволяет вам сделать это. Кроме того, при использовании варианта 2 со временем или если другие люди будут поддерживать этот код, определение a_list может быть отделено от def do_something. Это может не иметь большого значения, но я думаю, что это несколько нежелательно.

4 голосов
/ 09 марта 2010

Никогда не создавайте что-либо более одного раза, если вам не нужно. Это простая оптимизация, которую можно выполнить с вашей стороны, и я лично не нахожу второй пример безобразным.

Некоторые могут поспорить, что не стоит беспокоиться об оптимизации таких мелочей, как эта, но я чувствую, что что-то такое простое, что нужно исправить, должно быть сделано немедленно. Мне не хотелось бы, чтобы ваше приложение создавало несколько копий всего, что ему не нужно, чтобы просто сохранить произвольное ощущение «красоты кода». :)

3 голосов
/ 09 марта 2010

, если ваш a_list не изменяется, уберите его из функции.

2 голосов
/ 09 марта 2010
  1. У вас есть данные
  2. У вас есть метод, связанный с ним
  3. Вы не хотите хранить данные в глобальном масштабе только для оптимизации скорости метода, если вам не нужно.

Я думаю, что для этого нужны классы.

class Processor:
    def __init__(this):
        this.data = "any thing whatever".split()
    def fun(this,arg):
        # do stuff with arg and list

inst = Processor()
inst.fun("skippy)

Кроме того, если вы когда-нибудь захотите разделить данные в файл, вы можете просто изменить конструктор, чтобы сделать это.

1 голос
/ 09 марта 2010

Ну, похоже, все сводится к инициализации массива в функции или нет:

import time
def fun1():
        a = ['any', 'think', 'whatever']
        sum = 0
        for i in range(100):
                sum += i

def fun2():
        sum = 0
        for i in range(100):
                sum += i


def test_fun(fun, times):
        start = time.time()
        for i in range(times):
                fun()
        end=time.time()
        print "Function took %s" % (end-start)

# Test
print 'warming up'
test_fun(fun1, 100)
test_fun(fun2, 100)

print 'Testing fun1'
test_fun(fun1, 100000)
print 'Testing fun2'
test_fun(fun2, 100000)

print 'Again'
print 'Testing fun1'
test_fun(fun1, 100000)
print 'Testing fun2'
test_fun(fun2, 100000)

и результаты:

>python test.py
warming up
Function took 0.000604152679443
Function took 0.000600814819336
Testing fun1
Function took 0.597407817841
Testing fun2
Function took 0.580779075623
Again
Testing fun1
Function took 0.595198154449
Testing fun2
Function took 0.580571889877

Похоже, что нет никакой разницы.

0 голосов
/ 09 марта 2010

Я работал над автоматизированными системами, которые обрабатывают более 100 000 000 записей в день, где повышение производительности составляет 1%.

Я усвоил большой урок по работе с этой системой: чем быстрее, тем лучше, но только когда вы знаете, когда это достаточно быстро.

Улучшение на 1% привело бы к значительному сокращению общего времени обработки, но этого недостаточно для того, чтобы нам понадобилось наше следующее обновление оборудования. Мое приложение было настолько быстрым, что количество времени, которое я потратил, пытаясь доить этот последний 1%, вероятно, стоило бы больше, чем новый сервер.

В вашем случае вам придется вызывать do_something десятки тысяч раз, прежде чем существенно улучшится производительность. В некоторых случаях это будет иметь значение, в других - нет.

0 голосов
/ 09 марта 2010

Если список никогда не изменяется, почему вы вообще используете списки?

Не зная ваших реальных требований, я бы порекомендовал просто использовать некоторые операторы if, чтобы полностью избавиться от списка и части «прочитать что-то из списка».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...