Osa 10

Näkyvyysmääreet

Aikaisemmin mainittiin, että yliluokassa yksityiseksi määritettyihin piirteisiin ei pääse käsiksi aliluokassa. Tarkastellaan esimerkkinä luokkaa Muistikirja, jossa muistiinpanojen säilyttämiseen käytettävä lista-attribuutti on piilotettu asiakkailta:


class Muistikirja:
    """ Muistikirjaan voidaan tallentaa muistiinpanoja merkkijonoina """

    def __init__(self):
        # yksityinen attribuutti
        self.__muistiinpanot = []

    def lisaa_muistiinpano(self, muistiinpano):
        self.__muistiinpanot.append(muistiinpano)

    def palauta_muistiinpano(self, indeksi):
        return self.__muistiinpanot[indeksi]

    def kaikki_muistiinpanot(self):
        return ",".join(self.__muistiinpanot)

Luokan sisäisen eheyden kannalta tietorakenteena toimivan listan piilottaminen asiakkaalta on sinänsä järkevää, koska luokka tarjoaa itse sopivat operaatiot muistiinpanojen lisäämiseksi ja selaamiseksi. Ongelmalliseksi tilanne muodostuu, jos yritetään kirjoittaa Muistikirja-luokan perivä luokka ProMuistikirja, johon halutaan lisätä muistiinpanojen etsiminen ja järjestäminen. Piilotettu attribuutti ei ole käytettävissä myöskään aliluokissa; metodi etsi_muistiinpanot antaa kutsuttaessa virheen:

class MuistikirjaPro(Muistikirja):
    """ Parempi muistikirja haku- ja järjestystoiminnoilla """
    def __init__(self):
        # Tämä on ok, koska luokan Muistikirja konstruktori
        # on julkinen
        super().__init__()

    # Tämä antaa virheen
    def etsi_muistiinpanot(self, hakusana):
        loydetty = []
        # Attribuutti __muistiinpanot on yksityinen, eikä näy
        # aliluokalle
        for muistiinpano in self.__muistiinpanot:
            if hakusana in muistiinpano:
                loydetty.append(muistiinpano)

        return loydetty
Esimerkkitulostus

AttributeError: 'MuistikirjaPro' object has no attribute '_MuistikirjaPro__muistiinpanot'

Suojatut piirteet

Toisin kuin joistain muista ohjelmointikielistä, Pythonista ei suoraan löydy ominaisuutta joka piilottaa piirteet asiakkailta mutta samaan aikaan avaa ne mahdollisille aliluokille. Ratkaisuksi Python-yhteisö onkin päätynyt konventioon eli yleisesti ymmärrettyyn merkintätapaan suojatuille (eli protected) piirteille.

Koska piirre voidaan piilottaa kirjoittamalla sen tunnisteen (eli nimen) eteen kaksi alaviivaa


def __init__(self):
    self.__muistiinpanot = []

on yleisesti sovittu että yhdellä alaviivalla alkavat piirteet ovat tarkoitettu ainoastaan luokan ja sen aliluokkien käyttöön, eikä niitä tulisi käyttää suoraan sen ulkopuolelta.


def __init__(self):
    self._muistiinpanot = []

Alla on esitetty koko muistikirjaesimerkki uudestaan niin, että muistiinpanot on merkitty suojatuiksi yliluokassa yksityisen sijasta:


class Muistikirja:
    """ Muistikirjaan voidaan tallentaa muistiinpanoja merkkijonoina """

    def __init__(self):
        # suojattu attribuutti
        self._muistiinpanot = []

    def lisaa_muistiinpano(self, muistiinpano):
        self._muistiinpanot.append(muistiinpano)

    def palauta_muistiinpano(self, indeksi):
        return self._muistiinpanot[indeksi]

    def kaikki_muistiinpanot(self):
        return ",".join(self._muistiinpanot)

class MuistikirjaPro(Muistikirja):
    """ Parempi muistikirja haku- ja järjestystoiminnoilla """
    def __init__(self):
        # Tämä on ok, koska luokan Muistikirja konstruktori
        # on julkinen
        super().__init__()

    # Nyt metodi toimii, koska suojattu attribuutti näkyy
    # aliluokalle
    def etsi_muistiinpanot(self, hakusana):
        loydetty = []
        for muistiinpano in self._muistiinpanot:
            if hakusana in muistiinpano:
                loydetty.append(muistiinpano)

        return loydetty

Seuraavassa taulukossa on vielä esitetty piirteiden näkyvyys kaikkien eri suojausmääreiden tapauksessa:

NäkyvyysmääreEsimerkkiNäkyy asiakkaalleNäkyy aliluokalle
Julkinenself.nimikylläkyllä
Suojattuself._nimieikyllä
Yksityinenself.__nimieiei

Näkyvyysmääreet toimivat vastaavasti kaikkien piirteiden kanssa. Luokassa Henkilo oleva metodi isot_alkukirjaimet on suojattu, joten sitä voi käyttää myös aliluokassa Jalkapalloilija:


class Henkilo:
    def __init__(self, nimi: str):
        self._nimi = self._isot_alkukirjaimet(nimi)

    def _isot_alkukirjaimet(self, nimi):
        nimi_isoilla = []
        for n in nimi.split(" "):
            nimi_isoilla.append(n.capitalize())

        return " ".join(nimi_isoilla)

    def __repr__(self):
        return self.__nimi

class Jalkapalloilija(Henkilo):

    def __init__(self, nimi: str, lempinimi: str, pelipaikka: str):
        super().__init__(nimi)
        # metodia voi kutsua, koska se on suojattu yliluokassa
        self.__lempinimi = self._isot_alkukirjaimet(lempinimi)
        self.__pelipaikka = pelipaikka

    def __repr__(self):
        r =  f"Jalkapalloilija - nimi:{self._nimi}, lempinimi: {self.__lempinimi}"
        r += f", pelipaikka: {self.__pelipaikka}"
        return r

# Testataan
if __name__ == "__main__":
    jp = Jalkapalloilija("petri pythonen", "pyttis", "hyökkääjä")
    print(jp)
Esimerkkitulostus

Jalkapalloilija - nimi:Petri Pythonen, lempinimi: Pyttis, pelipaikka: hyökkääjä

Loading
Loading