V minulém díle jste viděli...¶
Práce se základními moduly standardní knihovny¶
Moduly slouží k přehlednému uchovávání definicí funkcí a proměnných v externích souborech, které do svého projektu můžeme jednoduše zahrnout pomocí příkazu import
. Tento přístup usnadňuje správu kódu a umožňuje opakované využití kódu bez kopírování definic do každého nového programu. To je zvláště užitečné při psaní funkcí, které chcete používat v různých projektech, aniž byste museli opisovat jejich definici do každého programu znovu.
Příkaz import
¶
Příkaz import
je základním nástrojem, jak zavést moduly a jejich funkcionalitu do našeho kódu. Tento příkaz má několik variant.
# Základní import
import module_name
# Import konkrétních prvků
from module_name import element1, element2
# Alternativní způsoby importu, alternativní pojmenování
import module_name as alias
from another_module import element1 as el1, element2 as el2
# Import všech prvků modulu - Nedoporučováno!
from module_name import *
Při volání příkazu import
se importovaný objekt stává součástí namespace, ve kterém je příkaz volán. Typicky se volá na začátku programu v globálním namespace, a importované objekty jsou pak dostupné odkudkoli v programu. Může se ale také volat v lokálním namespace, například uvnitř funkce, a efekt importu je pak platný jen v tomto kontextu.
Výběr modulů standardní knihovny¶
math
¶
Modul math
poskytuje matematické funkce pro pokročilé výpočty. Obsahuje metody pro operace s čísly, trigonometrii, logaritmy a další matematické operace. Knihovna je velice bohatá a nabízí široké spektrum funkcí, např. math.ceil(x)
, math.floor(x)
, math,trunc(x)
, math.prod(iterable, *, start=1)
, math,exp(x)
,math.log(x[, base] )
, math.sqrt(x)
a konstant, např. math.pi
,math.e
nebo math,inf
cmath
¶
Tento modul poskytuje funkce podobné, jako v modulu math
. Umí však počítat v komplexním prostoru.
Navíc nabízí nějaké funkce aplikovatelné jen v komplexní rovině.
random
¶
Modul random slouží k generování pseudo-náhodných čísel. Pomocí různých metod může být využit v různých situacích, například při hraní her nebo simulacích. Hodit se mohou například funkce random.randint(a, b)
, random.choice(seq)
, random.random()
nebo random.shuffle(x)
.
sys
¶
Modul sys
poskytuje funkce a hodnoty, které Python interpreter využívá nebo ho ovlivňují. Umožňuje například manipulaci s cestami nebo předávání argumentů při spouštění skriptu.
os
¶
Modul os
poskytuje funkce pro interakci se systémovým prostředím, jako je manipulace s adresáři, spouštění příkazů nebo práce s proměnnými prostředí. Slouží jako rozhraní ke komunikaci s operačním systémem.
pprint
¶
Modul pprint
(pretty-print) slouží k "krásnému" formátování výstupu, což je užitečné zejména při práci s komplexními datovými strukturami.
collections
¶
Modul collections
poskytuje alternativní datové typy k vestavěným typům jako seznamy nebo slovníky.
Tyto dodatečné datové typy mohou usnadnit práci při implementaci například některých algoritmů.
itertools
¶
Modul itertools
poskytuje funkce pro vytváření efektivních iterátorů, které jsou užitečné jak samostatně, tak ve vzájemné kombinaci. Obsahuje funkce například pro slučování a kombinaci více zdrojů, opakování, nebo filtrování.
csv
¶
Modul csv
poskytuje funkcionalitu pro práci s formátem CSV (Comma-Separated Values), což je často používaný formát pro ukládání tabulkových dat.
json
¶
Modul json
umožňuje práci s formátem JSON (JavaScript Object Notation), což je populární formát pro výměnu dat mezi aplikacemi.
argparse
¶
argparse
je velmi užitečný modul v jazyce Python, který umožňuje jednoduché a efektivní zpracování příkazové řádky. Tento modul slouží k definování, konfiguraci a parsování argumentů a jejich hodnot spouštěného skriptu. To usnadňuje usnadňuje psaní rozsáhlejších Python skriptů.
Externí (nestandardní) balíčky a moduly¶
Kromě standardních modulů, které jsou součástí každé instalace Pythonu, existuje bohatý ekosystém externích modulů, nazývaných balíčky.
Tyto balíčky poskytují širokou škálu funkcionalit, které mohou být snadno integrovány do projektů.
Instalace externích balíčků je obvykle prováděna pomocí správce balíčků, například pip
.
Zde je několik příkladů externích balíčků:
requests
- Slouží k jednoduchému provádění HTTP requestůnumpy
- Poskytuje podporu pro efektivní manipulaci s multidimenzionálními poli a matematickými funkcemi.Pandas
- Nabízí vysokoúrovňové datové struktury a nástroje pro analýzu datDjango
- webový framework pro vývoj moderních webových aplikací v jazyce Python
Modulární stavba programu¶
Ukázali jsme si, jak vytvořit jednoduchý modul. To je velmi snadné - jedná se o obyčejný textový soubor s příponou .py, který obsahuje definice funkcí a tříd, konstanty a ostatní výrazy v jazyce Python. Název tohoto souboru zároveň reprezentuje jméno modulu (bez přípony .py).
Jméno modulu je také automaticky přístupné v globální proměnné (string) name. Ukázali jsme si, jak Python vytvořený modul nalezne, co je to sys.path
a jak s ní pracovat. Vysvětlili jsme si jak funguje spustitelný kód při importu modulů a představili si velmi často používanou konstrukci if __name__ == '__main__':
spouštící blok kódu jen při spuštění jako skript
Balíčky¶
Balíček (package) je kolekce modulů organizovaných do adresářové struktury. Může obsahovat také další balíčky. Slouží k lepší organizaci kódu větších projektů a umožňují hierarchickou strukturu modulů. Balíčky jsou reprezentovány adresáři obsahujícími soubory modulů a speciální soubor __init__.py
, který indikuje, že daný adresář je považován za balíček. Vysvětlili jsme si, co znamená proměnná __all__
v tomto souboru a jak se používá.
Balíček vytvoříme jednoduše:
- vytvoříme adresář pro balíček (název adresáře odpovídá názvu balíčku)
- umístíme do něj soubory modulů (
.py
soubory) - vytvoříme v něm soubor
__init__.py
. Ten způsobí, že daný adresář bude balíčkem. Tento soubor může být prázdný. Může ovšem také obsahovat nějaký inicializační kód, podobně jako u modulů. Takový kód je spuštěn jen jednou při prvotním importu balíčku.
Importovat z balíčků již umíme:
import balicek.modul
from balicek import modul
from balicek.podbalicek import modul
from balicek.podbalicek.modul import funkce
Objektově Orientované Programování (OOP)¶
Objektově orientované programování (Object-Oriented Programming – OOP) představuje moderní a efektivní přístup (paradigma) k vývoji softwaru, které klade důraz na modularitu a znovupoužitelnost. V OOP je program strukturován kolem objektů, které mohou obsahovat data ve formě proměnných a provádět akce pomocí funkcí. Těmto proměnným v kontextu OOP říkáme atributy a funkcím metody. Tento přístup tedy svazuje data a funkce, které s nimi pracují do jednoho celku. Jinými slovy je objekt entita, která má nějaký stav a chování. Z těchto objektů se následně staví celý program, přičemž jednotlivé moduly na sobě mohou být nezávislé, a na to konto snadno použitelné i v jiných projektech.
Příkladem objektu může být třeba list
(seznam): atributy jsou například jeho rozměry a prvky, metody třeba list.append(x)
nebo list.remove(x)
. Zjednodušeně řečeno je OOP přístup modelování konkrétních, reálných objektů a jejich vzájemných vztahů.
O Pythonu hovoříme jako o objektově orientovaném jazyku. To znamená, že skoro vše v Pythonu je objekt s nějakými atributy a metodami.
Základním stavebním kamenem OOP je třída (class), která definuje atributy a metody nějakého objektu. S tímto pojmem jsme se již setkali (například při výpisu type(list)
, ale ještě jsme se u něj nezastavili. Podívejme se nyní, jak třídu definovat:
class MyClass:
some_attr = 5 # class attribute
Předchozí kód definuje třídu s názvem MyClass
a jediným atributem some_attr
, bez metod. Tento kód nevytváří žádný objekt, podobně jako v případě definice funkce. Abychom vytvořili objekt, je třeba vytvořit tzv. instanci třídy:
class_instance = MyClass()
class_instance1 = MyClass()
# ukázka debugger
Všimněte si závorek při vytváření instance – tu totiž vytvoříme zavoláním třídy MyClass
(opět je nasnadě podobnost s voláním funkcí). K atributům a metodám třídy přistupujeme pomocí tečkové notace (pro čtení i zápis):
print(class_instance.some_attr)
print(class_instance1.some_attr)
# ukázka: změna hodnoty atributu
class_instance.some_attr = 100
print(class_instance.some_attr)
print(class_instance1.some_attr)
2 5 100 5
Poznámka: Narozdíl od pojmenovávání proměnných a funkcí, pro názvy tříd se vžilo použití CamelCase (velká počáteční písmena slov).
Tento příklad představuje nejjednodušší možnou třídu, ale k reálnému použití se nehodí. Povšimněme si ještě, že atribut some_attr
bude stejný pro všechny instance třídy.
Metoda __init__
¶
Abychom pochopili přidanou hodnotu tříd, je třeba porozumět vestavěné metodě __init__()
. Tuto metodu má automaticky každá třída a volá se kdykoliv vzniká nová instance. Z tohoto důvodu o ní často mluvíme jako o tzv. konstruktoru. Pomocí konstruktoru můžeme nastavit atributy instance (tj. konkrétního objektu) nebo provést operace související s jejím vznikem. V závislosti na tom, jestli se jedná o atribut společný pro celou třídu, nebo jen instanci, hovoříme o atributu třídy (class attribute) a atributu instance (instance attribute). Nyní je nám jasnější, proč při vytváření instance voláme třídu se závorkami – voláme totiž vlastně její konstruktor.
V následujícím příkladu vytvoříme třídu Person
se dvěma atributy, konstruktorem a jednou další metodou. Všimněte si, že v definici metod uvádíme jako jeden z parametrů (konkrétně první) identifikátor self
, který představuje odkaz na aktuální instanci třídy, pomocí něhož přistupujeme k jejím atributům a metodám. Ve volání metod tento argument vynecháváme, Python jej za nás doplní automaticky.
Pokud chceme v rámci metody přistoupit k některým atributům třídy či instance, můžeme tak učinit pomocí parametru self
a tečkové notace. Tím se odkazujeme na aktuální instanci.
Poznámka: Identifikátor
self
je podobný ukazatelithis
v jazycích C++ a Java.
class Person:
'''A class representing a single person's record.'''
def __init__(self, name, year_of_birth):
'''Constructor that sets instance attributes.
Expects two arguments: name (str), age (int).'''
self.name = name
self.year_of_birth = year_of_birth
self.compute_age()
print("A new instance created with the following attributes: ")
print(f"name: {self.name}, year of birth: {self.year_of_birth}")
def compute_age(self):
'''Computes the age of person.'''
from datetime import datetime
current_year = datetime.now().year
return current_year - self.year_of_birth
# create an instance
p1 = Person("John", 1936)
A new instance created with the following attributes: name: John, year of birth: 1936
# ukázka: atributy a metody - výpis a změna
print(p1.compute_age(), p1.name, p1.year_of_birth)
p1.year_of_birth = 1990
print(p1)
87 John 1936 <__main__.Person object at 0x7fc95419b550>
Metoda __str__
¶
Nyní se podívejme, co se stane, pokud se pokusíme vytisknout typ p1
a také přímo p1
:
print(type(p1))
print(p1)
<class '__main__.Person'> <__main__.Person object at 0x7f963c6ca1d0>
Z výpisu vidíme, že se typ je třída Person
definovaná v hlavním modulu (__main__
je vstupní bod Python Interpreteru). Druhý výpis nám toho příliš neprozradí - pouze že se jedná o instanci třídy (Person object
) uloženou na konkrétní adrese v paměti. Nezřídka proto chceme definovat, jakým způsobem třídu vypsat. Příkladem může být výpis obyčejného seznamu:
a = [1, 2, 3]
print(type(a))
print(a)
<class 'list'> [1, 2, 3]
Vidíme, že i v tomto případě se jedná o třídu, konkrétně list
. Ta již není definovaná v hlavním modulu.
Výpis instance funguje tak, jak bychom očekávali – do konzole se vytiskne obsah seznamu. Toto chování je způsobeno implementovanou další vestavěnou metodou __str__
, která definuje převod instance třídy na string. Tuto metodu funke print
zavolá a její návratovou hodnotu vytiskne.
Podívejme se nyní, jak bychom mohli předchozí příklad přepsat trochu elegantněji:
class Person:
'''A class representing a single person's record.'''
def __init__(self, name, year_of_birth):
'''Constructor that sets instance attributes.
Expects two arguments: name (str), age (int).'''
self.name = name
self.year_of_birth = year_of_birth
print("A new instance created with the following attributes: ")
print(self)
def __str__(self):
'''Prints formatted information about name and age attributes.'''
#print("Converting to string...") # uncomment to see when __str__ gets called
return f"name: {self.name}, year of birth: {self.year_of_birth}"
def compute_age(self):
'''Computes the age of person.'''
from datetime import datetime
current_year = datetime.now().year
return current_year - self.year_of_birth
# create an instance
p1 = Person("John", 1936)
# change the attribute 'year_of_birth'
p1.year_of_birth = 1937
# print the information
print(p1)
A new instance created with the following attributes: name: John, year of birth: 1936 name: John, year of birth: 1937
Poznámka: atributy třídy/instance můžeme stejně jako instance smazat pomocí klíčového slova
del
.
# create an instance
p2 = Person("Jack", 1999)
A new instance created with the following attributes: name: Jack, year of birth: 1999
# delete age attribute
del p2.year_of_birth
# try to print it
try:
print(p2)
# raises exception because attribute year_of_birth does not exist anymore
except AttributeError as error:
print(f"Error: {error}")
Error: 'Person' object has no attribute 'year_of_birth'
# now delete the instance altogether
del p2
# try to print it
try:
print(p2)
# raises exception because the variable with name p2 no longer exists
except NameError as error:
print(f"Error: {error}")
Error: name 'p2' is not defined
# ukázka: že del funguje i pro jiné datové typu (ukázat v debuggeru)
x = 67
del x
Zapouzdření¶
Jedním z hlavních důvodů pro použití objektového přístupu je tzv. zapouzdření, jakési odstínění programátora, jež využívá danou třídu od detailů její implementace. Zapouzdření umožňuje skrýt některé atributy a metody tak, aby mohly být použit pouze "zevnitř". Objekt si tak můžeme představit jako "blackbox", o kterém sice nevíme, jak funguje uvnitř, ale víme, jak se chová navenek a jak se používá (tedy jaké má rozhraní neboli interface). Nemůžeme proto způsobit nějakou chybu, protože používáme a vidíme jen to, co nám tvůrce třídy zpřístupnil.
Jako příklad můžeme použít opět třídu list
. Jako uživatele této třídy nás nezajímá, jakým způsobem funguje obsluha paměti při přidávání jednotlivých prvků do seznamu - stačí nám pouze znalost metody list.append(x)
, která tento problém řeší interně.
Dědění¶
Dalším klíčovým konceptem OOP je tzv. dědění (inheritance). Dědění nám umožňuje definovat třídu, tzv. potomka (child class), která "zdědí" všechny metody a atributy nějaké jiné třídy, tzv. předka (parent class), a zpravidla je nějak rozšíří. Potomek je tedy specializovanější verzí svého předka. Nejlépe si tento koncept ukážeme na příkladu. Jako předka použijeme třídu Person
definovanou dříve. Od ní odvodíme více specifikovanou třídu Student
:
class Student(Person): # this is how we define inheritance
pass
s0 = Student("Mahulena", 1990) # we can create a new instance just as before
print("Age:", s0.compute_age())
A new instance created with the following attributes: name: Mahulena, year of birth: 1990 Age: 33
isinstance(s0, Person)
True
isinstance(s0, Student)
True
V tuto chvíli má třída Student
stejné atributy a metody jako třída Person
. Nyní ji budeme chtít rozšířit přidáním atributu grades
, což bude seznam uchovávající studentovi známky:
class Student(Person): # this is how we define inheritance
def __init__(self, name, age, grades):
'''Constructor that sets instance attributes.
Expects three arguments: name (str), age (int) and grade (float).'''
Person.__init__(self, name, age) # we call parent constructor
self.grades=grades # we add another attribute
s1 = Student("Karel", 1995, [1,3,5]) # we redefined the constructor, now it expects three arguments
print(s1.grades) # we can access the additional attribute
print("Age:", s1.compute_age()) # access parent method
print(s1)
A new instance created with the following attributes: name: Karel, year of birth: 1995 [1, 3, 5] Age: 28 name: Karel, year of birth: 1995
Když definujeme konstruktor, přetížíme (override) tím definici konstruktoru předka. Při vzniku nové instance se bude používat tento nový konstruktor. Podobné chování platí pro jakoukoli metodu.
Povšimněte si volání konstruktoru předka Person
, do kterého předáváme rovněž odkaz na aktuální instance self
. Místo konkrétního názvu třídy můžeme použít funkci super()
, která vrátí název předka. V tomto případě funkci nevoláme s argumentem self
.
class Student(Person): # this is how we define inheritance
def __init__(self, name, age, grades): # overriding parent constructor
'''Constructor that sets instance attributes.
Expects three arguments: name (str), age (int) and grade (float).'''
super().__init__(name, age) # we call parent constructor using super()
self.grades = grades # we add another attribute. This can be another object
s1=Student("Karel", 1995, [1,3,5]) # we redefined the constructor, now it expects three arguments
print(s1.grades) # we can access the additional attribute
print("Age:",s1.compute_age()) # access parent method
A new instance created with the following attributes: name: Karel, year of birth: 1995 [1, 3, 5] Age: 28
Kromě atributů můžeme přidávat/přetěžovat rovněž metody. Přetěžme metodu __str__
. Povšimněte si, že při vykonávání příkazu print(self)
v konstruktoru předka dochází k výpisu pomocí přetížené funkce __str__
. To proto, že funkce přetížené potomkem "mají přednost". Z tohoto důvodu je nutné přiřazení atributu grades
provést před voláním konstruktoru předka.
Za zdůraznění stojí, že atributem může být opět nějaký objekt (jako v našem případě, kdy je atribut grades
instance třídy list
)
class Student(Person): # this is how we define inheritance
def __init__(self, name, year_of_birth, grades):
'''Constructor that sets instance attributes.
Expects three arguments: name (str), age (int) and grade (float).'''
self.grades = grades # we add another attribute
super().__init__(name, year_of_birth) # we call parent constructor using super()
def __str__(self):
'''Overriding str method.'''
return f"{super().__str__()}, grades: {self.grades}"
def add_grade(self,grade):
'''Appends grade to a list of grades.'''
self.grades.append(grade)
def compute_grades_mean(self,weights=None):
'''Computes the mean of all grades.
Optional argument weights gives weights to grades.
By default, a simple mean is computed.'''
if weights is None:
return sum(self.grades) / len(self.grades)
return sum([w*grade for w, grade in zip(weights, self.grades)]) / sum(weights)
s1=Student("Karel", 1995, [1,3,5]) # we redefined the constructor, now it expects three arguments
# add grades
s1.add_grade(1)
s1.add_grade(2)
print(s1) # print the instance, calling __str__ method
print("Average grade:", s1.compute_grades_mean())
print("Average weighted grade:", s1.compute_grades_mean([0.5, 2, 5, 1, 1]))
A new instance created with the following attributes: name: Karel, year of birth: 1995, grades: [1, 3, 5] name: Karel, year of birth: 1995, grades: [1, 3, 5, 1, 2] Average grade: 2.4 Average weighted grade: 3.6315789473684212
Poznámka: Dědění bychom tedy měli používat v případě, že potomek je specializací předka (student je speciální případ člověka atd.), nikoliv pokud předek má potomka (například auto má kolo - kolo rozhodně nebude dědit od auta).
Polymorfismus¶
Polymorfismus (polymorphism) znamená jednoduše "mít mnoho forem". V praxi to znamená, že funkci se stejným názvem můžeme volat na různé objekty. Např. funkci len
můžeme zavolat na instanci tříd list
, tuple
, dict
nebo str
:
# polymorphic behavior with len() function
a = [1, 2, 3, 4, 5]
b = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
c = {'first': 1, 'second': 2}
d = "nazdárek párek"
# use the len() function to get the length of each object and print the results
print(f"Length of list 'a': {len(a)}")
print(f"Length of tuple 'b': {len(b)}")
print(f"Length of dictionary 'c': {len(c)}")
print(f"Length of string 'd': {len(d)}")
Length of list 'a': 5 Length of tuple 'b': 10 Length of dictionary 'c': 2 Length of string 'd': 14
V kontextu OOP pak může více tříd mít metody se stejným názvem. Příkladem může být metoda count
:
# polymorphic behavior with count() method
a = [1, 2, 3, 1, 4, 1, 5]
b = (10, 20, 30, 10, 40, 10, 50)
c = "Living is just eternal pain and suffering. Especially if you are a programmer."
# using count() method with different types
count_in_list = a.count(1)
count_in_tuple = b.count(10)
count_in_string = c.count(" ")
print(f"Count of 1 in list: {count_in_list}")
print(f"Count of 10 in tuple: {count_in_tuple}")
print(f"Count of blank spaces in string: {count_in_string}")
Count of 1 in list: 3 Count of 10 in tuple: 3 Count of blank spaces in string: 12
Obecně je polymorfismus situace, ve které může jeden objekt zastoupit jiný objekt se stejným rozhraním.
Příklady¶
1) Bankovní účet¶
Vytvořte třídu BankAccount
s konstruktorem, který přijímá jméno a výchozí zůstatek (defaultně 0).
Implementujte metody deposit
a withdraw
pro vkládání a výběr peněz. Při pokusu o výběr většího obnosu, než je stav účtu, se žádné peníze nevyberou, ale program neskončí.
Implementujte metodu get_balance
pro získání aktuálního zůstatku na účtu.
class BankAccount:
def __init__(self, name, balance = 0):
self.name = name
self.balance = balance
def __str__(self):
return(f"BankAccount {self.name} : {self.balance}")
def deposit(self, amount):
if (amount < 0):
print("Invalid deposit amount. Deposit must be a non-negative number.")
else:
self.balance += amount
def withdraw(self, amount):
if (amount < 0):
print("Invalid withdrawal amount. Deposit must be a non-negative number.")
else:
if amount <= self.balance:
self.balance -= amount
else:
print("Insufficient funds. Withdrawal unsuccessful.")
def get_balance(self):
return self.balance
a = BankAccount("John Doe")
a.deposit(100)
a.withdraw(50)
assert a.get_balance() == 50, a.get_balance()
a = BankAccount("Peter Pan",100)
a.deposit(100)
a.withdraw(50)
assert a.get_balance() == 150, a.get_balance()
a = BankAccount("Bob Smith", 100)
a.withdraw(500)
assert a.get_balance() == 100, a.get_balance()
2) Geometrické tvary¶
Vytvořte třídu Rectangle
s konstruktorem, který přijímá velikost dvou stran. Definujte metodu calculate_area
, která spočítá obsah obdélníku.
Vytvořte potomka Square
, který je speciálním případem obdélníku tak, aby pro něj metoda calculate_area
vracela správný výsledek a konstruktor přijímal jediný argument.
Ošetřete hodnoty předávané do konstruktoru a případně vyvolejte příslušnou výjimku.
class Rectangle:
def __init__(self, width, height):
if width <= 0 or height <= 0:
raise ValueError("Both width and height must be greater than zero.")
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side_length):
# super().__init__(side_length, side_length)
Rectangle.__init__(self, side_length, side_length)
r = Rectangle(10, 5.5)
assert r.calculate_area() == 55
s = Square(10)
assert s.calculate_area() == 100
s = Square(9)
assert s.calculate_area() == 81
3) Knihovna¶
Vytvořte třídu Book
s konstruktorem, který přijímá název a autora knihy, případně další informace o nějaké knize (rok vydání, nakladatelství, ISBN...). Implementujte metodu __str__
.
Vytvořte třídu Library
s konstruktorem, který inicializuje prázdný seznam knih.
Implementujte metodu add_book
v třídě Library
pro přidání knihy do knihovny.
Implementujte metodu list_books
v třídě Library
pro výpis názvů a autorů knih v knihovně (případně dalších atributů)
class Book:
def __init__(self, title, author, year=None, publisher=None, isbn=None):
self.title = title
self.author = author
self.year = year
self.publisher = publisher
self.isbn = isbn
def __str__(self):
s = f"{self.title} by {self.author}"
return s
class Library:
def __init__(self):
self.books = []
def add_book(self, book):
if isinstance(book, Book):
self.books.append(book)
print(f"Book '{book.title}' added to the library.")
else:
print("Invalid book object. Please provide a valid Book instance.")
def list_books(self):
if not self.books:
print("The library is empty.")
else:
print("Books in the library:")
for book in self.books:
print(book)
print("")
book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 1925, "Scribner", "9780743273565")
book2 = Book("To Kill a Mockingbird", "Harper Lee", 1960, "J.B. Lippincott & Co.", "9780061120084")
print(book1)
print(book2)
library = Library()
library.list_books()
library.add_book(book1)
library.add_book(book2)
library.list_books()
The Great Gatsby by F. Scott Fitzgerald To Kill a Mockingbird by Harper Lee The library is empty. Book 'The Great Gatsby' added to the library. Book 'To Kill a Mockingbird' added to the library. Books in the library: The Great Gatsby by F. Scott Fitzgerald To Kill a Mockingbird by Harper Lee
4) Lepší knihovna¶
Vylepšete předchozí třídu tak, aby mohla obsahovat více stejných knih a zároveň doplňte funkci borrow_book
a return_book
. V případě pokusu o výpůjčku knihy, která neexistuje nebo je již zapůjčená vyvolejte vyjímku.
class Library:
def __init__(self):
self.books = []
self.borrowed = []
def add_book(self, book):
if isinstance(book, Book):
self.books.append(book)
print(f"Book '{book.title}' added to the library.")
else:
print("Invalid book object. Please provide a valid Book instance.")
def list_books(self):
print("----------------")
if not self.books:
print("The library is empty.")
else:
print("Books in the library:")
for book in self.books:
print(book)
print("")
def borrow_book(self, name):
for book in self.books:
if book.title == name:
self.books.remove(book)
self.borrowed.append(book)
return
raise ValueException(f"Book called {name} not available.")
def return_book(self, name):
for book in self.borrowed:
if book.title == name:
self.borrowed.remove(book)
self.books.append(book)
return
raise ValueException(f"Book called {name} not borrowed.")
book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 1925, "Scribner", "9780743273565")
book2 = Book("To Kill a Mockingbird", "Harper Lee", 1960, "J.B. Lippincott & Co.", "9780061120084")
book3 = Book("The Great Gatsby", "F. Scott Fitzgerald", 1925, "Scribner", "9780743273565")
library = Library()
library.list_books()
library.add_book(book1)
library.add_book(book2)
library.add_book(book3)
library.list_books()
library.borrow_book("The Great Gatsby")
library.borrow_book("The Great Gatsby")
library.list_books()
library.return_book("The Great Gatsby")
library.return_book("The Great Gatsby")
library.list_books()
---------------- The library is empty. Book 'The Great Gatsby' added to the library. Book 'To Kill a Mockingbird' added to the library. Book 'The Great Gatsby' added to the library. ---------------- Books in the library: The Great Gatsby by F. Scott Fitzgerald To Kill a Mockingbird by Harper Lee The Great Gatsby by F. Scott Fitzgerald ---------------- Books in the library: To Kill a Mockingbird by Harper Lee ---------------- Books in the library: To Kill a Mockingbird by Harper Lee The Great Gatsby by F. Scott Fitzgerald The Great Gatsby by F. Scott Fitzgerald
5) Zapisovatel do souboru¶
Vytvořte třídu FileWriter
, která do konstruktoru dostane název textového souboru, který následně vytvoří. Implementujte metodu append_text(text)
, která do souboru přidá zadaný text, metodu get_text
, která vrátí obsah souboru, metodu clear_file
, která smaže veškerý text v souboru a metodu delete_file
, která zcela odstraní textový soubor.
class FileWriter:
pass
file_writer = FileWriter("example.txt")
file_writer.append_text("Hello, this is line 1.")
file_writer.append_text("And this is line 2.")
print("Content of the file:")
print(file_writer.get_text())
file_writer.clear_file()
print("\nContent of the file after clearing:")
print(file_writer.get_text())
file_writer.append_text("This is a new line after clearing.")
print("\nUpdated content of the file:")
print(file_writer.get_text())
file_writer.delete_file()
6) Body v prostoru¶
Implementujte třídu Point
, která bude reprezentovat bod v prostoru (souřadnice [x, y, z]), případně v rovině (souřadnice z bude nulová) nebo na přímce (y i z budou nulové). Můžete využít principu dědění pro odlišení těchto třech případů se společným předkem.
Implementujte metodu compute_distance
která jako argument dostane další bod ve stejném prostoru (tj. počítáme vzdálenost vždy jen mezi dvěma body v prostoru/rovině/přímce, nikdy ne mezi bodem v rovině a prostoru/přímce) a spočte vzdálenost těchto dvou bodů. V případě, že body jsou v "jiných" prostorech, vyvolá výjimku.
Implementujte metodu compute_distance_in_projection
, která spočítá vzdálenost libovolných dvou bodů tak, že zohlední jen nenulové složky souřadnic. (tj. např. [1,1] a [2,2,5] je výsledek stejný jako pro [1,1] a [2,2], tedy sqrt(2))
Dokážete program zobecnit na libovolnou dimenzionalitu prostoru?
class Point:
#def __init__(self, x, y, z):
# self.x = (x, y, z)
def __init__(self, *x):
self.x = x
def compute_distance(self, other):
if isinstance(other, Point):
if len(self.x) == len(other.x):
# Oba body jsou ve stejném prostoru, rovině nebo na přímce
distance = (sum( (x-y)**2 for (x, y) in zip(self.x, other.x)))**0.5
return distance
else:
raise ValueError("Cannot compute distance between points in different spaces.")
else:
raise ValueError("Invalid argument. Please provide a valid Point instance.")
def compute_distance_in_projection(self, other):
if isinstance(other, Point):
# Oba body jsou ve stejném prostoru, rovině nebo na přímce
distance = (sum( (x-y)**2 for (x,y) in zip(self.x, other.x)))**0.5
return distance
else:
raise ValueError("Invalid argument. Please provide a valid Point instance.")
class PlanePoint(Point):
def __init__(self, x, y):
# self.x = (x, y)
super().__init__(x, y)
class LinePoint(Point):
def __init__(self, x):
self.x = (x,)
# Příklad použití tříd Point, PlanePoint a LinePoint
point1 = Point(1, 2, 3)
point2 = Point(4, 0, 6)
plane_point1 = PlanePoint(1, 2)
plane_point2 = PlanePoint(4, 5)
line_point1 = LinePoint(1)
line_point2 = LinePoint(4)
try:
distance_space = point1.compute_distance(point2)
print(f"Distance in space: {distance_space}")
except ValueError as e:
print(f"Error: {e}")
try:
distance_plane = plane_point1.compute_distance(plane_point2)
print(f"Distance in plane: {distance_plane}")
except ValueError as e:
print(f"Error: {e}")
try:
distance_line = line_point1.compute_distance(line_point2)
print(f"Distance on line: {distance_line}")
except ValueError as e:
print(f"Error: {e}")
# Příklad chyby - vzdálenost mezi body v různých prostorech
try:
invalid_distance = point1.compute_distance(plane_point1)
print(f"Invalid distance: {invalid_distance}")
except ValueError as e:
print(f"Error: {e}")
# Vzdálenost v projekci
distance_projection = point1.compute_distance_in_projection(line_point2)
print(f"Distance in projection: {distance_projection}")
Distance in space: 4.69041575982343 Distance in plane: 4.242640687119285 Distance on line: 3.0 Error: Cannot compute distance between points in different spaces. Distance in projection: 3.0
7) Vlastní datová struktura¶
Zkuste vymyslet nějakou vlastní datovou strukturu a implementovat ji jako třídu s příslušnými atributy a metodami.
8) Vylepšení předchozích příkladů¶
Vylepšete některý z příkladů z minulých hodin tak, že svoje řešení implementujete pomocí OOP paradigma, aby bylo možné jej znovu použít v budoucím kódu.