Wyzwanie Python #6: Rozwiązanie

Maciej Bartoszuk 6 stycznia 2022
 

Wyjątki w poprzednich zadaniach

Kalkulator

Aby pobrać liczbę i powtarzać pytanie o liczbę aż do otrzymania poprawnej wartości, stworzyliśmy funkcję pomocniczą pobierz_liczbe()

Aby zabezpieczyć się przed dzieleniem przez zero, które może wystąpić zarówno w przypadku dzielenia, jak i obliczania reszty z dzielenia, wzięliśmy w blok try-except cały kod obliczający wynik wraz z wypisaniem wyniku, tak aby następny kod, po bloku try-except, był już zapytaniem o kolejne działanie.

def pobierz_liczbe():
  while True:
    try:
      liczba = float(input())
      break
    except ValueError:
      print("Podany tekst nie jest liczbą")
  return liczba

def main():
  koniec = False
  while not koniec:
    print("Podaj w oddzielnych wierszach liczbę, operację matematyczną: +,-,*,/,%, a następnie kolejną liczbę:")
    liczba1 = pobierz_liczbe()
    operacja = input()
    liczba2 = pobierz_liczbe()

    try:
      if operacja == "+":
        wynik = liczba1 + liczba2
      elif operacja == "-":
        wynik = liczba1 - liczba2
      elif operacja == "*":
        wynik = liczba1 * liczba2
      elif operacja == "/":
          wynik = liczba1 / liczba2
      elif operacja == "%":
        wynik = liczba1 % liczba2
      else:
        print("Niepoprawna operacja")
        break
      print("Twój wynik to: " + str(wynik))
    except ZeroDivisionError:
      print("Nastąpiło dzielenie przez zero")

    print("Chcesz wykonać kolejne działanie? Wpisz literę t lub n")

    kolejne = input()
    if kolejne == "n":
      koniec = True
    elif kolejne != "t":
      print("Niepoprawny wybór")
      break

if __name__ == "__main__":
  main()

Ułamek

W tym wypadku nie możemy w implementacji naszej klasy złapać wyjątku. Nie wiemy, czy moment, gdy dzielimy ułamek przez zero jest absolutnym błędem krytycznym, który powinien awaryjnie zakończyć działanie programu, czy też można tę sytuację odratować. Dlatego tylko rzucamy wyjątek, a to na użytkowniku naszej klasy (w tym wypadku funkcja main()) spoczywa odpowiedzialność, co z tym fantem zrobić.

Dodajmy, że zamiast rzucać wyjątek w funkcji dziel(), rzucamy go w konstruktorze. Dzięki temu jednym kodem zabezpieczamy się także przed ręcznym stworzeniem ułamka o mianowniku zero.

import math

class Ulamek:
  def __init__(self, licznik, mianownik):
    if mianownik == 0:
      raise ZeroDivisionError("Mianownik równy zero.")
    self.licznik = licznik
    self.mianownik = mianownik

  def wypisz(self):
    print(f"({self.licznik})/({self.mianownik})")

  def skroc(self):
    nwd = math.gcd(self.licznik, self.mianownik)
    self.licznik //= nwd
    self.mianownik //= nwd

  @staticmethod
  def dodaj(u1, u2):
    lewy_licznik = u1.licznik * u2.mianownik
    prawy_licznik = u2.licznik * u1.mianownik
    wynik = Ulamek(lewy_licznik + prawy_licznik, u1.mianownik * u2.mianownik)
    wynik.skroc()
    return wynik

  @staticmethod
  def odejmij(u1, u2):
    lewy_licznik = u1.licznik * u2.mianownik
    prawy_licznik = u2.licznik * u1.mianownik
    wynik = Ulamek(lewy_licznik - prawy_licznik, u1.mianownik * u2.mianownik)
    wynik.skroc()
    return wynik

  @staticmethod
  def mnoz(u1, u2):
    wynik = Ulamek(u1.licznik * u2.licznik, u1.mianownik * u2.mianownik)
    wynik.skroc()
    return wynik

  @staticmethod
  def dziel(u1, u2):
    wynik = Ulamek(u1.licznik * u2.mianownik, u1.mianownik * u2.licznik)
    wynik.skroc()
    return wynik


def main():
  u1 = Ulamek(0, 4)
  u2 = Ulamek(2, 6)  # nieskrocony

  try:
    u3 = Ulamek.dziel(u2, u1)
  except ZeroDivisionError as e:
    print(e)

if __name__ == "__main__":
  main()

Operacje arytmetyczne

Tutaj postępujemy bardzo podobnie do klasy Ulamek: znów rzucamy wyjątek w konstruktorze.

from abc import ABC, abstractmethod
import math


class Wezel(ABC):
    @abstractmethod
    def nazwa(self):
        pass

    def wypisz(self):
        print(f"Wykonuję {self.nazwa()}.", end=' ')

    @abstractmethod
    def wartosc(self):
        pass

class Liczba(Wezel):
    def __init__(self, liczba):
        self.liczba = liczba

    def nazwa(self):
        return "liczba"

    def wypisz(self):
        print(f"Jestem liczbą {self.liczba}")

    def wartosc(self):
        return self.liczba


class Suma(Wezel):
    def __init__(self, skladnik1, skladnik2):
        self.skladnik1 = skladnik1
        self.skladnik2 = skladnik2

    def nazwa(self):
        return "dodawanie"

    def wypisz(self):
        self.skladnik1.wypisz()
        self.skladnik2.wypisz()
        super().wypisz()
        print(f"{self.skladnik1.wartosc()}+{self.skladnik2.wartosc()}={self.wartosc()}")

    def wartosc(self):
        return self.skladnik1.wartosc() - self.skladnik2.wartosc()


class Roznica(Wezel):
    def __init__(self, odjemna, odjemnik):
        self.odjemna = odjemna
        self.odjemnik = odjemnik

    def nazwa(self):
        return "odejmowanie"

    def wypisz(self):
        self.odjemna.wypisz()
        self.odjemnik.wypisz()
        super().wypisz()
        print(f"{self.odjemna.wartosc()}-{self.odjemnik.wartosc()}={self.wartosc()}")

    def wartosc(self):
        return self.odjemna.wartosc() - self.odjemnik.wartosc()


class Iloczyn(Wezel):
    def __init__(self, czynnik1, czynnik2):
        self.czynnik1 = czynnik1
        self.czynnik2 = czynnik2

    def nazwa(self):
        return "mnożenie"

    def wypisz(self):
        self.czynnik1.wypisz()
        self.czynnik2.wypisz()
        super().wypisz()
        print(f"{self.czynnik1.wartosc()}*{self.czynnik2.wartosc()}={self.wartosc()}")

    def wartosc(self):
        return self.czynnik1.wartosc() * self.czynnik2.wartosc()


class Iloraz(Wezel):
    def __init__(self, dzielna, dzielnik):
      if dzielnik.wartosc() == 0:
        raise ZeroDivisionError("Wartość dzielnika równa zero.")
      self.dzielna = dzielna
      self.dzielnik = dzielnik

    def nazwa(self):
        return "dzielenie"

    def wypisz(self):
        self.dzielna.wypisz()
        self.dzielnik.wypisz()
        super().wypisz()
        print(f"{self.dzielna.wartosc()}/{self.dzielnik.wartosc()}={self.wartosc()}")

    def wartosc(self):
        return self.dzielna.wartosc() / self.dzielnik.wartosc()


class Silnia(Wezel):
    def __init__(self, liczba):
        self.liczba = liczba

    def nazwa(self):
        return "silnia"

    def wypisz(self):
        self.liczba.wypisz()
        super().wypisz()
        print(f"{self.liczba.wartosc()}!={self.wartosc()}")

    def wartosc(self):
        return math.factorial(self.liczba.wartosc())


def main():
    minus_jeden = Liczba(-1)
    cztery = Liczba(4)
    piec = Liczba(5)
    siedem = Liczba(7)
    osiem = Liczba(8)
    zero = Liczba(0)

    try:
      dodawanie = Suma(piec, siedem)
      odejmowanie = Roznica(osiem, cztery)
      mnozenie = Iloczyn(dodawanie, odejmowanie)
      dzielenie = Iloraz(mnozenie, zero)
      silnia = Silnia(dzielenie)
      silnia.wypisz()
    except ZeroDivisionError as e:
      print(e)


if __name__ == "__main__":
  main()

Liczenie linii

To zadanie jest raczej samowyjaśniające się. Zwróćmy uwagę, że obsługujemy przypadek, gdy nie ma pliku na dysku.

def main():
    plik_we = "/sciezka/do/pliku.txt"
    plik_wy = "/sciezka/do/pliku2.txt"
    try:
      with open(plik_we) as we:
        wiersze = we.readlines()
        liczba_wierszy = len(wiersze)
        print(f"Liczba linii w pliku to {liczba_wierszy}")
        with open(plik_wy, "w") as wy:
          wy.writelines([plik_we, '\n', str(liczba_wierszy)])
    except FileNotFoundError:
      print("Nie znaleziono pliku")


if __name__ == "__main__":
  main()

Sprzątanie

To zadanie jest dość proste, jednak podejdźmy do niego ze szczególną ostrożnością, gdyż jest w nim kod kasujący pliki. Dlatego ze szczególną uwagą wybierzmy folder, w którym będziemy działać. Zmodyfikowaliśmy trochę kod względem oryginalnej treści: kasujemy pliki zmodyfikowane w ciągu ostatnich dwóch minut, a także o rozmiarze mniejszym niż 1 MB. Dlatego wystarczy w celach testowych utworzyć pusty plik tekstowy w folderze (najlepiej stworzonym specjalnie na potrzeby tego ćwiczenia) i w ciągu dwóch minut uruchomić kod.

Przejdźmy do samego kodu. Pozyskujemy listę plików w ustalonym folderze, a następnie dla każdego z nich wykonujemy następujące działania: tworzymy ściężkę bezwzględną, której będziemy używać, aby skasować plik. Pobieramy informacje o pliku, w tym rozmiar i datę modyfikacji. Następnie je przekształcamy: rozmiar w bajtach zamieniamy na rozmiar w MB (dzielimy przez $1024^2$, bo 1024 bajtów to jeden kilobajt, a 1024 kilobajty to jeden megabajt), a datę zamieniamy na łatwiejszy w użyciu format. Następnie robimy nieomawiany wcześniej ruch: od aktualnej daty odejmujemy datę modyfikacji. Dzięki temu dostajemy typ różnicy czasowej (timedelta). Obiekt tej klasy przechowuje mikrosekundy, sekundy oraz dni. Dlatego, aby obliczyć lata czy minuty, należy samemu dokonać mnożenia. Zwróćmy uwagę, że ma znaczenie, co odejmiemy od czego, bo możemy dostać ujemną różnicę. Wypisujemy wiele informacji na temat pliku na ekran, a następnie kasujemy pliki spełniające warunki.

import os
import datetime

def main():
  katalog = "/sciezka/do/folderu"
  pliki = os.listdir(katalog)
  print(pliki)
  for plik in pliki:
    sciezka_bezwzgledna = os.path.join(katalog, plik)
    (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(sciezka_bezwzgledna)
    rozmiar_MB = size/(1024**2)
    data_modyfikacji = datetime.datetime.fromtimestamp(mtime)
    roznica = datetime.datetime.now() - data_modyfikacji

    print(f"{sciezka_bezwzgledna}, size = {size}, rozmiar MB: {rozmiar_MB}, mtime = {data_modyfikacji}, rok={data_modyfikacji.year}, roznica={roznica}, wiecej niz rok = {roznica.days >= 365}")

    if roznica.seconds < 2*60 and rozmiar_MB < 1:
        os.remove(sciezka_bezwzgledna)

if __name__ == "__main__":
  main()

Maciej Bartoszuk

Ukończył z wyróżnieniem informatykę na wydziale Matematyki i Nauk Informacyjnych Politechniki Warszawskiej, gdzie aktualnie pracuje w zakładzie Sztucznej Inteligencji i Metod Obliczeniowych. Tam też od 2013 roku prowadzi zajęcia dydaktyczne z programowania w R, Pythonie, C/C++, C#. Uczestnik studiów doktoranckich w Instytucie Podstaw Informatyki Polskiej Akademii Nauk w latach 2013-2015. W 2018 roku obronił doktorat z wyróżnieniem na swoim rodzimym wydziale: System do oceny podobieństwa kodów źródłowych w językach funkcyjnych oparty na metodach uczenia maszynowego i agregacji danych, który obejmuje zarówno algorytmy przetwarzania kodów źródłowych programów, jak i data science. Współautor książki Przetwarzanie i analiza danych w języku Python wydanej przez PWN. Ponadto trener na bootcampach Data Science, gdzie uczy programować w języku Python pod kątem analizy danych.
Komentarze
Ostatnie posty
Data Science News #204
Data Science News #203
Data Science News #202
Data Science News #201