[python] Modifikace seznamu bez kopirovnani (bylo SQLite - forma selectovaných dat)

Petr Prikryl PrikrylP na skil.cz
Pondělí Leden 8 08:32:50 CET 2007


superman
> > JInak, proc pouzivate for i in range(len(a)): ? Uz jsem si toho
> > vsimnul driv, u jinych prispevku. Preci, kdyz chci iterovat pres
> > prvky, tak musi staci for item in a:
> [...]
> 2) Protože taková iterace je read only. Já můžu dát for item 
>    in a, ale už nezměním ten konkrétní prvek přímo v poli.
>    [...] Existuje možnost jak to udělat bez indexování a bez 
>    toho, aby v paměti byly dočasně dvě pole?

Iterace není "read only". Read only jsou zpřístupňované objekty.

Od Python 2.3 existuje standardní funkce enumerate(), která
vrací iterátor. Jeho metoda next() vrací dvojici (index, element).
Takže původní kód

  a = [(1,2),(3,4)]
  for i in range(len(a)):
      a[i] = list(a[i])

Můžu přepsat na 

  a = [ (1, 2), (3, 4) ]
  for i, elem in enumerate(a):
      a[i] = list(elem)

V původním kódu je navíc použito range(), které by mělo
být alespoň nahrazeno xrange(). Další problém je v tom,
že do původního řešení nemohu zasadit "nekonečnou" 
iterovatelnou strukturu.

Podobný nedostatek má i řešení Lukáše Linharta

  data2 = [list(t) for t in data]

které navíc provádí kopii celého seznamu. Na druhou stranu
je velmi jednoduché a přehledné a záleží na tom, jaký
problém chci ve skutečnosti řešit (jak velké jsou konvertované
struktury a jak často chci konverzi provádět).

Pokud bych chtěl být extrémista, pak lze při zpracování
seznamu na místě samém (používá se pojem "in situ")
kombinovat enumerate() s testováním typu elementu 
a konverzi provádět jen pro elementy typu tuple:

from types import *
a = [ (1, 2), (3, 4) ]
for i, elem in enumerate(a):
    if type(elem) is TupleType:
        a[i] = list(elem)

> V PHP existuje stejná iterace, dokonce si tam můžete udělat 
> i vlastní iterátor, čehož jsem hojně využíval ve svých 
> třídách a objektech. Ale v PHP je iterátor dvojí, jeden 
> read only jako v Pythonu a druhý s možností změnit prvek 
> pole, a ten mi v Pythonu chybí (a nebo o něm nevím).

V Pythonu můžu rovněž definovat vlastní iterátor, 
který nejspíše nabude podobu generátoru. Pokud bych
měl definován vlastní kontejner, pak od Python 2.2
mohu definovat i jeho vlastní iterátor dodefinováním
metod __iter__() a next().

Tohle je problém v rozdílném pojetí homogenního pole
(všechny prvky mají stejný typ) a pythonovského
seznamu, který je svým způsobem sice taky homogenní,
ale obsahuje jen reference, které se automaticky 
dereferencují (když už musím použít tak pěkně české slovo).

Iterátor přes pythonovský seznam mě odkazuje na stejný
objekt, na jaký odkazuje a[i], ale nemohu se přes něj
dostat na samotný seznam a. Nemohu tedy změnit obsah 
seznamu. Nemohu tedy nahradit odkaz na n-tici odkazem
na jiný objekt -- seznam.

> Následující kód v PHP docela elegantně iterací přičte ke 
> každému prvku pole trojku, aniž by se musela vytvářet
> kopie pole:
> 
> $a = array(1,2,3,4,5);
> foreach ($a as &$value)
>    $value += 3;

V Pythonu nemohu měnit hodnotu prvku typu int, protože
jde o objekt s konstantní hodnotou (immutable). Mohu jej
pouze nahradit jiným objektem typu int, který je vypočtený
z hodnoty původního a z další konstanty. V seznamu tedy
musím opět použít obrat, kdy modifikuji samotný prvek 
seznamu. V tomto případě ale dojde k modifikaci každého
prvku seznamu, takže nejefektivnější a nejstručnější 
způsob spočívá v konstrukci nového, upraveného seznamu:

  a = [ 1, 2, 3, 4, 5 ]
  a = [ e+3 for e in a ]
  print a

Pokud bych definoval svou vlastní třídu objektů 
s celočíselnou hodnotou, které by mohly být modifikovány
(mutable), bylo by možné modifikovat seznam bez
vytváření nového.

> V C++ také existuje iterace, dokonce s možností projet 
> jen část prvků daných iterátorem:

Pro seznam lze v Pythonu pro tento účel využít slice,
který vystupuje v roli části původního seznamu.

pepr


Další informace o konferenci Python