Най-питоничният и/или производителен начин за присвояване на една стойност на срез?

Искам да присвоя една стойност на част от списък. Има ли по-добро решение за това от едно от следните?

Може би най-ефективният, но някак грозен:

>>> l=[0,1,2,3,4,5]
>>> for i in range(2,len(l)): l[i] = None

>>> l
[0, 1, None, None, None, None]

Кратко (но не знам дали Python разпознава, че не е необходимо пренареждане на елементите на списъка):

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None]*(len(l)-2)
>>> l
[0, 1, None, None, None, None]

Същото предупреждение като по-горе:

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None for _ in range(len(l)-2)]
>>> l
[0, 1, None, None, None, None]

Не съм сигурен дали използването на библиотека за такава тривиална задача е разумно:

>>> import itertools
>>> l=[0,1,2,3,4,5]
>>> l[2:] = itertools.repeat(None,len(l)-2)
>>> l
[0, 1, None, None, None, None]

Проблемът, който виждам с присвояването на среза (срещу цикъла for), е, че Python може би се опитва да се подготви за промяна в дължината на "l". В края на краищата, промяната на списъка чрез вмъкване на по-къс/по-дълъг отрязък включва копиране на всички елементи (т.е. всички препратки) на списъка AFAIK. Ако Python прави това и в моя случай (въпреки че е ненужно), операцията става O(n) вместо O(1) (ако приемем, че винаги променям само няколко елемента).


person Vroomfondel    schedule 09.12.2013    source източник
comment
Въпреки че не е отговор, мисля, че можете да направите своята кратка версия по-четима по този начин: l[2:] = [None]*len(l[2:]. Просто лошо предложение :)   -  person Paulo Bu    schedule 09.12.2013


Отговори (6)


Мисля, че в Python няма функция готова за това. Харесвам втория ви подход, но имайте предвид, че има компромис между пространство и време. Това е много добро четиво, препоръчано от @user4815162342: Python Patterns - Анекдот за оптимизация .

Както и да е, ако това е операция, която в крайна сметка ще изпълнявате във вашия код, мисля, че най-добрият ви вариант е да я обвиете в помощна функция:

def setvalues(lst, index=0, value=None):
    for i in range(index, len(lst)):
        lst[i] = value

>>>l=[1,2,3,4,5]
>>>setvalues(l,index=2)
>>>l
>>>[1, 2, None, None, None]

Това има някои предимства:

  1. Кодът се преработва във функция, така че лесно се променя, ако промените решението си как да изпълните действието.
  2. Можете да имате няколко функции, които постигат една и съща цел и следователно можете да измервате тяхната ефективност.
  3. Можете да им пишете тестове.
  4. Всяко друго предимство, което можете да получите чрез рефакторинг :)

Тъй като IMHO няма директно бъдеще на Python за това действие, това е най-доброто решение, което мога да си представя.

Надявам се това да помогне!

person Paulo Bu    schedule 09.12.2013
comment
Не беше ли xrange изяден от титаните на Python в 3.0? - person Vroomfondel; 09.12.2013
comment
@Vroomfondel благодаря за вниманието! Просто исках да подобря представянето му. Сменям го веднага :) - person Paulo Bu; 09.12.2013
comment
В Python 3 xrange се изписва просто range. Жалко е, че range съществува и в Python 2, което означава, че горният код работи и на двете, но с фини разлики в поведението. Като цяло, тъй като двете не са съвместими, трябва да се реши дали кодът е насочен към Python 2 или Python 3. - person user4815162342; 10.12.2013
comment
Също така, гарантирано е, че тази реализация е по-малко ефективна от присвояването на срез, тъй като преминавате през елементите/индексите на списъка в Python. - person user4815162342; 10.12.2013
comment
Може би си прав за изпълнението. Ще разгледам как се изпълнява списъкът на Python, за да добиете представа за режийните разходи за създаване на нов списък (срез) и добавянето му срещу индексиране. Но моята гледна точка никога не е била да предоставя най-бързото. Дори не ги мерих. Просто направих по-прагматичното предложение, за да имам по-гъвкав код. Благодаря за подкрепата. - person Paulo Bu; 10.12.2013
comment
@user4815162342: Добавих малко съдържание относно индексирането в списъците на Python. Не мисля, че итеративното внедряване е много скъпо. И определено, по-малко пространствено скъпо от нарязването. - person Paulo Bu; 10.12.2013
comment
Не разбирате коментара ми. Не твърдя, че цикълът върху елементи от списък в Python е алгоритмично неефективен, само че е неефективен, защото го правите в интерпретатора. Това е фиксирано постоянно забавяне, но константата може да бъде доста голяма. Ето защо решението repeat+slice l[2:] = [None] * len(l) - 2 е по-бързо с такава огромна разлика - то оставя целия цикъл на C. За повече информация как да подходите към анализа на производителността на интерпретирания код, вижте класиката на Guido, Анекдот за оптимизиране на Python. - person user4815162342; 10.12.2013
comment
@user4815162342 Ще го прочета веднага, благодаря, че ми го посочи. - person Paulo Bu; 10.12.2013
comment
@user4815162342: Това беше много приятно четене. Ще премахна моето предложение за ефективност и вместо това ще посоча тази статия. Благодаря отново. - person Paulo Bu; 10.12.2013

Време:

python -mtimeit "l=[0,1,2,3,4,5]" "for i in range(2,len(l)):" "    l[i] = None"
1000000 loops, best of 3: 0.669 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = [None]*(len(l)-2)"
1000000 loops, best of 3: 0.419 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = [None for _ in range(len(l)-2)]"
1000000 loops, best of 3: 0.655 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = itertools.repeat(None,len(l)-2)"
1000000 loops, best of 3: 0.997 usec per loop

Изглежда, че l[2:] = [None]*(len(l)-2) е най-добрата от предоставените от вас опции (за обхвата, с който работите).

Забележка:

Имайте предвид, че резултатите ще варират в зависимост от версията на Python, операционната система, други работещи в момента програми и най-вече - размера на списъка и на парчето, което трябва да бъде заменено. За по-големи обхвати вероятно последната опция (използване на itertools.repeat) ще бъде най-ефективна, тъй като е едновременно лесна за четене (pythonic) и ефективна (производителност).

person Inbar Rose    schedule 09.12.2013
comment
Вашият показател се отнася за малки списъци. Тъй като l нараства, отнема повече време за разпределяне и освобождаване на временния списък и решението, базирано на itertools.repeat, става сравнително по-евтино. За да тествате това, заменете настройката на списъка с l = range(6) и увеличете списъка. Необходим е изненадващо голям списък, за да стане itertools.repeat по-бърз, но това се случва — в моята система, в списък със стотици хиляди елементи. - person user4815162342; 09.12.2013
comment
@user4815162342 Абсолютно - В това няма съмнение. Този отговор просто определя времето на всички опции, предоставени от OP. - person Inbar Rose; 09.12.2013
comment
Точно това е моята гледна точка - определянето на времето на всички опции на измислен набор от данни е ключов инструмент, но не замества анализа на ефективността. Последното изречение на вашия отговор, от друга страна, оставя у читателя впечатлението, че предоставеният момент е достатъчна основа за избор на най-добрия вариант. - person user4815162342; 09.12.2013

Всичките ви решения са Pythonic и почти еднакво четими. Ако наистина ви е грижа за производителността и смятате, че тя има значение в този случай, използвайте модула timeit, за да ги сравните.

Като казах това, бих очаквал, че първото решение почти сигурно не е най-производителното, защото итерира върху елементите на списъка в Python. Освен това Python не оптимизира списъка, създаден от дясната страна на присвояването, но създаването на списък е изключително бързо и в повечето случаи малък временен списък изобщо не засяга изпълнението. Лично за кратък списък бих избрал вашето второ решение, а за по-дълъг списък бих избрал itertools.repeat().

Имайте предвид, че itertools всъщност не се брои за "библиотека", идва с Python и се използва толкова често, че по същество е част от езика.

person user4815162342    schedule 09.12.2013

Да бъдеш по-Pythonic и да бъдеш по-производителен са цели, които понякога могат да се сблъскат. Така че вие ​​основно задавате два въпроса. Ако наистина се нуждаете от производителността: измерете я и вземете най-бързата. Във всички останали случаи просто изберете това, което е най-четливо, с други думи това, което е най-Pythonic (това, което е най-четливо/познато на други програмисти на Python).

Лично аз смятам, че второто ви решение е доста четливо:

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None]*(len(l)-2)
>>> l
[0, 1, None, None, None, None]

Началото на втория ред веднага ми казва, че замествате определена част от стойностите на списъка.

person Niels Bom    schedule 09.12.2013

Бих предложил нещо подобно:

>>> l = [0, 1, 2, 3, 4, 5]
>>> n = [None] * len(l)
>>> l[2:] = n[2:]
>>> l
[0, 1, None, None, None, None]

Изглежда красиво: без изрични цикли, без „ако“, без сравнения! На цената на 2N сложност! (или не?)

person Don    schedule 09.12.2013
comment
Ами ако l съдържа 1000 елемента и OP иска да замени само последните 10? В този случай за промяна само на 10 елемента това ще създаде нов списък от 1000 елемента. - person Ashwini Chaudhary; 09.12.2013

РЕДАКТИРАНЕ - Редактиране на оригиналния списък сега.

Забравихте това, (IMO, това е по-четливо)

>>> l = [i if i < 2 else None for i in range(6)]
>>> l
[0, 1, None, None, None, None]

Ако е необходимо запазване,

>>> l = range(6)
>>> l
[0, 1, 2, 3, 4, 5]
>>> l[:] = [l[i] if i < 2 else None
...              for i in range(len(l))]
>>> l
[0, 1, None, None, None, None]

Измерено от времето, производителността е приблизително 2,5 пъти по-бавна от това, което Inbar получи като най-бърз метод.

person shad0w_wa1k3r    schedule 09.12.2013
comment
Подобрение може да бъде: l = [l[i] if i < 2 else None for i in range(len(l))]. По този начин той използва елементите от списъка. - person Paulo Bu; 09.12.2013
comment
Вашето решение с времето: python -mtimeit "l=[0,1,2,3,4,5]" "[l[i] if i < 2 else None for i in range(len(l))]" 1000000 loops, best of 3: 0.81 usec per loop Така че - не е толкова добро, колкото другите. - person Inbar Rose; 09.12.2013
comment
Това не е еквивалентно на това, което OP прави, ако има други препратки към l, тогава те няма да бъдат засегнати тук. - person Ashwini Chaudhary; 09.12.2013
comment
Хм, да @AshwiniChaudhary. Благодаря, че посочи, редактирам, за да предупредя за това. - person shad0w_wa1k3r; 09.12.2013