Początkujący, problem z klasą

0

Cześć, zaczynam dopiero uczyć się pythona, aktualnie jestem na klasach i w celu ćwiczenia chciałem zrobić prostą klasę o "użytkowniku forum", ale mam problem który mnie zatrzymał, a nie bardzo wiem jak ująć ten problem w słowa, aby znaleźć rozwiązanie na necie, dlatego piszę o poradę i wytłumaczenie, dlaczego tak się dzieje
Problem z tym, że w klasie używam zmiennej reputation, którą planowałem, że ma być dynamiczna, zmieniana funkcjami, a kolejna zmienna rank, ma zależeć od wartości reputation. Jednak mimo zmieniania funkcjami wartości reputation, rank zostaje bez zmian, nie rozumiem dlaczego tak się dzieje, mimo że wartość reputation jest aktualizowana i przechowywana, rank sie nie zmienia, tutaj kod (dla samych ifow i ifa z elifami wychodzi tak samo):

class User():
    def __init__(self, first, last, reputation=0):
        self.first = first
        self.last = last
        self.reputation = reputation
        self.rank = ""
        if self.reputation < 0:
            self.rank = "Troll"
        if self.reputation >= 0 and self.reputation < 10:
            self.rank = "Poczatkujacy"
        if self.reputation >= 10 and self.reputation < 20:
            self.rank = "Bywalec"
        if self.reputation >= 20 and self.reputation < 30:
            self.rank = "Doświadczony"
        if self.reputation >= 30:
            self.rank = "Profesjonalista"

    def increase_rep(self, x=5):
        self.reputation += x

    def decrease_rep(self, x=5):
        self.reputation -= x

    def describe_user(self):
        print(f"User {self.first.title()} {self.last.title()} is {self.rank} with {self.reputation} reputation.")

    def greet_user(self):
        print(f"Welcome {self.first.title()} {self.last.title()}!")

uzytkownik = User("jan", "kowalski")

uzytkownik.describe_user()
uzytkownik.increase_rep(100)
uzytkownik.describe_user()
uzytkownik.reputation = -100
uzytkownik.describe_user()

w konsoli wychodzi:
User Jan Kowalski is Poczatkujacy with 0 reputation.
User Jan Kowalski is Poczatkujacy with 100 reputation.
User Jan Kowalski is Poczatkujacy with -100 reputation.

Rozumiem, że rank jest definiowana na samym początku wywołania klasy, dla domyślnej wartości, ale nie rozumiem dlaczego nie zmienia się,
kiedy zmienimy tę domyślną wartośc. Powinienem może zrobić osobną funckję, która zwraca wartość rank w zależności od rep? Ale dalej nie rozumiem czemu taki sposób nie działa :/
Będę wdzięczny za podpowiedzi co trzeba zmienić i wyjaśnienie dlaczego tak się dzieje!

5

ale nie rozumiem dlaczego nie zmienia się, kiedy zmienimy tę domyślną wartośc

Nie zmienia się właśnie dlatego, że przypisujesz tej zmiennej wartość dokładnie raz, w konstruktorze, i potem jej nie odświeżasz; Python automatycznie nie śledzi wszystkich możliwych ifów i nie odświeża takich zmiennych automatycznie (mało który język coś takiego obsługuje, tak na marginesie).

Możliwe wyjścia z tej sytuacji są przynajmniej dwa:

  1. Możesz zrobić funkcję w stylu def refresh_rank(), do której przerzucisz tę drabinkę ifów, i którą będziesz ręcznie uruchamiał przy każdej modyfikacji reputacji,
  2. (imo lepsze, ponieważ nie wymaga pamiętania "aaa bo jest taka tajna funkcja do odświeżania którą musisz zawołać gdy robisz voodoo magic magic") Możesz pozbyć się reputation jako pola i zamienić je na zwyczajną funkcję:
class User():
  def __init__(self, first, last, reputation=0):
      self.first = first
      self.last = last
      self.reputation = reputation
      
  # ...

  def describe_rank(self):
      if self.reputation < 0:
          return "Troll"
      if self.reputation >= 0 and self.reputation < 10:
          return "Poczatkujacy"
      if self.reputation >= 10 and self.reputation < 20:
          return "Bywalec"
      if self.reputation >= 20 and self.reputation < 30:
          return "Doświadczony"
      if self.reputation >= 30:
          return "Profesjonalista"

  def describe_user(self):
      print(f"User {self.first.title()} {self.last.title()} is {self.describe_rank()} with {self.reputation} reputation.")

0
Patryk27 napisał(a):

ale nie rozumiem dlaczego nie zmienia się, kiedy zmienimy tę domyślną wartośc

Nie zmienia się właśnie dlatego, że przypisujesz tej zmiennej wartość dokładnie raz, w konstruktorze, i potem jej nie odświeżasz; Python automatycznie nie śledzi wszystkich możliwych ifów i nie odświeża takich zmiennych automatycznie (mało który język coś takiego obsługuje, tak na marginesie).

Możliwe wyjścia z tej sytuacji są przynajmniej dwa:

  1. Możesz zrobić funkcję w stylu def refresh_rank(), do której przerzucisz tę drabinkę ifów, i którą będziesz ręcznie uruchamiał przy każdej modyfikacji reputacji,
  2. (imo lepsze, ponieważ nie wymaga pamiętania "aaa bo jest taka tajna funkcja do odświeżania którą musisz zawołać gdy robisz voodoo magic magic") Możesz pozbyć się reputation jako pola i zamienić je na zwyczajną funkcję:
class User():
  def __init__(self, first, last, reputation=0):
      self.first = first
      self.last = last
      self.reputation = reputation
      
  # ...

  def describe_rank(self):
      if self.reputation < 0:
          return "Troll"
      if self.reputation >= 0 and self.reputation < 10:
          return "Poczatkujacy"
      if self.reputation >= 10 and self.reputation < 20:
          return "Bywalec"
      if self.reputation >= 20 and self.reputation < 30:
          return "Doświadczony"
      if self.reputation >= 30:
          return "Profesjonalista"

  def describe_user(self):
      print(f"User {self.first.title()} {self.last.title()} is {self.describe_rank()} with {self.reputation} reputation.")

Dziękuję za przykład i szczególnie dziękuję za wyjaśnienie! Dzięki za pomoc!

2

Ja bym zrobił z propertisem by można było używać User.rank, do tego wyciągnąłbym dane o rangach do jakiejś tablicy, a dane o obiekcie zwracał przez str lub repr.

# tu tablica z nazwami rang i max pkt rep dla danej rangi
USER_RANKS = [
    [-1, "Troll"],
    [9, "Poczatkujacy"],
    [19, "Bywalec"],
    [29, "Doświadczony"],
    [None, "Profesjonalista"],  
]

class User():    
    def __init__(self, first, last, reputation=0):
        self.first = first
        self.last = last
        self.reputation = reputation

    @property    
    def rank(self):
        for user_rank in USER_RANKS:
            if (user_rank[0] is None) or (self.reputation <= user_rank[0]):
                return user_rank[1]
    
    def increase_rep(self, x=5):
        self.reputation += x

    def decrease_rep(self, x=5):
        self.reputation -= x

    def __str__(self): # lub __repr__
        return f"User {self.first.title()} {self.last.title()} is {self.rank} with {self.reputation} reputation."

    def greet_user(self):
        print(f"Welcome {self.first.title()} {self.last.title()}!")

uzytkownik = User("jan", "kowalski")

print(uzytkownik)
uzytkownik.increase_rep(100)
print(uzytkownik)
uzytkownik.reputation = -100
print(uzytkownik)
uzytkownik.reputation = 20
print(uzytkownik)
5

Przerobiona odpowiedź z wyżej, tylko bez fora po nic, i z wyjątkami na ujemną reputację.

class User:    
    def __init__(self, first, last, reputation=0):
        self.first = first
        self.last = last
        if reputation < 0:
          raise ValueError("Negative reputation")
        self._reputation = reputation

    @property    
    def rank(self):
        if self._reputation >= 30: return 'Profesjonalista'
        if self._reputation >= 20: return 'Doświadczony':
        if self._reputation >= 10: return 'Bywalec'
        return 'Początkujący'

    @property
    def reputation(self):
        return self._reputation

     @reputation.setter
     def reputation(self, reputation):
         if reputation < 0:
            raise ValueError("Negative reputation")
         self._reputation = reputation

    def __str__(self):
        return f"User {self.first.title()} {self.last.title()} is {self.rank} with {self._reputation} reputation."

    def greet_user(self):
        print(f"Welcome {self.first.title()} {self.last.title()}!")
4

Jeszcze można użyć czystego deskryptora i odseparować całą logikę reputacji :P

class Reputation:
    def __set_name__(self, owner, name):
        self.rep = name

    def __set__(self, instance, value):
        if value >= 30: instance.__dict__[self.rep] = "profesjonalista"
        elif value >= 20: instance.__dict__[self.rep] = "doswiadczony"
        elif value >= 10: instance.__dict__[self.rep] = "bywalec"
        else: instance.__dict__[self.rep] = "poczatkujacy"

    def __get__(self, instance, owner):
        return instance.__dict__[self.rep]

class User:
    reputation = Reputation()
    def __init__(self, reputation):
        self.reputation = reputation

user = User(reputation=21)
print(user.reputation)
doswiadczony
2
ledi12 napisał(a):

Jeszcze można użyć czystego deskryptora i odseparować całą logikę reputacji :P

Tylko jeśli już chcesz odseparować reputację, to czemu nie zrobić normalnie OO?

class Reputation:    
    def __init__(self, reputation=0):
        if reputation < 0:
          raise ValueError("Negative reputation")
        self._reputation = reputation

    @property    
    def rank(self):
        if self._reputation >= 30: return 'Profesjonalista'
        if self._reputation >= 20: return 'Doświadczony':
        if self._reputation >= 10: return 'Bywalec'
        return 'Początkujący'

    def set(self, reputation):
         if reputation < 0:
            raise ValueError("Negative reputation")
         self._reputation = reputation

    def get(self):
        return self._reputation

class User:    
    def __init__(self, first, last, reputation=0):
        self.first = first
        self.last = last
        self._reputation = Reputation(reputation)

    @property    
    def rank(self):
        return self._reputation.rank

    @property
    def reputation(self):
        return self._reputation.get()

     @reputation.setter
     def reputation(self, reputation):
        self._reputation.set(reputation)

    def __str__(self):
        return f"User {self.first.title()} {self.last.title()} is {self.rank} with {self._reputation} reputation."

    def greet_user(self):
        print(f"Welcome {self.first.title()} {self.last.title()}!")

I moje pytanie po co tu deskryptor? Tylko zaciemni cały design. W żaden sposób nie pomoże. Oszczędzisz dwie deklaracje metody ale kod jest mniej czytelny i mnie odporny na zmiany. Deskryptor i inne takie meta programmingi to krok wstecz moim zdaniem.

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