Obsah
1. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy
2. Základní vlastnosti slovníků (dictionary) v Pythonu
4. Specifikace několika povolených typů (union)
7. Typové kontroly v těle funkcí
9. Postup při úpravě stávajících projektů bez typových anotací
12. Výsledek spuštění Mypy ve striktním režimu
13. Využití Mypy pro nalezení chybějících typových anotací
14. Přidání typů návratových hodnot u funkcí, které žádnou hodnotu nevracejí
15. Typové anotace parametrů funkcí a jejich návratových hodnot
16. Výsledný kód s typovými anotacemi
17. Kontrola na reálné chyby související s použitými datovými typy
18. Výsledný kód s typovými informacemi po opravě všech chyb
19. Repositář s demonstračními příklady
1. Statické typové kontroly zdrojových kódů Pythonu prováděné nástrojem Mypy
Ve druhém článku o statické kontrole zdrojových kódů naprogramovaných v Pythonu prováděné nástrojem Mypy si nejprve popíšeme práci s typově bezpečnými slovníky (dictionary), odvozením datových typů nástrojem Mypy (což je určitá forma typové inference), specifikací typu volatelná funkce (Callable, to je jedna z nejdůležitějších vlastností Mypy) a ve druhé polovině článku si navíc na poněkud delším zdrojovém kódu ukážeme, jakým způsobem je možné postupně převést aplikaci bez určení datových typů ke zdrojovému kódu, v němž jsou na všech potřebných místech doplněny typové informace. Uvidíme, že i kód s doplněnými typovými informacemi je stále stručnější, než by tomu bylo v jazyku typu C++ či Java.
2. Základní vlastnosti slovníků (dictionary) v Pythonu
V úvodním článku jsme si mj. ukázali, jak lze určit typ prvků seznamů a n-tic (pokaždé se přitom jedná o odlišný koncept). Podívejme se nyní na další standardní datovou strukturu programovacího jazyka Python. Jedná se o slovníky. Práce s nimi je snadná; ukažme si například vytvoření prázdného slovníku s následným přidáním tří prvků do slovníku. Povšimněte si, že v tomto konkrétním případě jsou všechny klíče řetězci a všechny hodnoty jsou typu celé číslo:
d = {} d["foo"] = 1 d["bar"] = 3 d["baz"] = 10 print(d)
V Pythonu jsou ovšem slovníky heterogenními datovými strukturami, což konkrétně znamená, že jak klíče, tak i hodnoty mohou být (prakticky) libovolného typu, a to i v rámci jednoho slovníku. Opět si to ukažme:
d = {} d["foo"] = 1 d["bar"] = 3.14 d[10] = 10 d[42] = "answer" print(d)
Jak však bude tento kód zkontrolován nástrojem Mypy, pokud použijeme přepínač –strict? Podívejme se nejprve na výsledek statické typové kontroly:
$ mypy --strict dict_type2.py dict_type2.py:4: error: Incompatible types in assignment (expression has type "float", target has type "int") [assignment] dict_type2.py:5: error: Invalid index type "int" for "Dict[str, int]"; expected type "str" [index] dict_type2.py:6: error: Invalid index type "int" for "Dict[str, int]"; expected type "str" [index] dict_type2.py:6: error: Incompatible types in assignment (expression has type "str", target has type "int") [assignment] Found 4 errors in 1 file (checked 1 source file)
Z výsledku je patrné, že si Mypy z prvního přiřazení do slovníku odvodil typ slovníku, což je velmi důležitá vlastnost nazývaná typová inference (nejedná se sice o žádnou novinku, protože typová inference je například součástí programovacího jazyka ML z roku 1978, ovšem do dalších programovacích jazyků se tato velmi užitečná vlastnost rozšiřuje teprve postupně). Navíc je z výsledků vypsaných nástrojem Mypy patrné, že v tomto konkrétním případě nemusí být typová inference zcela korektní a bude lepší typ slovníku specifikovat explicitně.
3. Typová anotace u slovníků
Při specifikaci typu slovníku je zapotřebí zadat jak typ klíčů, tak i typ hodnot. V případě, že pouze vyžadujeme, aby nějaká proměnná (či parametr funkce) byla typu slovník s libovolnými klíči a hodnotami, můžeme využít datového typu Any:
from typing import Dict, Any d:Dict[Any, Any] = {} d["foo"] = 1 d["bar"] = 3.14 d[10] = 10 d[42] = "answer" print(d)
Samozřejmě můžeme hodnoty zapisované do slovníku omezit, například tak, že klíči budou pouze řetězce a hodnotami čísla typu float:
from typing import Dict, Any d:Dict[str, float] = {} d["foo"] = 1 d["bar"] = 3.14 d[10] = 10 d[42] = "answer" print(d)
Mypy pochopitelně v tomto krátkém příkladu nalezne celou řadu chyb, což je naprosto v pořádku:
$ mypy dict_type4.py dict_type4.py:7: error: Invalid index type "int" for "Dict[str, float]"; expected type "str" [index] dict_type4.py:8: error: Invalid index type "int" for "Dict[str, float]"; expected type "str" [index] dict_type4.py:8: error: Incompatible types in assignment (expression has type "str", target has type "float") [assignment] Found 3 errors in 1 file (checked 1 source file)
4. Specifikace několika povolených typů (union)
V některých situacích budeme chtít specifikovat, že hodnoty ukládané do slovníků mohou být několika zvolených typů. Nechceme tedy určit pouze jediný typ, ale na druhou stranu nám nemusí vyhovovat typ Any. V takových případech je možné použít typ Union, který se ve specifikacích používá následovně:
Union[typ1, typ2, typ3, ...]
Můžeme tedy upravit typ slovníku tak, že klíči mají být stále řetězce, ale hodnotami mohou být celá čísla, čísla typu float či řetězce:
from typing import Dict, Union d:Dict[str, Union[int, float, str]] = {} d["foo"] = 1 d["bar"] = 3.14 d[10] = 10 d[42] = "answer" print(d)
Výsledek typové kontroly nástrojem Mypy:
$ mypy dict_type5.py dict_type5.py:7: error: Invalid index type "int" for "Dict[str, Union[int, float, str]]"; expected type "str" [index] dict_type5.py:8: error: Invalid index type "int" for "Dict[str, Union[int, float, str]]"; expected type "str" [index] Found 2 errors in 1 file (checked 1 source file)
Samozřejmě je možné určit, že klíče mohou být typu int nebo str, a to následovně:
from typing import Dict, Union d:Dict[Union[int, str], Union[int, float, str]] = {} d["foo"] = 1 d["bar"] = 3.14 d[10] = 10 d[42] = "answer" print(d)
Nyní bude statická typová analýza vypadat následovně:
$ mypy dict_type6.py Success: no issues found in 1 source file
5. Povolení hodnoty None
Dalším častým požadavkem je povolení hodnoty None, která je typu None (typ i hodnota se tedy jmenuje stejně). Například tento skript je typově nekorektní:
from typing import Dict d:Dict[str, float] = {} d["foo"] = 1 d["bar"] = 3.14 d["baz"] = None print(d)
Výsledek typové kontroly:
$ mypy dict_type7.py dict_type7.py:7: error: Incompatible types in assignment (expression has type "None", target has type "float") [assignment] Found 1 error in 1 file (checked 1 source file)
Pokud skutečně budeme chtít povolit i hodnotu None, samozřejmě můžeme použít Union, ovšem existuje i idiomatičtější přístup – využití typu Optional. Je to snadné a čitelné:
from typing import Dict, Optional d:Dict[str, Optional[float]] = {} d["foo"] = 1 d["bar"] = 3.14 d["baz"] = None print(d)
Nyní bude statická typová analýza skriptu vypadat následovně:
$ mypy dict_type8.py Success: no issues found in 1 source file
6. Typový systém a funkce
Ještě jednou se vraťme k problematice specifikace datových typů u funkcí. Víme již, jak lze specifikovat typ parametrů funkce i typ návratové hodnoty. Mějme například funkci, která akceptuje (nekomplexní) číselné hodnoty a vrátí hodnotu True v případě, že předávaná hodnota bude kladná. Realizace této funkce i s uvedením typových informací je relativně snadná:
def positive(x:float) -> bool: return x > 0.0 x:bool = positive(0.5) y:int = positive(42) z:float = positive(False) w:str = positive(3)
7. Typové kontroly v těle funkcí
K čemu je však zápis plně „typově anotované“ funkce vhodný? Na prvním místě se jedná o informaci, která pomáhá integrovaným vývojovým prostředím. Ovšem možná ještě důležitější je fakt, že Mypy dokáže na základě typů parametrů otestovat, zda se s parametry pracuje uvnitř funkce korektně. Co to ovšem konkrétně znamená? Podívejme se na následující příklad, který bude pochopitelně funkční pouze pro ty objekty, které mají definovánu metodu strip:
def append(a, b): return a.strip()+b.strip() print(append(1, 2))
Po spuštění vznikne běhová chyba:
Traceback (most recent call last): File "appender1.py", line 4, in print(append(1, 2)) File "appender1.py", line 2, in append return a.strip()+b.strip() AttributeError: 'int' object has no attribute 'strip'
Typově bezpečná varianta bude vypadat následovně:
def append(a:str, b:str) -> str: return a.strip()+b.strip()
V upravené variantě tohoto příkladu sice specifikujeme, že funkci se mají předávat parametry typu int, ovšem v těle funkce se (omylem, refaktoringem, chybou copy&paste atd.) s parametry pracuje, jakoby se jednalo o řetězce (či naopak – spletli jsme se v určení typů):
def append(a:int, b:int) -> int: return a.strip()+b.strip()
Tento problém nástroj Mypy samozřejmě relativně snadno odhalí, protože má k dispozici informace o tom, jaké metody (a zda vůbec nějaké) jsou pro specifikovaný datový typ dostupné – a metoda strip to pro celá čísla zcela určitě není:
$ mypy appender3.py appender3.py:2: error: "int" has no attribute "strip" [attr-defined] Found 1 error in 1 file (checked 1 source file)
8. Typ Callable
Nyní již dokážeme u funkcí specifikovat jak typy parametrů, tak i typ návratové hodnoty. Ovšem v programovacím jazyku Python jsou i samotné funkce hodnotami a proto je v některých případech vhodné vědět, jakým způsobem se specifikuje typ samotné funkce – resp. typ všech funkcí se stejnými počty a typy parametrů a se stejným typem návratové hodnoty (tak jednoduché to ovšem není – a to díky varianci, o níž se ještě zmíníme). Funkce jsou specifikovány typem Callable, což ovšem není (podobně jako u slovníků) plné určení typu. Musíme ještě doplnit typy parametrů a typ návratové hodnoty. Celý zápis typu funkce by tedy mohl vypadat následovně:
Callable[[typ_parametru_1, typ_parametru_2, ...], typ_návratové_hodnoty]
Jak lze takový typ použít? Například ho můžeme využít ve funkcích vyššího řádu (higher order functions), které jako svůj parametr akceptují jinou funkci či naopak funkci vrací. Podívejme se na jednoduchý příklad, v němž jedné funkci budeme chtít při jejím volání předat funkci jinou. Předávaná funkce bude využita později – zavolá se v těle první funkce:
def printIsPositive(x:float, condition) -> None: if condition(x): print("Positive") else: print("Negative") def positiveFloat(x:float) -> bool: return x > 0.0 def positiveInt(x:int) -> bool: return x > 0 printIsPositive(4, positiveFloat) printIsPositive(4, positiveInt) printIsPositive(-0.5, positiveFloat) printIsPositive(-0.5, positiveInt)
Typová anotace není v tomto příkladu úplná, na což nás Mypy upozorní, pochopitelně za předpokladu, že použijeme přepínač –strict:
x.py:4: error: Function is missing a type annotation for one or more arguments [no-untyped-def] Found 1 error in 1 file (checked 1 source file)
Typ funkcí, které je možné do printIsPositive předat, můžeme omezit, a to právě s využitím Callable. Je to patrné při pohledu na následující úpravu předchozího demonstračního příkladu:
from typing import Callable def printIsPositive(x:float, condition:Callable[[float], bool]) -> None: if condition(x): print("Positive") else: print("Negative") def positiveFloat(x:float) -> bool: return x > 0.0 def positiveInt(x:int) -> bool: return x > 0 printIsPositive(4, positiveFloat) printIsPositive(4, positiveInt) printIsPositive(-0.5, positiveFloat) printIsPositive(-0.5, positiveInt)
Zkusme si zkontrolovat typovou korektnost:
$ mypy callable2.py callable2.py:20: error: Argument 2 to "printIsPositive" has incompatible type "Callable[[int], bool]"; expected "Callable[[float], bool]" [arg-type] callable2.py:22: error: Argument 2 to "printIsPositive" has incompatible type "Callable[[int], bool]"; expected "Callable[[float], bool]" [arg-type] Found 2 errors in 1 file (checked 1 source file)
V této chvíli totiž na scénu (opět) přichází variance, resp. její dva typy. Připomeňme si, že existují čtyři typy variance:
- Covariance
- Contravariance
- Invariance
- Bivariance
U typu „funkce“ (tedy Callable) v Pythonu platí: argumenty/parametry jsou kontravariantní, kdežto návratová hodnota je kovariantní. Zajímavá je zejména kontravariance parametrů, takže si předchozí příklad zjednodušme – nebudeme funkci vyššího řádu předávat dva parametry, ale pouze jediný parametr typu Callable:
from typing import Callable def printIsPositive(condition:Callable[[float], bool]) -> None: if condition(5): print("Positive") else: print("Negative") def positiveFloat(x:float) -> bool: return x > 0.0 def positiveInt(x:int) -> bool: return x > 0 printIsPositive(positiveFloat) printIsPositive(positiveInt)
Výsledek typové kontroly:
$ mypy callable3.py callable3.py:20: error: Argument 1 to "printIsPositive" has incompatible type "Callable[[int], bool]"; expected "Callable[[float], bool]" [arg-type] Found 1 error in 1 file (checked 1 source file)
Zde se právě uplatnila (poněkud neintuitivní) kontravariance. Parametr funkce printIsPositive tedy musíme upravit tak, že bude akceptovat funkci s parametrem typu int a nikoli float:
from typing import Callable def printIsPositive(condition:Callable[[int], bool]) -> None: if condition(5): print("Positive") else: print("Negative") def positiveFloat(x:float) -> bool: return x > 0.0 def positiveInt(x:int) -> bool: return x > 0 printIsPositive(positiveFloat) printIsPositive(positiveInt)
Tentokrát typová kontrola proběhne v pořádku:
$ mypy callable4.py Success: no issues found in 1 source file
9. Postup při úpravě stávajících projektů bez typových anotací
Ve druhé polovině dnešního článku si ukážeme, jakým způsobem by se mohlo postupovat při úpravě stávajících projektů napsaných v Pythonu, které prozatím nepoužívají typové anotace. Celý postup je možné shrnout do několika bodů:
- Refaktoring, ideálně tak, aby se nepoužívaly globální proměnné a globální kód
- Použití Mypy pro nalezení chybějících typových anotací
- Postupné doplnění typových anotací
- Odstranění reálných chyb nalezených nástrojem Mypy
10. Původní zdrojový kód upravovaného demonstračního příkladu před refaktoringem a bez typových informací
Podívejme se nejdříve na původní zdrojový kód příkladu tak, jak byl kdysi ukázán v článku o knihovně Pygame. Tento zdrojový kód, který naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites1.py, neobsahuje typové informace (anotace) a bude ho zapotřebí poněkud refaktorovat:
#!/usr/bin/python # vim: set fileencoding=utf-8 import pygame import sys # Nutno importovat kvůli konstantám QUIT atd. from pygame.locals import * # Velikost okna aplikace WIDTH = 320 HEIGHT = 240 # Třída představující sprite zobrazený jako jednobarevný čtverec. class BlockySprite(pygame.sprite.Sprite): # Konstruktor def __init__(self, color, size, x, y): # Nejprve je nutné zavolat konstruktor předka, # tj. konstruktor třídy pygame.sprite.Sprite: pygame.sprite.Sprite.__init__(self) # Vytvoření obrázku představujícího vizuální obraz spritu: self.image = pygame.Surface([size, size]) self.image.fill(color) # Vytvoření obalového obdélníku # (velikost se získá z rozměru obrázku) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y # Počáteční rychlost spritu self.speed_x = 0 self.speed_y = 0 # Nastavení barvy spritu, který kolidoval s hráčem def yellowColor(self): self.image.fill(YELLOW) # Nastavení barvy spritu, který nekolidoval s hráčem def grayColor(self): self.image.fill(GRAY) # Inicializace knihovny Pygame pygame.init() clock = pygame.time.Clock() # Vytvoření okna pro vykreslování display = pygame.display.set_mode([WIDTH, HEIGHT]) # Nastavení titulku okna pygame.display.set_caption("Pygame test #22") # Konstanty s n-ticemi představujícími základní barvy BLACK = (0, 0, 0) RED = (255, 0, 0) GRAY = (128, 128, 128) YELLOW = (255, 255, 0) # Objekt sdružující všechny sprity all_sprites = pygame.sprite.Group() # Objekt sdružující všechny sprity kromě hráče all_sprites_but_player = pygame.sprite.Group() # Vytvoření několika typů spritů # barva x y velikost wall1 = BlockySprite(GRAY, 50, 10, 10) wall2 = BlockySprite(GRAY, 15, 100, 100) wall3 = BlockySprite(GRAY, 15, 100, 150) wall4 = BlockySprite(GRAY, 15, 200, 100) wall5 = BlockySprite(GRAY, 15, 200, 150) wall6 = BlockySprite(GRAY, 15, 150, 100) wall7 = BlockySprite(GRAY, 15, 150, 150) player = BlockySprite(RED, 40, WIDTH / 2 - 20, HEIGHT / 2 - 20) # Přidání několika dalších spritů do seznamu # (jen jeden sprite - ten poslední - bude ve skutečnosti pohyblivý) all_sprites.add(wall1) all_sprites.add(wall2) all_sprites.add(wall3) all_sprites.add(wall4) all_sprites.add(wall5) all_sprites.add(wall6) all_sprites.add(wall7) all_sprites.add(player) # Seznam všech nepohyblivých spritů all_sprites_but_player.add(wall1) all_sprites_but_player.add(wall2) all_sprites_but_player.add(wall3) all_sprites_but_player.add(wall4) all_sprites_but_player.add(wall5) all_sprites_but_player.add(wall6) all_sprites_but_player.add(wall7) # Posun všech spritů ve skupině na základě jejich rychlosti @profile def move_sprites(sprite_group, playground_width, playground_height): for sprite in sprite_group: # Posun spritu sprite.rect.x = sprite.rect.x + sprite.speed_x sprite.rect.y = sprite.rect.y + sprite.speed_y # Kontrola, zda sprite nenarazil do okrajů okna if sprite.rect.x < 0: sprite.rect.x = 0 sprite.speed_x = 0 if sprite.rect.x + sprite.rect.width > playground_width: sprite.rect.x = playground_width - sprite.rect.width sprite.speed_x = 0 if sprite.rect.y < 0: sprite.rect.y = 0 sprite.speed_y = 0 if sprite.rect.y + sprite.rect.height > playground_height: sprite.rect.y = playground_height - sprite.rect.height sprite.speed_y = 0 # Vykreslení celé scény na obrazovku @profile def draw_scene(display, background_color, sprite_group): # Vyplnění plochy okna černou barvou display.fill(background_color) # Vykreslení celé skupiny spritů do bufferu sprite_group.draw(display) # Obnovení obsahu obrazovky (překlopení zadního a předního bufferu) pygame.display.update() # Změna barvy spritu na základě kolize s hráčem @profile def change_colors(sprite_group, hit_list): # Projít všemi sprity ze skupiny, kterou detekovala kolizní funkce for sprite in sprite_group: if sprite in hit_list: sprite.yellowColor() else: sprite.grayColor() # Zjistí kolize spritu se "stěnami" (nepohyblivými sprity) @profile def check_collisions(player, sprite_group): # Vytvoření seznamu spritů, které kolidují s hráčem hit_list = pygame.sprite.spritecollide(player, sprite_group, False) # Změna barev kolidujících spritů change_colors(sprite_group, hit_list) collisions = len(hit_list) # Přenastavení titulku okna caption = "Pygame test #22: collisions " + str(collisions) pygame.display.set_caption(caption) @profile def mainLoop(): while True: # Načtení a zpracování všech událostí z fronty for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.quit() sys.exit() # Stiskem kurzorových kláves je možné měnit směr pohybu spritu elif event.key == pygame.K_LEFT: player.speed_x = -3 elif event.key == pygame.K_RIGHT: player.speed_x = +3 elif event.key == pygame.K_UP: player.speed_y = -3 elif event.key == pygame.K_DOWN: player.speed_y = +3 if event.type == KEYUP: # Puštění kurzorových kláves vede k zastavení pohybu spritu if event.key == pygame.K_LEFT: player.speed_x = 0 elif event.key == pygame.K_RIGHT: player.speed_x = 0 elif event.key == pygame.K_UP: player.speed_y = 0 elif event.key == pygame.K_DOWN: player.speed_y = 0 move_sprites(all_sprites, display.get_width(), display.get_height()) check_collisions(player, all_sprites_but_player) draw_scene(display, BLACK, all_sprites) clock.tick(20) mainLoop() # finito
11. Refaktoring kódu
Předchozí zdrojový kód je vhodné před doplněním o typové informace ještě náležitě upravit. Postupně některé jeho části vložíme do funkcí a zajistíme, aby se nepoužívaly globální proměnné (pouze globální „konstanty“, i když koncept pravých konstant v Pythonu není). Refaktorovaný kód sice stále nepoužívá typové anotace, ale už je na tuto důležitou změnu připraven:
#!/usr/bin/python # vim: set fileencoding=utf-8 import pygame import sys # Nutno importovat kvůli konstantám QUIT atd. from pygame.locals import * # Velikost okna aplikace WIDTH = 320 HEIGHT = 240 # Konstanty s n-ticemi představujícími základní barvy BLACK = (0, 0, 0) RED = (255, 0, 0) GRAY = (128, 128, 128) YELLOW = (255, 255, 0) CAPTION = "Sprites in Pygame" # Třída představující sprite zobrazený jako jednobarevný čtverec. class BlockySprite(pygame.sprite.Sprite): # Konstruktor def __init__(self, color, size, x, y): # Nejprve je nutné zavolat konstruktor předka, # tj. konstruktor třídy pygame.sprite.Sprite: pygame.sprite.Sprite.__init__(self) # Vytvoření obrázku představujícího vizuální obraz spritu: self.image = pygame.Surface([size, size]) self.image.fill(color) # Vytvoření obalového obdélníku # (velikost se získá z rozměru obrázku) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y # Počáteční rychlost spritu self.speed_x = 0 self.speed_y = 0 # Nastavení barvy spritu, který kolidoval s hráčem def yellowColor(self): self.image.fill(YELLOW) # Nastavení barvy spritu, který nekolidoval s hráčem def grayColor(self): self.image.fill(GRAY) def initDisplay(caption): # Vytvoření okna pro vykreslování display = pygame.display.set_mode([WIDTH, HEIGHT]) # Nastavení titulku okna pygame.display.set_caption(caption) return display def createSprites(): # Objekt sdružující všechny sprity all_sprites = pygame.sprite.Group() # Objekt sdružující všechny sprity kromě hráče all_sprites_but_player = pygame.sprite.Group() # Vytvoření několika typů spritů # barva x y velikost wall1 = BlockySprite(GRAY, 50, 10, 10) wall2 = BlockySprite(GRAY, 15, 100, 100) wall3 = BlockySprite(GRAY, 15, 100, 150) wall4 = BlockySprite(GRAY, 15, 200, 100) wall5 = BlockySprite(GRAY, 15, 200, 150) wall6 = BlockySprite(GRAY, 15, 150, 100) wall7 = BlockySprite(GRAY, 15, 150, 150) player = BlockySprite(RED, 40, WIDTH / 2 - 20, HEIGHT / 2 - 20) # Přidání několika dalších spritů do seznamu # (jen jeden sprite - ten poslední - bude ve skutečnosti pohyblivý) all_sprites.add(wall1) all_sprites.add(wall2) all_sprites.add(wall3) all_sprites.add(wall4) all_sprites.add(wall5) all_sprites.add(wall6) all_sprites.add(wall7) all_sprites.add(player) # Seznam všech nepohyblivých spritů all_sprites_but_player.add(wall1) all_sprites_but_player.add(wall2) all_sprites_but_player.add(wall3) all_sprites_but_player.add(wall4) all_sprites_but_player.add(wall5) all_sprites_but_player.add(wall6) all_sprites_but_player.add(wall7) return all_sprites, all_sprites_but_player, player # Posun všech spritů ve skupině na základě jejich rychlosti def move_sprites(sprite_group, playground_width, playground_height): for sprite in sprite_group: # Posun spritu sprite.rect.x = sprite.rect.x + sprite.speed_x sprite.rect.y = sprite.rect.y + sprite.speed_y # Kontrola, zda sprite nenarazil do okrajů okna if sprite.rect.x < 0: sprite.rect.x = 0 sprite.speed_x = 0 if sprite.rect.x + sprite.rect.width > playground_width: sprite.rect.x = playground_width - sprite.rect.width sprite.speed_x = 0 if sprite.rect.y < 0: sprite.rect.y = 0 sprite.speed_y = 0 if sprite.rect.y + sprite.rect.height > playground_height: sprite.rect.y = playground_height - sprite.rect.height sprite.speed_y = 0 # Vykreslení celé scény na obrazovku def draw_scene(display, background_color, sprite_group): # Vyplnění plochy okna černou barvou display.fill(background_color) # Vykreslení celé skupiny spritů do bufferu sprite_group.draw(display) # Obnovení obsahu obrazovky (překlopení zadního a předního bufferu) pygame.display.update() # Změna barvy spritu na základě kolize s hráčem def change_colors(sprite_group, hit_list): # Projít všemi sprity ze skupiny, kterou detekovala kolizní funkce for sprite in sprite_group: if sprite in hit_list: sprite.yellowColor() else: sprite.grayColor() # Zjistí kolize spritu se "stěnami" (nepohyblivými sprity) def check_collisions(player, sprite_group): # Vytvoření seznamu spritů, které kolidují s hráčem hit_list = pygame.sprite.spritecollide(player, sprite_group, False) # Změna barev kolidujících spritů change_colors(sprite_group, hit_list) collisions = len(hit_list) # Přenastavení titulku okna caption = CAPTION + ": collisions " + str(collisions) pygame.display.set_caption(caption) def mainLoop(display, clock, all_sprites, all_sprites_but_player, player): while True: # Načtení a zpracování všech událostí z fronty for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.quit() sys.exit() # Stiskem kurzorových kláves je možné měnit směr pohybu spritu elif event.key == pygame.K_LEFT: player.speed_x = -3 elif event.key == pygame.K_RIGHT: player.speed_x = +3 elif event.key == pygame.K_UP: player.speed_y = -3 elif event.key == pygame.K_DOWN: player.speed_y = +3 if event.type == KEYUP: # Puštění kurzorových kláves vede k zastavení pohybu spritu if event.key == pygame.K_LEFT: player.speed_x = 0 elif event.key == pygame.K_RIGHT: player.speed_x = 0 elif event.key == pygame.K_UP: player.speed_y = 0 elif event.key == pygame.K_DOWN: player.speed_y = 0 move_sprites(all_sprites, display.get_width(), display.get_height()) check_collisions(player, all_sprites_but_player) draw_scene(display, BLACK, all_sprites) clock.tick(20) def main(): # Inicializace knihovny Pygame pygame.init() clock = pygame.time.Clock() display = initDisplay(CAPTION) all_sprites, all_sprites_but_player, player = createSprites() mainLoop(display, clock, all_sprites, all_sprites_but_player, player) if __name__ == "__main__": main() # finito
12. Výsledek spuštění Mypy ve striktním režimu
V případě, že spustíme nástroj Mypy s přepínačem –strict, vypíšou se jak všechny chybějící anotace, tak i další potenciální problémy nalezené ve zdrojových kódech:
$ mypy --strict sprites2.py
Výsledek by měl v našem případě konkrétně vypadat takto:
sprites2.py:26: error: Function is missing a type annotation [no-untyped-def] sprites2.py:46: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:46: note: Use "-> None" if function does not return a value sprites2.py:50: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:50: note: Use "-> None" if function does not return a value sprites2.py:54: error: Function is missing a type annotation [no-untyped-def] sprites2.py:64: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:66: error: Need type annotation for "all_sprites" [var-annotated] sprites2.py:69: error: Need type annotation for "all_sprites_but_player" [var-annotated] sprites2.py:73: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:74: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:75: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:76: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:77: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:78: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:79: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:80: error: Call to untyped function "BlockySprite" in typed context [no-untyped-call] sprites2.py:106: error: Function is missing a type annotation [no-untyped-def] sprites2.py:127: error: Function is missing a type annotation [no-untyped-def] sprites2.py:137: error: Function is missing a type annotation [no-untyped-def] sprites2.py:147: error: Function is missing a type annotation [no-untyped-def] sprites2.py:151: error: Call to untyped function "change_colors" in typed context [no-untyped-call] sprites2.py:158: error: Function is missing a type annotation [no-untyped-def] sprites2.py:189: error: Call to untyped function "move_sprites" in typed context [no-untyped-call] sprites2.py:190: error: Call to untyped function "check_collisions" in typed context [no-untyped-call] sprites2.py:191: error: Call to untyped function "draw_scene" in typed context [no-untyped-call] sprites2.py:195: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:195: note: Use "-> None" if function does not return a value sprites2.py:200: error: Call to untyped function "initDisplay" in typed context [no-untyped-call] sprites2.py:202: error: Call to untyped function "createSprites" in typed context [no-untyped-call] sprites2.py:204: error: Call to untyped function "mainLoop" in typed context [no-untyped-call] sprites2.py:208: error: Call to untyped function "main" in typed context [no-untyped-call] Found 29 errors in 1 file (checked 1 source file)
13. Využití Mypy pro nalezení chybějících typových anotací
Prozatím se však budeme chtít soustředit na nalezení těch řádků v programovém kódu, kde chybí typové anotace. K tomuto účelu nemusí být striktní režim nejvhodnější. Namísto toho použijeme jiné přepínače (které jsou součástí striktního režimu):
$ mypy --explicit-package-bases --disallow-untyped-calls --disallow-untyped-defs --disallow-incomplete-defs sprites2.py
Výsledek bude nyní vypadat následovně:
sprites2.py:26: error: Function is missing a type annotation [no-untyped-def] sprites2.py:46: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:46: note: Use "-> None" if function does not return a value sprites2.py:50: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:50: note: Use "-> None" if function does not return a value sprites2.py:54: error: Function is missing a type annotation [no-untyped-def] sprites2.py:64: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:106: error: Function is missing a type annotation [no-untyped-def] sprites2.py:127: error: Function is missing a type annotation [no-untyped-def] sprites2.py:137: error: Function is missing a type annotation [no-untyped-def] sprites2.py:147: error: Function is missing a type annotation [no-untyped-def] sprites2.py:158: error: Function is missing a type annotation [no-untyped-def] sprites2.py:195: error: Function is missing a return type annotation [no-untyped-def] sprites2.py:195: note: Use "-> None" if function does not return a value sprites2.py:208: error: Call to untyped function "main" in typed context [no-untyped-call] Found 12 errors in 1 file (checked 1 source file)
Oněch dvanáct nalezených chyb postupně opravíme.
14. Přidání typů návratových hodnot u funkcí, které žádnou hodnotu nevracejí
Nejdříve můžeme mechanicky doplnit typy návratových hodnot u funkcí, které ve skutečnosti žádnou hodnotu nevracejí. Návratovým typem tedy bude None:
def yellowColor(self) -> None: def grayColor(self) -> None: def move_sprites(sprite_group, playground_width, playground_height) -> None: def draw_scene(display, background_color, sprite_group) -> None: def change_colors(sprite_group, hit_list) -> None: def check_collisions(player, sprite_group) -> None: def mainLoop(display, clock, all_sprites, all_sprites_but_player, player) -> None: def main() -> None:
15. Typové anotace parametrů funkcí a jejich návratových hodnot
Typové informace přidáme k použitým konstantám – n-ticím:
BLACK: Tuple[int, int, int] = (0, 0, 0) RED: Tuple[int, int, int] = (255, 0, 0) GRAY: Tuple[int, int, int] = (128, 128, 128) YELLOW: Tuple[int, int, int] = (255, 255, 0)
Dále přidáme typové anotace pro parametry funkcí/metod i pro jejich návratové hodnoty:
def __init__(self, color: Tuple[int, int, int], size: int, x: int, y: int): def initDisplay(caption: str) -> pygame.Surface: def move_sprites(sprite_group: pygame.sprite.Group, playground_width: int, playground_height:int) -> None: def draw_scene(display: pygame.Surface, background_color: Tuple[int, int, int], sprite_group: pygame.sprite.Group) -> None: def change_colors(sprite_group: pygame.sprite.Group, hit_list: List[pygame.sprite.Sprite]) -> None: def check_collisions(player: BlockySprite, sprite_group: pygame.sprite.Group) -> None: def mainLoop(display: pygame.Surface, clock: pygame.time.Clock, all_sprites: pygame.sprite.Group, all_sprites_but_player: pygame.sprite.Group, player: BlockySprite) -> None:
Nejsložitější je případ funkce createSprites vracející n-tici:
def createSprites() -> Tuple[pygame.sprite.Group, pygame.sprite.Group, BlockySprite]:
V této funkci navíc určíme i typy lokálních proměnných:
all_sprites: pygame.sprite.Group = pygame.sprite.Group() all_sprites_but_player: pygame.sprite.Group = pygame.sprite.Group()
16. Výsledný kód s typovými anotacemi
A takto bude vypadat kód, do něhož byly (víceméně mechanicky) doplněny typové anotace:
#!/usr/bin/python # vim: set fileencoding=utf-8 from typing import NewType, Tuple, List, Any import pygame import sys # Nutno importovat kvůli konstantám QUIT atd. from pygame.locals import * # Velikost okna aplikace WIDTH = 320 HEIGHT = 240 # Konstanty s n-ticemi představujícími základní barvy BLACK: Tuple[int, int, int] = (0, 0, 0) RED: Tuple[int, int, int] = (255, 0, 0) GRAY: Tuple[int, int, int] = (128, 128, 128) YELLOW: Tuple[int, int, int] = (255, 255, 0) CAPTION = "Sprites in Pygame" # Třída představující sprite zobrazený jako jednobarevný čtverec. class BlockySprite(pygame.sprite.Sprite): # Konstruktor def __init__(self, color: Tuple[int, int, int], size: int, x: int, y: int): # Nejprve je nutné zavolat konstruktor předka, # tj. konstruktor třídy pygame.sprite.Sprite: pygame.sprite.Sprite.__init__(self) # Vytvoření obrázku představujícího vizuální obraz spritu: self.image = pygame.Surface([size, size]) self.image.fill(color) # Vytvoření obalového obdélníku # (velikost se získá z rozměru obrázku) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y # Počáteční rychlost spritu self.speed_x = 0 self.speed_y = 0 # Nastavení barvy spritu, který kolidoval s hráčem def yellowColor(self) -> None: self.image.fill(YELLOW) # Nastavení barvy spritu, který nekolidoval s hráčem def grayColor(self) -> None: self.image.fill(GRAY) def initDisplay(caption: str) -> pygame.Surface: # Vytvoření okna pro vykreslování display = pygame.display.set_mode([WIDTH, HEIGHT]) # Nastavení titulku okna pygame.display.set_caption(caption) return display def createSprites() -> Tuple[pygame.sprite.Group, pygame.sprite.Group, BlockySprite]: # Objekt sdružující všechny sprity all_sprites: pygame.sprite.Group = pygame.sprite.Group() # Objekt sdružující všechny sprity kromě hráče all_sprites_but_player: pygame.sprite.Group = pygame.sprite.Group() # Vytvoření několika typů spritů # barva x y velikost wall1 = BlockySprite(GRAY, 50, 10, 10) wall2 = BlockySprite(GRAY, 15, 100, 100) wall3 = BlockySprite(GRAY, 15, 100, 150) wall4 = BlockySprite(GRAY, 15, 200, 100) wall5 = BlockySprite(GRAY, 15, 200, 150) wall6 = BlockySprite(GRAY, 15, 150, 100) wall7 = BlockySprite(GRAY, 15, 150, 150) player = BlockySprite(RED, 40, WIDTH / 2 - 20, HEIGHT / 2 - 20) # Přidání několika dalších spritů do seznamu # (jen jeden sprite - ten poslední - bude ve skutečnosti pohyblivý) all_sprites.add(wall1) all_sprites.add(wall2) all_sprites.add(wall3) all_sprites.add(wall4) all_sprites.add(wall5) all_sprites.add(wall6) all_sprites.add(wall7) all_sprites.add(player) # Seznam všech nepohyblivých spritů all_sprites_but_player.add(wall1) all_sprites_but_player.add(wall2) all_sprites_but_player.add(wall3) all_sprites_but_player.add(wall4) all_sprites_but_player.add(wall5) all_sprites_but_player.add(wall6) all_sprites_but_player.add(wall7) return all_sprites, all_sprites_but_player, player # Posun všech spritů ve skupině na základě jejich rychlosti def move_sprites(sprite_group: pygame.sprite.Group, playground_width: int, playground_height:int) -> None: for sprite in sprite_group: # Posun spritu sprite.rect.x = sprite.rect.x + sprite.speed_x sprite.rect.y = sprite.rect.y + sprite.speed_y # Kontrola, zda sprite nenarazil do okrajů okna if sprite.rect.x < 0: sprite.rect.x = 0 sprite.speed_x = 0 if sprite.rect.x + sprite.rect.width > playground_width: sprite.rect.x = playground_width - sprite.rect.width sprite.speed_x = 0 if sprite.rect.y < 0: sprite.rect.y = 0 sprite.speed_y = 0 if sprite.rect.y + sprite.rect.height > playground_height: sprite.rect.y = playground_height - sprite.rect.height sprite.speed_y = 0 # Vykreslení celé scény na obrazovku def draw_scene(display: pygame.Surface, background_color: Tuple[int, int, int], sprite_group: pygame.sprite.Group) -> None: # Vyplnění plochy okna černou barvou display.fill(background_color) # Vykreslení celé skupiny spritů do bufferu sprite_group.draw(display) # Obnovení obsahu obrazovky (překlopení zadního a předního bufferu) pygame.display.update() # Změna barvy spritu na základě kolize s hráčem def change_colors(sprite_group: pygame.sprite.Group, hit_list: List[pygame.sprite.Sprite]) -> None: # Projít všemi sprity ze skupiny, kterou detekovala kolizní funkce for sprite in sprite_group: if sprite in hit_list: sprite.yellowColor() else: sprite.grayColor() # Zjistí kolize spritu se "stěnami" (nepohyblivými sprity) def check_collisions(player: BlockySprite, sprite_group: pygame.sprite.Group) -> None: # Vytvoření seznamu spritů, které kolidují s hráčem hit_list = pygame.sprite.spritecollide(player, sprite_group, False) # Změna barev kolidujících spritů change_colors(sprite_group, hit_list) collisions = len(hit_list) # Přenastavení titulku okna caption = CAPTION + ": collisions " + str(collisions) pygame.display.set_caption(caption) def mainLoop(display: pygame.Surface, clock: pygame.time.Clock, all_sprites: pygame.sprite.Group, all_sprites_but_player: pygame.sprite.Group, player: BlockySprite) -> None: while True: # Načtení a zpracování všech událostí z fronty for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.quit() sys.exit() # Stiskem kurzorových kláves je možné měnit směr pohybu spritu elif event.key == pygame.K_LEFT: player.speed_x = -3 elif event.key == pygame.K_RIGHT: player.speed_x = +3 elif event.key == pygame.K_UP: player.speed_y = -3 elif event.key == pygame.K_DOWN: player.speed_y = +3 if event.type == KEYUP: # Puštění kurzorových kláves vede k zastavení pohybu spritu if event.key == pygame.K_LEFT: player.speed_x = 0 elif event.key == pygame.K_RIGHT: player.speed_x = 0 elif event.key == pygame.K_UP: player.speed_y = 0 elif event.key == pygame.K_DOWN: player.speed_y = 0 move_sprites(all_sprites, display.get_width(), display.get_height()) check_collisions(player, all_sprites_but_player) draw_scene(display, BLACK, all_sprites) clock.tick(20) def main() -> None: # Inicializace knihovny Pygame pygame.init() clock = pygame.time.Clock() display = initDisplay(CAPTION) all_sprites, all_sprites_but_player, player = createSprites() mainLoop(display, clock, all_sprites, all_sprites_but_player, player) if __name__ == "__main__": main() # finito
17. Kontrola na reálné chyby související s použitými datovými typy
Pokud nyní Mypy spustíme znovu, nalezne dvě reálné chyby, které se v programu nachází:
sprites3.py:82: error: Argument 3 to "BlockySprite" has incompatible type "float"; expected "int" [arg-type] sprites3.py:82: error: Argument 4 to "BlockySprite" has incompatible type "float"; expected "int" [arg-type] Found 2 errors in 1 file (checked 1 source file)
Tyto chyby jsou způsobeny tím, že operace / vrací hodnotu typu float, o čemž se můžeme snadno přesvědčit přímo v interpretru Pythonu:
>>> x=4/2 >>> type(x) <class 'float'> >>> y=4//2 >>> type(y) <class 'int'>
Oprava tedy bude snadná a bude se týkat jediného programového řádku:
player = BlockySprite(RED, 40, WIDTH // 2 - 20, HEIGHT // 2 - 20)
18. Výsledný kód s typovými informacemi po opravě všech chyb
Pro úplnost se podívejme na to, jak bude vypadat výsledný kód skriptu po jeho refaktoringu, přidání typových informací i opravě chyb nalezených nástrojem Mypy. Tento kód je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/mypy/sprites4.py:
#!/usr/bin/python # vim: set fileencoding=utf-8 from typing import NewType, Tuple, List import pygame import sys # Nutno importovat kvůli konstantám QUIT atd. from pygame.locals import * # Velikost okna aplikace WIDTH = 320 HEIGHT = 240 # Konstanty s n-ticemi představujícími základní barvy BLACK: Tuple[int, int, int] = (0, 0, 0) RED: Tuple[int, int, int] = (255, 0, 0) GRAY: Tuple[int, int, int] = (128, 128, 128) YELLOW: Tuple[int, int, int] = (255, 255, 0) CAPTION = "Sprites in Pygame" # Třída představující sprite zobrazený jako jednobarevný čtverec. class BlockySprite(pygame.sprite.Sprite): # Konstruktor def __init__(self, color: Tuple[int, int, int], size: int, x: int, y: int): # Nejprve je nutné zavolat konstruktor předka, # tj. konstruktor třídy pygame.sprite.Sprite: pygame.sprite.Sprite.__init__(self) # Vytvoření obrázku představujícího vizuální obraz spritu: self.image = pygame.Surface([size, size]) self.image.fill(color) # Vytvoření obalového obdélníku # (velikost se získá z rozměru obrázku) self.rect = self.image.get_rect() self.rect.x = x self.rect.y = y # Počáteční rychlost spritu self.speed_x = 0 self.speed_y = 0 # Nastavení barvy spritu, který kolidoval s hráčem def yellowColor(self) -> None: self.image.fill(YELLOW) # Nastavení barvy spritu, který nekolidoval s hráčem def grayColor(self) -> None: self.image.fill(GRAY) def initDisplay(caption: str) -> pygame.Surface: # Vytvoření okna pro vykreslování display = pygame.display.set_mode([WIDTH, HEIGHT]) # Nastavení titulku okna pygame.display.set_caption(caption) return display def createSprites() -> Tuple[pygame.sprite.Group, pygame.sprite.Group, BlockySprite]: # Objekt sdružující všechny sprity all_sprites: pygame.sprite.Group = pygame.sprite.Group() # Objekt sdružující všechny sprity kromě hráče all_sprites_but_player: pygame.sprite.Group = pygame.sprite.Group() # Vytvoření několika typů spritů # barva x y velikost wall1 = BlockySprite(GRAY, 50, 10, 10) wall2 = BlockySprite(GRAY, 15, 100, 100) wall3 = BlockySprite(GRAY, 15, 100, 150) wall4 = BlockySprite(GRAY, 15, 200, 100) wall5 = BlockySprite(GRAY, 15, 200, 150) wall6 = BlockySprite(GRAY, 15, 150, 100) wall7 = BlockySprite(GRAY, 15, 150, 150) player = BlockySprite(RED, 40, WIDTH // 2 - 20, HEIGHT // 2 - 20) # Přidání několika dalších spritů do seznamu # (jen jeden sprite - ten poslední - bude ve skutečnosti pohyblivý) all_sprites.add(wall1) all_sprites.add(wall2) all_sprites.add(wall3) all_sprites.add(wall4) all_sprites.add(wall5) all_sprites.add(wall6) all_sprites.add(wall7) all_sprites.add(player) # Seznam všech nepohyblivých spritů all_sprites_but_player.add(wall1) all_sprites_but_player.add(wall2) all_sprites_but_player.add(wall3) all_sprites_but_player.add(wall4) all_sprites_but_player.add(wall5) all_sprites_but_player.add(wall6) all_sprites_but_player.add(wall7) return all_sprites, all_sprites_but_player, player # Posun všech spritů ve skupině na základě jejich rychlosti def move_sprites(sprite_group: pygame.sprite.Group, playground_width: int, playground_height:int) -> None: for sprite in sprite_group: # Posun spritu sprite.rect.x = sprite.rect.x + sprite.speed_x sprite.rect.y = sprite.rect.y + sprite.speed_y # Kontrola, zda sprite nenarazil do okrajů okna if sprite.rect.x < 0: sprite.rect.x = 0 sprite.speed_x = 0 if sprite.rect.x + sprite.rect.width > playground_width: sprite.rect.x = playground_width - sprite.rect.width sprite.speed_x = 0 if sprite.rect.y < 0: sprite.rect.y = 0 sprite.speed_y = 0 if sprite.rect.y + sprite.rect.height > playground_height: sprite.rect.y = playground_height - sprite.rect.height sprite.speed_y = 0 # Vykreslení celé scény na obrazovku def draw_scene(display: pygame.Surface, background_color: Tuple[int, int, int], sprite_group: pygame.sprite.Group) -> None: # Vyplnění plochy okna černou barvou display.fill(background_color) # Vykreslení celé skupiny spritů do bufferu sprite_group.draw(display) # Obnovení obsahu obrazovky (překlopení zadního a předního bufferu) pygame.display.update() # Změna barvy spritu na základě kolize s hráčem def change_colors(sprite_group: pygame.sprite.Group, hit_list: List[pygame.sprite.Sprite]) -> None: # Projít všemi sprity ze skupiny, kterou detekovala kolizní funkce for sprite in sprite_group: if sprite in hit_list: sprite.yellowColor() else: sprite.grayColor() # Zjistí kolize spritu se "stěnami" (nepohyblivými sprity) def check_collisions(player: BlockySprite, sprite_group: pygame.sprite.Group) -> None: # Vytvoření seznamu spritů, které kolidují s hráčem hit_list = pygame.sprite.spritecollide(player, sprite_group, False) # Změna barev kolidujících spritů change_colors(sprite_group, hit_list) collisions = len(hit_list) # Přenastavení titulku okna caption = CAPTION + ": collisions " + str(collisions) pygame.display.set_caption(caption) def mainLoop(display: pygame.Surface, clock: pygame.time.Clock, all_sprites: pygame.sprite.Group, all_sprites_but_player: pygame.sprite.Group, player: BlockySprite) -> None: while True: # Načtení a zpracování všech událostí z fronty for event in pygame.event.get(): if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.quit() sys.exit() # Stiskem kurzorových kláves je možné měnit směr pohybu spritu elif event.key == pygame.K_LEFT: player.speed_x = -3 elif event.key == pygame.K_RIGHT: player.speed_x = +3 elif event.key == pygame.K_UP: player.speed_y = -3 elif event.key == pygame.K_DOWN: player.speed_y = +3 if event.type == KEYUP: # Puštění kurzorových kláves vede k zastavení pohybu spritu if event.key == pygame.K_LEFT: player.speed_x = 0 elif event.key == pygame.K_RIGHT: player.speed_x = 0 elif event.key == pygame.K_UP: player.speed_y = 0 elif event.key == pygame.K_DOWN: player.speed_y = 0 move_sprites(all_sprites, display.get_width(), display.get_height()) check_collisions(player, all_sprites_but_player) draw_scene(display, BLACK, all_sprites) clock.tick(20) def main() -> None: # Inicializace knihovny Pygame pygame.init() clock = pygame.time.Clock() display = initDisplay(CAPTION) all_sprites, all_sprites_but_player, player = createSprites() mainLoop(display, clock, all_sprites, all_sprites_but_player, player) if __name__ == "__main__": main() # finito
19. Repositář s demonstračními příklady
Všechny Pythonovské skripty, které jsme si v dnešním článku ukázali, naleznete na adrese https://github.com/tisnik/most-popular-python-libs. Následují odkazy na jednotlivé příklady (pro jejich spuštění je nutné mít nainstalován balíček mypy):
20. Odkazy na Internetu
- mypy homepage
https://www.mypy-lang.org/ - mypy documentation
https://mypy.readthedocs.io/en/stable/ - Mypy na PyPi Optional static typing for Python
https://pypi.org/project/mypy/ - 5 Reasons Why You Should Use Type Hints In Python
https://www.youtube.com/watch?v=dgBCEB2jVU0 - Python Typing – Type Hints & Annotations
https://www.youtube.com/watch?v=QORvB-_mbZ0 - What Problems Can TypeScript Solve?
https://www.typescriptlang.org/why-create-typescript - How to find code that is missing type annotations?
https://stackoverflow.com/questions/59898490/how-to-find-code-that-is-missing-type-annotations - Do type annotations in Python enforce static type checking?
https://stackoverflow.com/questions/54734029/do-type-annotations-in-python-enforce-static-type-checking - Understanding type annotation in Python
https://blog.logrocket.com/understanding-type-annotation-python/ - Static type checking with Mypy — Perfect Python
https://www.youtube.com/watch?v=9gNnhNxra3E - Static Type Checker for Python
https://github.com/microsoft/pyright - Differences Between Pyright and Mypy
https://github.com/microsoft/pyright/blob/main/docs/mypy-comparison.md - 4 Python type checkers to keep your code clean
https://www.infoworld.com/article/3575079/4-python-type-checkers-to-keep-your-code-clean.html - Pyre: A performant type-checker for Python 3
https://pyre-check.org/ - „Typing the Untyped: Soundness in Gradual Type Systems“ by Ben Weissmann
https://www.youtube.com/watch?v=uJHD2×yv7×o - Covariance and contravariance (computer science)
https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) - Functional Programming: Type Systems
https://www.youtube.com/watch?v=hy1wjkcIBCU - A Type System From Scratch – Robert Widmann
https://www.youtube.com/watch?v=IbjoA5×VUq0 - „Type Systems – The Good, Bad and Ugly“ by Paul Snively and Amanda Laucher
https://www.youtube.com/watch?v=SWTWkYbcWU0 - Type Systems: Covariance, Contravariance, Bivariance, and Invariance explained
https://medium.com/@thejameskyle/type-systems-covariance-contravariance-bivariance-and-invariance-explained-35f43d1110f8 - Statická vs. dynamická typová kontrola
https://www.root.cz/clanky/staticka-dynamicka-typova-kontrola/ - Typový systém
https://cs.wikipedia.org/wiki/Typov%C3%BD_syst%C3%A9m - Comparison of programming languages by type system
https://en.wikipedia.org/wiki/Comparison_of_programming_languages_by_type_system - Flow
https://flow.org/ - TypeScript
https://www.typescriptlang.org/ - Sorbet
https://sorbet.org/ - Pyright
https://github.com/microsoft/pyright - Mypy: Type hints cheat sheet
https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html - PEP 484 – Type Hints
https://peps.python.org/pep-0484/