NOVINKA: Staň se datovým analytikem od 0 Kč a získej jistotu práce, lepší plat a nové kariérní možnosti. Více informací:

Lekce 24 - Výčtové typy (enum) a konstanty v Pythonu Nové

V minulé lekci, Abstraktní třídy v Pythonu, jsme si vysvětlili, co jsou to abstraktní třídy.

V tomto tutoriálu OOP v Pythonu se podíváme na výčtové typy a konstanty.

Výčtové typy a konstanty v Pythonu

V programech se nám často stává, že nějaká proměnná může nabývat několika přednastavených hodnot. Příkladem může být stav objednávky v internetovém obchodě. Objednávka může být nová, přijatá, potvrzená nebo dokončená. Žádného jiného stavu nabývat nemůže. Podobným příkladem jsou např. dny v týdnu, pracovní pozice a podobně.

S dosavadními znalostmi bychom si stav objednávky ukládali asi jako textový řetězec. To však může být častým zdrojem chyb. Nemáme žádný mechanismus, který nám zkontroluje, že je hodnota zadaná správně. Proto Python obsahuje tzv. výčtové typy z modulu enum, ve kterém najdeme třídu Enum.

Výčtové typy (Enum)

Výčtové typy byly v Pythonu zavedeny od verze 3.4. Jejich definice je přímočará:

from enum import Enum

class StavObjednavky(Enum):
    NOVA = 1
    PRIJATA = 2
    POTVRZENA = 3
    DOKONCENA = 4

Definovali jsme si třídu StavObjednavky, která dědí ze třídy Enum, kterou jsme si importovali. Každý člen tohoto výčtu (NOVA, PRIJATA…) má dvě části:

  • jméno (např. NOVA)
  • a hodnotu (např. 1).

Hodnota může být libovolná neměnná konstanta. V našem případě jde o číslo, ale můžeme použít i textový řetězec nebo jiný datový typ. Čísla zde slouží jen jako interní reprezentace, například pro ukládání do databáze nebo pro snadné porovnávání.

IDE nám stavy nabízí v našeptávači:

Nabídka stavů objednávky v PyCharmu - Objektově orientované programování v Pythonu

Použití je následující:

stav = StavObjednavky.PRIJATA

if stav == StavObjednavky.PRIJATA:
    print("Objednávka byla přijata.")

Díky výčtovému typu máme k dispozici jasně pojmenované hodnoty. Pokud proměnnou anotujeme typem StavObjednavky, nástroje pro typovou kontrolu pohlídají, že obsahuje jen členy tohoto výčtu.

Hodnotu člena získáme přes atribut value:

print(StavObjednavky.DOKONCENA.value) # vypíše: 4

Enum jako třída s logikou

Výčtové typy v Pythonu se chovají jako běžné třídy, můžeme tedy každému členovi přidat vlastní data (například popisný text) a i vlastní metody. To je užitečné, pokud chceme, aby samotný výčtový typ nesl nejen název a hodnotu, ale i další logiku, která s ním souvisí.

V tomto příkladu má každý stav objednávky přiřazený popisný text a metoda __str__() zajistí, že se při vypsání člena Enum zobrazí právě tento text:

from enum import Enum

class StavObjednavky(Enum):
    NOVA = "Nová objednávka"
    PRIJATA = "Objednávka byla přijata ke zpracování"
    POTVRZENA = "Objednávka byla potvrzena"
    DOKONCENA = "Zboží bylo expedováno"

    def __str__(self):
        return self.value

stav = StavObjednavky.NOVA
print(stav) # vypíše: Nová objednávka

Každý člen Enum je ve skutečnosti instance této třídy a může mít vlastní data i metody. V našem případě jsme přímo při deklaraci nastavili textový popis.

Více hodnot najednou

Někdy potřebujeme, aby proměnná obsahovala více hodnot současně. Typickým příkladem jsou uživatelská práva – uživatel může mít právo na čtení i zápis zároveň.

V Pythonu existují dva hlavní způsoby, jak to řešit.

Množina členů Enum

Jednoduché řešení je použít set a vložit do něj členy výčtu. Nejprve si definujeme práva jako Enum:

from enum import Enum, auto

class Prava(Enum):
    ZADNA = auto()
    ZAPIS = auto()
    CTENI = auto()
    SPUSTENI = auto()
    VYMAZANI = auto()
    TISK = auto()

Funkce auto() z modulu enum členům přiřadí postupná celá čísla od 1. Díky tomu nemusíme ručně číslovat a když později přidáme nové právo doprostřed, nic nepřečíslováváme.

Poté deklarujeme množinu práv, do níž přiřadíme například právo pro čtení a tisk:

prava_uzivatele = {Prava.CTENI, Prava.TISK}

Pokud bychom chtěli nějaká práva přidat, použijeme:

prava_uzivatele.add(Prava.SPUSTENI)

Můžeme také práva odebrat:

prava_uzivatele.discard(Prava.CTENI)

Práva uživatele zkontrolujeme takto:

if Prava.TISK in prava_uzivatele:
   print("Mohu tisknout!")

A všechna práva vypíšeme:

print(prava_uzivatele) # vypíše: {<Prava.SPUSTENI: 4>, <Prava.TISK: 6>}

Bitové příznaky (IntFlag)

Pro efektivní práci s kombinacemi práv můžeme z modulu enum použít třídu IntFlag. Členy pak lze kombinovat bitovými operátory:

from enum import IntFlag, auto

class Prava(IntFlag):
    ZADNA = 0
    ZAPIS = auto()
    CTENI = auto()
    SPUSTENI = auto()
    VYMAZANI = auto()
    TISK = auto()

Funkce auto() při použití s třídou IntFlag přiřazuje hodnoty jako mocniny dvojky (1, 2, 4, 8…), takže se dají bezpečně kombinovat pomocí bitových operací.

Nyní můžeme nastavit práva pro čtení a zápis tímto způsobem:

prava_uzivatele = Prava.CTENI | Prava.ZAPIS

Použili jsme bitový operátor OR (|), uživatel tedy může číst i zapisovat.

Přidání dalšího práva vypadá takto:

prava_uzivatele |= Prava.SPUSTENI

Ukažme si také odebrání práv:

prava_uzivatele &= ~Prava.CTENI

Můžeme si opět udělat kontrolu:

if prava_uzivatele & Prava.TISK:
    print("Mohu tisknout!")
else:
    print("Nemám právo tisknout!")

Tento způsob nastavování práv využijeme, pokud potřebujeme ukládat práva jako jedinou číselnou hodnotu (bitovou masku), posílat je po síti nebo ukládat do databáze.

V příkladech jsme použili několik speciálních zápisů. Operátor |= přidá nové právo k těm stávajícím, zatímco &= ~ konkrétní právo odebere. Pomocí operátoru & pak v podmínce ověřujeme, zda uživatel dané právo má. Tyto operace fungují proto, že každé právo má hodnotu s jediným nastaveným bitem (mocniny dvou).

Konstanty

Kromě výčtových typů se v kódu můžeme setkat i s konstantami, tedy proměnnými, jejichž hodnota by měla zůstat stejná po celou dobu běhu programu. Python nemá speciální klíčové slovo pro jejich definici, ale podle běžné konvence se zapisují VELKÝMI_PÍSMENY a neočekává se, že se budou měnit:

MINIMALNI_DELKA_HESLA = 5

Tím říkáme, že proměnná MINIMALNI_DELKA_HESLA by se neměla měnit. Pokud chceme, aby na možné přepsání upozornily nástroje pro kontrolu typů, můžeme použít anotaci Final z modulu typing:

from typing import Final

MINIMALNI_DELKA_HESLA: Final[int] = 5

Pokud nyní někdo takto deklarovanou hodnotu přepíše, nástroje pro statickou analýzu (např. SonarQube) na to upozorní. Zvládne to i naše vývojové prostředí PyCharm:

Konstanta v Pythonu - Objektově orientované programování v Pythonu

Statická analýza je součást statického testování. Při něm nástroje kontrolují zdrojový kód bez jeho spuštění. Dokážou tak odhalit potenciální chyby, porušení konvencí nebo nechtěné změny konstant ještě před spuštěním programu. Více se o statickém testování dozvíte v lekci Statické testování a v kurzu ISTQB.

Konstanty na třídách

Konstanty se často definují i uvnitř tříd, pokud s nimi třída úzce souvisí:

from typing import Final

class Uzivatel:
    MINIMALNI_DELKA_HESLA: Final[int] = 5

print(Uzivatel.MINIMALNI_DELKA_HESLA) # vypíše: 5

Tímto způsobem je hodnota "uzamčena" uvnitř třídy a dává smysl jen v jejím kontextu.

Python přepsání konstant technicky nezakazuje. Final je jen upozornění pro vývojáře, ne skutečný zámek při běhu programu. Editor a kontrola kódu nás varují, ale samotný Python změně nezabrání.

V příští lekci, Nejčastější chyby Python nováčků - Umíš pojmenovat objekty?, si ukážeme nejčastější chyby začátečníků v Pythonu ohledně pojmenování tříd, metod a atributů.


 

Předchozí článek
Abstraktní třídy v Pythonu
Všechny články v sekci
Objektově orientované programování v Pythonu
Přeskočit článek
(nedoporučujeme)
Nejčastější chyby Python nováčků - Umíš pojmenovat objekty?
Článek pro vás napsal Adam Hamšík
Avatar
Uživatelské hodnocení:
7 hlasů
.
Aktivity