Próba przekszałcenie kodu proceduralnego w typowo obiektowy

0

Witam ponownie . Zacząłem się bawić w obiektówkę . Przykładowo mam kod proceduralny z liczeniem wieku :

import datetime

def pobierz_imie():
    return input('podaj imie: ')

def pobierz_rok_urodzenia():
    return int(input('podaj rok urodzenia: '))

def policz_ile_lat(rok_urodzenia, aktualny_rok):
    return aktualny_rok - rok_urodzenia

def odpowiedz_uzytkownikowi(imie, wiek):
    print(f'Czesc {imie}! Masz {wiek} lat.')

while True:
    imie = pobierz_imie()
    rok_urodzenia = pobierz_rok_urodzenia()
    aktualny_rok = datetime.datetime.now().year
    wiek = policz_ile_lat(rok_urodzenia, aktualny_rok)
    odpowiedz_uzytkownikowi(imie, wiek)

Kod w obiektówce:

import datetime

class Dane:
    def __init__(self):
        self.imie = self.Pobierz_imie   #konstruktor/  - powinno być self.imie = imie /jak takzrobię mam bład
        self.rok_urodzenia = self.pobierz_rok_urodzenia()
        self.aktualny_rok = datetime.datetime.now().year
        self.wiek = self.policz_ile_lat(self.rok_urodzenia, self.aktualny_rok)
        self.odpowiedz_uzytkownikowi(self.imie, self.wiek)

    def Pobierz_imie(self):
        return input('Podaj imie: ')

    def pobierz_rok_urodzenia(self):
        return int(input('podaj rok urodzenia: '))
        

    def policz_ile_lat(self,rok_urodzenia, aktualny_rok):
        return self.aktualny_rok - self.rok_urodzenia

    def odpowiedz_uzytkownikowi(self,imie,wiek):
        print(f'Czesc {self.imie}! Masz {self.wiek} lat.')

    def wyswietl(self):
      #print(self.Pobierz_imie,self.pobierz_rok_urodzenia,self.odpowiedz_uzytkownikowi,self.policz_ile_lat)
      print(self.imie,self.aktualny_rok,self.rok_urodzenia,self.wiek)
      

dane1 = Dane()
dane1.wyswietl()

I ten program próbuję przekształcić w typową obiektówkę... Niby wynik obliczenia wieku mi wychodzi (z błedami) , ale nie mam pytania o imie, żebym podał.... Tylko od razu pytanie o rok urodzenia... Czy w konstruktorze klasy dobrze dodałem te wszystkie "dane"? Powinno być self.imie = imie, self.rok_urodzenia = rok_urodzenia itp..., a zrobiłem trochę inaczej ! Czy jest możliwość całkowitego przerobienia tego programu w typową "obiektówkę" Ogólnie czy dobrze kombinuję, jeśli chodzi o przerobienie tego kodu na obiektówkę? Chętnie bym zobaczył prawidlowy kod obiektowy według was, zebym sobie porównał lub wskazówki co robię zle. Pozdrawiam

2

W konstruktorze powinno być self.imie = self.Pobierz_imie(), dlatego od razu pyta o rok urodzenia.
Generalnie to rozdziela się pobieranie danych od obiektów na których pracujesz, a dane umieszczasz w obiekcie przekazując je przez konstruktor np.

import datetime


class Dane:
    def __init__(self, imie, rok_urodzenia):
        self.imie = imie
        self.rok_urodzenia = rok_urodzenia
        self.aktualny_rok = datetime.datetime.now().year
        self.wiek = self.policz_ile_lat()
        self.odpowiedz_uzytkownikowi(self.imie, self.wiek)

    def policz_ile_lat(self):
        return self.aktualny_rok - self.rok_urodzenia

    def odpowiedz_uzytkownikowi(self):
        print(f'Czesc {self.imie}! Masz {self.wiek} lat.')

    def wyswietl(self):
        print(self.imie, self.aktualny_rok, self.rok_urodzenia, self.wiek)


def Pobierz_imie():
    return input('Podaj imie: ')


def pobierz_rok_urodzenia():
    return int(input('podaj rok urodzenia: '))


pobrane_imie = Pobierz_imie()
pobrany_rok = pobierz_rok_urodzenia()

dane1 = Dane(pobrane_imie, pobrany_rok) # można też dać Dane(rok_urodzenia=pobrany_rok, imie=pobrane_imie) wtedy kolejność parametrów nie ma znaczenia
dane1.wyswietl()

Nie musisz też przekazywać zmiennych to funkcji wewnątrz klasy jak np. w twoim policz_ile_lat. Ustawiasz wartości dla rok_urodzenia i aktualny_rok w konstruktorze, a potem możesz się odwołać do nich przez self.rok_urodzenia i będzie tam dokładnie to co ustawiłeś.

1

Ja bym te inputy wyrzucil poza klase i przekazywal jako atrybut do obiektu, cos jak kolega up ;)

3

Ja bym nie dodawał pola aktualny_rok - to powinno sie liczyc na bieżąco.

0

Niespójność: napisałeś metodę Pobierz_imie(self) nie wiadomo dlaczego z dużej litery (później autocomplete rozumiem, że umocniło ten błąd. Czy raczej nie tyle błąd, bo to jest technicznie poprawne, tylko niespójność stylu).

1

Ja bym sobie kod podzielił na klasę "ludka" i klasę odpowiadającą za komunikację z użytkownikiem. Klasy w osobnych plikach. Aktualny rok dałbym jako metodę prywatną, albo property nie widoczne za weznątrz. Wiek wystawione jako property readonly. Wyświetlanie obiektu poprzez __str__. Do obiektu osoby dopisałbym testy liczenia wieku. Komunikację rozszerzył o logi. Brakuje też zabezpieczeń przed niewłaściwymi danymi.

osoby.py:

from datetime import datetime

class Osoba:
    def __init__(self, imie=None, rok_urodzenia=None):
        self.imie = imie
        self.rok_urodzenia = rok_urodzenia

    @property
    def __aktualny_rok(self):
        return datetime.now().year

    @property
    def wiek(self):
        if not self.rok_urodzenia: return None
        return self.__aktualny_rok - self.rok_urodzenia

    def __str__(self):
        return f'{self.imie} {self.__aktualny_rok} {self.rok_urodzenia} {self.wiek}'

komunikacja.py:

class Komunikacja:
    def pobierz_imie(self):
        return input('Podaj imie: ')

    def pobierz_rok_urodzenia(self):
        return int(input('podaj rok urodzenia: '))

    def odpowiedz_uzytkownikowi(self, osoba):
        print(f'Cześć {osoba.imie}! Masz {osoba.wiek} lat.')

main.py:

from osoba import Osoba
from komunikacja import Komunikacja

def main():
    komunikacja = Komunikacja()
    osoba = Osoba()
    osoba.imie = komunikacja.pobierz_imie()
    osoba.rok_urodzenia = komunikacja.pobierz_rok_urodzenia()
    print(osoba)
    komunikacja.odpowiedz_uzytkownikowi(osoba)
    
if __name__ == '__main__':
    main()
0

Dzięki Wam wszystkim za pomocne informacje....

@Arthan Jeśi chodzi o zabezpiecznie danych przed nieprawidłową odpowiedzią , czy mogę tu w klasach zastosować tzw. wyjątki tzn:

def pobierz_rok_urodzenia(self):
        while True: 
            try:
                return int(input('podaj rok urodzenia: '))
            except ValueError:
                print('Wpisałeś złą wartosć, wpisz liczbę jeszcze raz')

Z wyjatkiem kod działa, tylko czy mogę to w klasach stosować?. Chodzi mi o poprawność kodu w obiektówce. Tzw while , for to tu się w obiektówce stosuje podobnie jak w proceduralnym kodzie? , co do testów, logów , może pokombinuję (choć nie wiem do konca o co chodzi tu ), co do property to wiedzę sobie uzupełnię na ten temat

2

Generalnie założyłbym, że możesz robić w klasach wszystko, co do tej pory robiłeś. Obiekty służą tylko po to, żeby opakować dane w wygodne pudełka i zdefiniować operacje, które można na nich wykonywać.

2

Tak jak napisał @ObywatelRP. Kiedy pudełka są wygodne? Wtedy kiedy poszczególne pudełko odpowiada za pojedynczą funkcjonalność, a nie że obiekt Pralka zmywa naczynia (a niekiedy nawet służy do gotowania). Pudełko takie powinno też udostępniać w miarę wygodny interfejs i bronić dostępu do niektórych części wewnętrznych, do których nie powinno być takiego dostępu z zewnątrz (hermetyzacja).

Wracając jeszcze do pobierania roku, zamiast przechwytywać wyjątek możesz tak napisać program by do niego tu nie dochodziło. Czy użytkownik podał poprawną liczbę możesz sprawdzić używając .isdigit(), do tego przydałoby się sprawdzanie czy liczba jest w jakimś sensownym zakresie, np. większa od 1900 i mniejsza niż obecny rok. Ja tam nie lubię używać pętli gdy nie muszę, dlatego tu zamiast while dałbym rekurencję przy niepowodzeniu.

Tu masz przykład prostych testów:

import unittest
from osoba import Osoba

class TestOsoba(unittest.TestCase):
    def setUp(self):
        self.osoba = Osoba()

    def test_rok_urodzenia_niepodany(self):
        self.assertIsNone(self.osoba.wiek)
        
    def test_rok_urodzenia_2000(self):
        self.osoba.rok_urodzenia = 2000
        self.assertEqual(self.osoba.wiek, 21)

if __name__ == '__main__':
    unittest.main()

Odpalasz ten plik jako osobny program, te testy są trochę słabe bo za miesiąc przestaną działać, ale masz jakiś przykład ;)

0

Dzięki za info, wskazówki.... W wolnej chwili posprawdzam co i jak.... Tak się ostatnio zastanawiałem nad bootcampem python ( dostaję od nich zaproszenie na szkolenia online/stacjonarne) , trochę to kosztuje.... Ale jednak wolę iść samemu w naukę, online, ksiązki , fora... Po pierwsze szkoda mi kasy na to ( jak sam mogę wiedzę zdobyć) ale za to więcej wysiłku i czasu mi to zajmnie, bo nie mam nauczyciela który by poprawiał moje błedy w programowaniu. Czasowo dłużej nauka mi zajmnie jak się domyślam. Druga sprawa to czas, którego za bardzo nie mam i bym się musiał dostosować do rytmu szkolenia takiego. A u mnie z tym byłoby krucho. Na razie kupiłem cienką książkę z przykładami ćwiczeń , zadaniami, i wytłumaczeniami jak co działa. Po trochu coś tam zdziałam dla siebie. A po trzecie wolę wydać kasę na jakieś ebooki, filmiki instruktażowe itp...., niż ponad 10 K za szkolenia z którymi jestem uwiązany czasowo. Opinie na temat bootcampów są zróżnicowane w sieci. . Pozdrawiam

0

@ObywatelRP: Własnie analizuję kod, uruchamia się z błedem.
W klasie Dane na końcu powinno być bez argumentów: self.imie , self wiek tzn:

 self.odpowiedz_uzytkownikowi()

choć jak tej funkcji w ogóle nie podam , to kod też się wywołuje poprawnie.
Grunt , ze kod się uruchamia :) A tak z ciekawośći, dlaczego dwie ostatnie funkcje w kodzie są akapitami różne? Tak sobie porównuję do innych kodów.

  
      def wyswietl(self):
        print(self.imie, self.aktualny_rok, self.rok_urodzenia, self.wiek)

def Pobierz_imie():
        return input('Podaj imie: ')

def pobierz_rok_urodzenia():
        return int(input('podaj rok urodzenia: '))
1

Generalnie, to może zacznijmy od tego, że python to taki śmieszny język, gdzie wcięcia służą do definiowania bloków kodu

def test_indent():
    if False:
        print("To się nie wyświetli")
        print("To też się nie wyświetli")
    print("A to już, tak bo jest inne wcięcie")

test_indent()

Tak samo jest z klasami

class Clazz:
    def method_in_class(self):
        print("method")

    def method2_in_class(self):
        print("method 2")

def method3_not_in_class(self):
    print("method 3")


clazz = Clazz()
clazz.method_in_class()
clazz.method2_in_class()
clazz.method3_not_in_class() # error

Nie chciałem, żeby metody pobierające dane były w klasie, która na nich operuje, więc nie są na tym samym poziomie wcięcia . Popatrz na kod, który dał ci @Arthan na poprzedniej stronie, Klasa osoba ma tylko metody służące do operowania na danych, które dostaje, a cała reszta, czyli pobieranie danych jest w klasie Komunikacja. U mnie też można by dać je w osobnej klasie, ale jestem leniwy i nie chciało mi się tego pisać, więc dałem tylko inne wcięcie żeby zaznaczyć, że te metody nie powinny tam być.

0

@ObywatelRP: Dzięki za info. Dla testów dałem to w osobnej klasie (inny plik) , działa bez problemu :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1