RE: [python] Dynamické vytvoření instance

Petr Prikryl Prikryl na skil.cz
Čtvrtek Březen 3 09:01:02 CET 2005


> Jsem začátečník, snažil jsem se 
> vytvořit funkci, která by 
> dynamicky vytvářela
> instance třídy :( 

V Pythonu se instance objektů vždy vytvářejí
dynamicky. Při použití exec se jde o kousek
dál -- dynamicky se kompiluje kód, který
jsem zkonstruoval jako řetězec. Asi to nelze
považovat za začátečnickou problematiku.
Jinými slovy, exec potřebuji až v situacích,
které nejsou typické a nedají se řešit
jednodušeji (viz ukázkový kód na konci):

> class trida:
>     pass;

... středník je tady zbytečný ;)

> 
> def new_objekt(x):
>     exec "obj"+str(x)+"= trida"

Tady chybí za identifikátorem třídy závorky.
V takovém případě se nevytváří instance,
ale jen reference na definici třídy.
(Použití identifikátoru objX se pak chová
naprosto stejně jako identifikátor třída).

Pro třídy se doporučuje volit identifikátor
s velkým počátečním písmenem.

>     exec "global obj"+str(x)

Tady je problém. Direktiva global se 
musí uvést předem. Nepamatuji si, kdy
jsem ji naposledy použil. Pokud ano,
vždy to bylo v situaci, kdy globální
proměnná tohoto jména už existovala.
Pokud se domnívám správně, pak objX 
už musí v globálním prostoru existovat.
Pak se bude při přístupu k proměnné 
uvedeného jména uvnitř funkce přistupovat
ke globální proměnné a ne k lokální 
proměnné stejného jména uvnitř funkce.

V souvislosti s exec je tu ale ještě jiný
zádrhel, který uvedené pokusy staví
na úroveň "vyfukování tabákového
kouře do umyvadla s vodou". Dokumentace
říká (6.13 The global statement):

  Programmer's note: the global is a directive 
  to the parser. It applies only to code parsed 
  at the same time as the global statement.
  In particular, a global statement contained 
  in an exec statement does not affect 
  the code block containing the exec statement, 
  and code contained in an exec statement 
  is unaffected by global statements in the code 
  containing the exec statement. The same applies 
  to the eval(), execfile() and compile() functions.

> new_objekt(1)       
> print obj1
> 
> Traceback (most recent call last):
>   File "C:/narozeniny/j.py", line 14, in ?
>     print obj1
> NameError: name 'obj1' is not defined

Výše uvedená poznámka z dokumentace znamená,
že za použití global nejsem schopný procpat
zkonstruované jméno zevnitř exec
mezi globální proměnné. Můžu to ale udělat
jinak. 

Instanci objektu nemusím vytvářet současně
s jejím jménem, protože jméno je nevýznamné
(zpřístupňuje referenci na objekt). Instanci
třídy můžu vytvořit klasicky a méně klasickým
způsobem pouze svážu referenci na takto 
vytvořený objekt s nově vytvořeným globálním 
jménem (viz následující příklad). Osobně bych 
ale dal přednost nějakému méně krkolomnému 
řešení.

j.py
==========================================
class Trida:
    def __str__(self):
        """Retezcova reprezentace objektu -- vyuzije ji print"""
        return 'jsem Trida'


def new_object(x):
    # Takhle to jde vytvorit a zde zpristupnit, ale
    # v globalnim prostoru to neuvidim.
    exec 'obj' + x + '= Trida()'
    print 'funkce new_object() vytvorila obj' + x
    exec 'print obj' + x

    # Alternativa bez direktivy global, ale s vyuzitim
    # zabudovane funkce globals(), ktera vraci slovnik
    # globalni urovne.
    identifikator = 'obj' + x + x      # zkonstruovane jmeno
    globals()[identifikator] = Trida()
    print 'funkce new_object() vytvorila obj' + x + x
    exec 'print obj' + x + x


print 'Nejjednodussi...'
obj1 = Trida()
print obj1


print 'Pres funkci s vedlejsim efektem -- krkolomne...'
new_object('A')
print 'Objekt vytvoreny funkci:', objAA


print '\nVice objektu do seznamu.'
lst = []
for i in xrange(10):
    lst.append(Trida())

for tt in lst:
   print tt


print '\nVice objektu do slovniku.'
d = {}
for i in xrange(10):
    d['obj%d' % i] = Trida()

for tt in d:
   print d[tt]


print '\nVice objektu pres exec (asi se pouziva velmi zrika)'
for i in xrange(10):
    prikaz = 'obj%d = Trida()' % i
    exec prikaz

print dir()
print obj0
print obj1
print obj2
print obj3
print obj4
print obj5
print obj6
print obj7
print obj8
print obj9

print '-' * 70
==========================================

A ještě nakonec... Využití funkce pro vytváření
instancí má smysl pouze tehdy, když funkce na
základě dalších okolností rozhodne, jaká instance
(jaké třídy nebo s jakým vnitřním stavem). Pak
se tomu říká "class factory". V tomto případě
se funkce použije čistým způsobem (nevolá se s cílem
využít vedlejší efekt, ale vracenou hodnotu).
Použití by vypadalo nějak takto:


==========================================
class Trida1: 
    def __str__(self):
        return 'instance tridy Trida1'

class Trida2: 
    def __str__(self):
        return 'instance tridy Trida2'


def mojeClassFactory(okolnosti):
   if okolnosti == 7:
       return Trida1()
   elif okolnosti == 8:
       return Trida2()
   else:
       return None


objA = mojeClassFactory(7)
print 'objA:', objA

objB = mojeClassFactory(8)
print 'objB:', objB

objC = mojeClassFactory(1)
print 'objC:', objC
==========================================

(Okolnosti mohou samozřejmě nabývat různou podobu.)

Petr

-- 
Petr Prikryl (prikrylp at skil dot cz) 



Další informace o konferenci Python