Cvičení č. 21 rozšířené o poznámky ze cvičení a řešení některých příkladů (ZS 2024/25)¶
V minulém díle jste viděli...¶
Modul pathlib
a třída Path
¶
Pro zjednodušení práce s aresáři a se soubory vznikla knihovna pathlib
. Seznámili jsme se s třídou Path
a jejími metodami a vlastnostmi:
cwd()
- zjištění aktuálního adresářehome()
- zjištění domovského adresářejoinpath()
- spojování cest- aritmetika cest - využití operátoru
/
pro spojování cest
home = Path.home()
download = Path("Download")
file = Path("rickroll.mp4")
full_path = home / download / file
- zjišťování informací o cestě a souborech
.name
: Vrací jméno souboru bez cesty..stem
: Vrací jméno souboru bez přípony..suffix
: Vrací příponu.anchor
: Část cesty před složkami..parent
: Vrací složku, ve které je soubor, nebo nadřazenou složku dané složce..exists()
: Říká, jestli složka/soubor existují.is_dir()
vrátíTrue
, pokud se jedná o složku (adresář)..is_file()
vrátíTrue
, pokud se jedná o soubor..is_symlink()
vrátíTrue
, pokud se jedná o symbolický link.
- zápis a čtení souboru
.read_text()
otevře soubor v textovém režimu a načte obsah do stringu..read_bytes()
otevře soubor v binárním režimu a načte jeho obsah jako byty..write_text()
otevře soubor v textovém režimu a zapíše do něj text..write_bytes()
otevře soubor v binárním režimu a zapíše binární data..touch()
vytvoří prázdný soubor. Pokud existuje, změní čas modifikace na aktuální čas..mkdir()
vytvoří novou složku. Nelze vytvořit více zanořených podsložek najednou.
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:
# typ instance a typ datového typu (třídy)
print(type(78), type(int))
print(type(class_instance), type(MyClass))
<class 'int'> <class 'type'> <class '__main__.MyClass'> <class 'type'>
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()
# instance vytváříme stejně jako u známých datových typů:
s = list()
t = list("abc")
print(s, t)
[] ['a', 'b', 'c']
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):
# ukázka: změna hodnoty atributu
print(class_instance.some_attr)
print(class_instance1.some_attr)
class_instance.some_attr = 1
print(class_instance.some_attr)
print(class_instance1.some_attr)
5 5 1 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
print("A new instance created with the following attributes: ")
print(f"name: {self.name}, year of birth: {self.year_of_birth}")
# z jedné metody mohu volat jinou:
self.compute_age()
print("age:", self.compute_age())
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 age: 88
# ukázka: atributy a metody - výpis a změna
p1.name, p1.year_of_birth, p1.compute_age()
('John', 1936, 88)
p1.name = "Pepa"
p1.name
'Pepa'
print(p1)
<__main__.Person object at 0x7f7ef136a3c0>
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 0x7897a04f3770>
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)
print(p1)
# 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: 1936 name: John, year of birth: 1937 The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
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ázka i v debuggeru)
x = 7
s = [1, 2, 3]
x
7
del x
del s[1]
s
[1, 3]
x
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[30], line 1 ----> 1 x NameError: name 'x' is not defined
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: 34 Age: 34
isinstance(s0, Student), type(s0) == Student
(True, True)
isinstance(s0, Person), type(s0) == Person
(True, False)
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, year_of_birth, grades):
'''Constructor that sets instance attributes.
Expects three arguments: name (str), year_of_birth (int) and grade (float).'''
Person.__init__(self, name, year_of_birth) # 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
A new instance created with the following attributes: name: Karel, year of birth: 1995 [1, 3, 5] Age: 29
# použije se metoda předka:
print(s1)
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, year_of_birth, grades): # overriding parent constructor
'''Constructor that sets instance attributes.
Expects three arguments: name (str), year_of_birth (int) and grade (float).'''
#Person.__init__(self, name, year_of_birth) # we call parent constructor
super().__init__(name, year_of_birth) # 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: 29
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 __len__(self): # ukázka: len
return len(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
# pokud implementujeme magickou metodu __len__(), můžeme i na studenta volat len()
len(s1)
5
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, initial_deposit=0):
self.name = name
self.balance = initial_deposit
def deposit(self, amount):
if 0 > amount:
raise ValueError("Invalid transaction")
self.balance += amount
def withdraw(self, amount):
if 0 > amount:
raise ValueError("Invalid transaction")
if 0 <= amount < self.balance:
self.balance -= amount
else:
print("Invalid transaction.")
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()
Invalid transaction.
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, a, b):
if not isinstance(a, [int, float]) or not isinstance(b, [int, float]):
raise TypeError("Invalid data type")
if 0 > a or 0 > b:
raise ValueError("Invalid shape")
self.a = a
self.b = b
def calculate_area(self):
return self.a * self.b
class Square(Rectangle):
def __init__(self, a):
Rectangle.__init__(self, a, a)
#super().__init__(a, a)
#self.a = a
#self.b = a
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):
return f"{self.title} by {self.author}"
class Library:
def __init__(self):
self.books = []
def add_book(self, book):
if not isinstance(book, Book):
raise TypeError("Invalid book object. Please provide a valid Book instance.")
self.books.append(book)
print(f"Book '{book.title}' added to the library.")
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 not isinstance(book, Book):
raise TypeError("Invalid book object. Please provide a valid Book instance.")
self.books.append(book)
print(f"Book '{book.title}' added to the library.")
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("")
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")
library = Library()
library.list_books()
library.add_book(book1)
library.add_book(book2)
library.add_book(book1)
library.list_books()
library.borrow_book("The Great Gatsby")
library.borrow_book("The Great Gatsby")
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 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?
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.