[python] ukončení interpretu, zničení objektu

Petr Prikryl PrikrylP na skil.cz
Úterý Červen 6 11:04:45 CEST 2006


Jan Martínek napsal...
> class M:
>    b = 0
>    def __del__(self):
>      M.b
>
> a1 = M()
>
> vyhodí výjimku
> Exception exceptions.AttributeError: "'NoneType' object 
> has no attribute 'b'" in <bound method M.__del__ of 
> <__main__.M instance at 0x2aaaaab50a28
> ignored   

a později...
> Děje se tak na Linuxu ve verzích Pythonu 2.4.2 i 2.4.3.

[Funguje to, pokud se místo "a1" uvede "a".] Potvrzuji
stejné chování pro Python 2.4.3 pod Windows.

Radek Kaňovský reagoval...
> Ja myslim, ze tohle chovani je v poradku. Metoda __del__ se
> muze spolehnout v podstate jenom na jmeno `self'. Globalni
> objekty uz mohou byt odalokovane.

Jan Janech...
> Ja viem, ze globalne objekty uz mozu byt zrusene garbage
> collectorom, ale ja som sa prave snazil ukazat ze u tej
> classy to tak nieje. Prave to sa mi zda zvlastne, ze
> premenna ma nastavenu hodnotu None, ale objekt triedy este
> existuje v pamati a moze byt referencovany... okrem toho,
> pri tom ako je python spraveny si neviem predstavit ako by
> niekto chcel zrusit najskor instanciu class objektu a az
> potom instanciu danej triedy...

Radek Kaňovský reagoval...
> On tu tridu nezrusi driv nez instanci, pouze neni trida
> dostupna pres nazev tridy z namespacu modulu. Pres
> `self.__class__' se da trida ziskat. Nekde se proste s
> rusenim referenci musi zacit.

Svatá pravda. Dokumentace pro __del__ říká:

   Warning: [...] Also, when __del__() is invoked in
   response to a module being deleted (e.g., when execution
   of the program is done), other globals referenced by the
   __del__() method may already have been deleted. For this
   reason, __del__() methods should do the absolute minimum
   needed to maintain external invariants. [...]
   
   Česky: Pokud se __del__() volá v rámci rušení modulu
   (např. při ukončení programu), mohou již být ostatní
   globální jména, na které se metoda __del__() odkazuje,
   zrušeny. Z tohoto důvodu by toho měla metoda __del__()
   z pohledu udržování externích invariantů dělat co nejméně.

A co se vlatně děje, když to s "a" nefunguje a s "a1"
funguje"? Když se ruší jména z globálního slovníku,
vyvolávají se akce související s rušením příslušných
objektů. Objekt se nezruší, pokud na něj existuje nějaká
reference, ale v tomto případě se neuvažují reference z
onoho rušeného globálního slovníku (to by se nedalo zrušit
nic). Globální slovník se prochází pravděpodobně v pořadí
určeném hash hodnotou, která souvisí s jménem. Jenže u hash
hodnoty nelze říci (bez znalosti implementace), zde je pro
"a" menší nebo větší, než pro "M". Při _vhodném_ jméně třídy
a nebo odkazu na instanci třídy lze dosáhnout různého
pořadí. Když se ruší nejdříve "a", pak v okamžiku volání
a.__del__() jméno "M" ještě existuje a ve slovníku se najde.
Pokud je to naopak (tj. nejdříve se zruší "M" -- při něm se
__del__() nevolá), pak se při rušení "a" volá a.__del__() a
jméno "M" se nenalezne. Lépe řečeno, položka slovníku "M"
není zcela zrušena, jméno stále existuje, ale kvůli
odstranění co nejvíce vzájemných referencí byla jménu "M"
přidělena reference None (proto NoneType).
 
Dokumentace na stejném místě pokračuje...

   Starting with version 1.5, Python guarantees that globals
   whose name begins with a single underscore are deleted
   from their module before other globals are deleted; if no
   other references to such globals exist, this may help in
   assuring that imported modules are still available at the
   time when the __del__() method is called.
   
   Česky: Od verze 1.5 Python zaručuje, že globální [věci],
   jejichž jméno začítá jedním podtržítkem, jsou v jejich
   vlastním modulu zrušeny dříve, než ostatní globální
   [věci] [...atd]
   
Takže stejné chybné chování v původně fungujícím příkladu
lze navodit tak, že třídě přidáme k jménu podtržítko. Takže
tohle funguje...

    class M:
       b = 0
       def __del__(self):
           M.b = 1
    a = M()

a tohle ne:    

    class _M:
       b = 0
       def __del__(self):
           _M.b = 1
    a = _M()

A kde se stala chyba? Metoda __del__() se nesnaží přímo
odkazovat na vlastní třídu, ale hledá jméno v globálním
slovníku, který se právě vyprazdňuje (jménu "M" byla mezi
tím přidělena reference None). Ale dělá tak jen proto, aby
zjistila odkaz na vlastní třídu. Jenže odkaz na vlastní
třídu nemusíme zjišťovat přes globální slovník, protože
každá instance jej zná. Podle mě tedy nejčistší řešení
naznačil už Radek Kaňkovský (schválně tam dávám "a1", které
nefungovalo):

    class M:
       b = 0
       def __del__(self):
           self.__class__.b = 1
    a1 = M()

pepr    


Další informace o konferenci Python