Python attribútumok öröklődése

Sziasztok!

Némi elméleti segítség kéne, hogy hogyan is kell Pythonban gondolkodni. Egész eddigi életemben vagy statikus típusrendszerű, erősen típusos nyelvekben programoztam, vagy olyan nyelven, ahol amúgy sem nagyon lehet ellenőrizgetni a paramétereket (Perl, Bash, stb.). Szóval nehezen megy az átállás a Contract Based Developmentről a Test Driven Development Pythonos verziójára, ahol Duck Typeing van + sok unit teszt.

Most éppen az elnevezéseken akadtam el, konkrétan a visibility kifejezésén: azt még elviselném, hogy a protected fv-eket '_'-sal jelezzem, a privátokat '__'-sal, mert meglepő módon ez nem sokat ront az olvashatóságon. Viszont sajnos az attribútumok is alapból védtelenek a származtatott osztályokkal szemben, így egy


self.msg = valami

egy osztályban ki tudja mit jelent. Például a következő kódban:


from __future__ import print_function


class Base(object):
    def __init__(self):
        self.msg = "Base msg"

    def printMsg(self):
        print(self.msg)


class Derived(Base):
    def __init__(self):
        super(Derived, self).__init__()
        self.msg = "Derived msg"


b = Base()
b.printMsg()

d = Derived()
d.printMsg()

Kimenet:


Base msg
Derived msg

A probléma csak úgy oldódik meg, ha

self.__msg

-t írok mindenhova. Mivel egy osztályhierarchia tervezésénél a legtöbb attribútum privát szokott lenni (alkalmanként egy-egy protected), most akkor Pythonban minden attribútumot is húzogassak alá kétszer?!

Ha nem lenne világos, most nem az attribútumok kliensek általi láthatósága a problémám, hanem a származtatott osztályokkal szembeni védelem. A rövid, kifejező attribútumnevek gyakran felbukkannak újra meg újra, képtelenség, hogy nagyobb osztályhierarchia esetén ne legyen névütközés. Ez pedig nagy probléma, mert anélkül változtatja meg az ősosztály viselkedését, hogy annak bármelyik függvényét felüldefiniálnám.

A Python guruk hogy birkóznak meg ezzel?

Hozzászólások

Hat, azt el kell fogadni hogy a Python az introspectiont az encapsulation fole helyezi, ez egy filozofiai kerdes :-)
Praktikusan van 2 megoldas is amit hasznalhatsz: minden attributum hozzaferes atfut az object __getattr__ es __setattr__ fugvenyein, ahol szurheted a hozzaferest. A masodik megoldas a property() fugveny amit dekoratorkent hasznalva read-only attributumot tudsz csinalni (erre a dosiban van is pelda)

De mindezektol fuggetelenul en nem igazan latom ezt akkora problemanak, pontosabban nagyon ritkan er ez meg ennyi plusz munkat.

Sot, konkretan a _ es __ is csak konvenciok, praktikusan nem akadalyozzak meg a hozzaferest, csak jelzik a fejlesztonek hogy nem kellene :-)

De így egy osztályhierarchiánál tele lesz a kód időzített bombákkal. Mert hiszen egy saját email típust implementáló osztálynál milyen változónevek jönnek elő? Egy légbőlkapott példa:

self.mail: ezt már tuti örököltük valahonnan
self._mail: ezt talán ellőtte annak a közvetlen gyereke, ahonnan a self.mail-t örököltük
self.mymail: valaki kínjában pont ezt választhatta
self.shit: ez a leggyakoribb változónév, biztos volt már :)
...

A gond az, hogy Pythonban az attribútumok viselkedése pont olyan polimorfikus, mint a függvényeké. Sosem tudhatod, hogy hol jelent meg már egy attribútum, és mivel attribútumot létrehozni bárhol lehet a kódban, észrevenni sem igazán lehet. Sajnos a fejlesztőrendszerek sincsenek egyelőre a helyzet magaslatán ez ügyben. Az Eclipse PyDev sem jó mindenre.

A '_'-szal kapcsolatban igazad van, de a '__' megoldja a problémát, mert új, csak az adott osztályra specifikus nevet hoz létre. Tehát a Derived-ban a self.__msg egy új változó, sosem azonos a Base-beli self.__msg-vel. Szóval pont jó lenne, csak állatira ronda lesz tőle a kód.

Minden olyan attribútumhoz property-t létrehozni, amit meg akarunk védeni a gyerekosztályoktól, tényleg rossz megoldás. Főleg azért, mert a property-hez (általában) tartozni fog egy attribútum, ami a védett adatot tartalmazza. Ez vagy '__'-os, vagy pont ott vagyunk ahonnan elindultunk. A property-k sajnos nem védenek a gyerekosztály véletlen hozzáférésétől, csak a külvilágnak mondanak valamit.

Összefoglalva: a Python programozás titka, hogy a származtatott osztály implementációjakor figyeljünk rá, nehogy véletlenül az örökölt attribútumok ismeretlen halmazából válasszunk nevet. :)

Roviden: igen. De mint tudjuk, "we're all consenting adults here"

A __-vel kapcsolatban azt irtam hogy konvencio, de fizikailag nem akadalyoz a hozzaferesben, max a veletlen hozzaferesben.

Persze irhatsz getter/setter-t mindenhez, de akkor az mar java :-)

Az akarhol letre lehet hozni attributumot-ra jo megoldas a __slots__

Igen, amikor begépelem a kódot, akkor ismeretlen. Megnézhetem ugyan könnyen az aktuális állapotot egy Python interpreterben /pl. példányosítom, és hívok egy dir()-t/, de az nem véd meg attól, hogy később, mikor én már megírtam a kódomat, valaki utólag tegyen hozzá egy olyan attribútumot valamelyik ősosztályhoz, amit én már "lestoppoltam" a származtatott osztályban. Ilyenkor hirtelen az egész a feje tetejére áll, mert míg addig a saját osztályom saját attribútumán dolgozott a kódom, ezután hirtelen megváltozik: a saját attribútumom megszűnik létezni, helyette a nevet az ősosztály attribútumára oldja fel az interpreter, és innentől az ősosztály implementációja és az én implementációm ugyanazt az attribútumot fogja használni két különböző dologra.

Persze futási időben lekérdezhetők ezek az attribútumok, de akkor futási időben kellene mindig ellenőrizni (ha jól értem). Persze mint minden, ez is megoldható, csak nyakatekert módon, és az ilyen ellenőrzések amúgy sem "Pythonosak".

Ha jól értem, a Python módszer az, hogy az ilyen véletlenekkel ne foglalkozzunk fejlesztés közben, helyette minden lényeges mozzanatra írjunk unitteszteket. Így az sem baj, ha a kód megkeveredik, mindaddig, amíg a megfigyelhető viselkedése megfelel az elvárásoknak.

De javítsatok ki, ha tévedek.

Szerk.: közben leesett a tantusz - hiszen pont a Python "open kimono" tulajdonsága miatt kívülről is látszik minden. Hát akkor szét is választhatom a paranoiát a production kódtól! Ha elfog a static typeing viszketegség, akkor _kívülről_, egy unit tesztben ellenőrzöm amit akarok, így én is nyugodtan alszom, és a kód sem lesz tele issubclass(), isinstance(), van_e_mar_ilyen_attributum() szerű marhaságokkal.

Szerintem sejtem a problemadat. Egyvalamit erts meg: Pythonban, Rubyban es a hasonlo dinamikus nyelvekben akkor extendalunk valamit, ha az adott osztaly kepessegeit ki szeretnenk boviteni, a meglevo funkcionalitas megtartasa mellett. Tehat, ha en peldaul egy SMTP osztalyt ki akarok boviteni az SMTP/TLS es az SMTP/SSL tamogatasanak kepessegevel, akkor csinalok egy gyerekosztalyt, ahol a megfelelo fuggvenyeket felulvagom.
Ha valamit csak _hasznalni_ szeretnek, akkor egyszeruen az abbol az objektumbol kepzett peldany referenciajat elmentem egy attributumba:


class KisOsztaly:
  def __init__(self):
    self._smtp = SMTP(host="127.0.0.1", port=25, tls=False, ssl=True)

Amennyiben egy meglevo osztalyt bovitesz ki funkcionalitasban, akkor igenis tessek tisztaba lenni azzal, hogy a parent osztaly milyen valtozokat hasznal.

Ez alol altalaban egy kivetel van, a GUI osztalyok, amikor egy widgetet kell megjelolnod mint parent osztalyt ahhoz, hogy az osztalyod az adott widgetkent funkcionaljon. Altalaban veve azonban a widget osztalyok viszonylag keves attributumot definialnak, azokat is alahuzasos elotaggal, ha te nem hasznalsz ilyet, akkor nem erdekes.

Vannak meg ilyen extrem esetek, hogy pluginek, meg hasonlok, mert ugye a python nem tud olyat, hogy "implements", de itt csak feluletet orokolsz, semmi egyebet, es altalaban nincsenek is attributumok, csak egy contract, hogy biztos meghivhato legyen az adott metodus, legfeljebb hulyeseget ad vissza.

Ha nagyon egyedi attributumra van szukseged, akkor csinalhatsz olyat, hogy pl. self.__jack_mailer_smtp, es akkor totalbiztosan egyedi attributumod van, mert a hierarchiara mar azert csak odafigyelsz.. :-)
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal