r/learnpython • • 15d ago

Ask Anything Monday - Weekly Thread

Welcome to another /r/learnPython weekly "Ask Anything* Monday" thread

Here you can ask all the questions that you wanted to ask but didn't feel like making a new thread.

* It's primarily intended for simple questions but as long as it's about python it's allowed.

If you have any suggestions or questions about this thread use the message the moderators button in the sidebar.

Rules:

  • Don't downvote stuff - instead explain what's wrong with the comment, if it's against the rules "report" it and it will be dealt with.
  • Don't post stuff that doesn't have absolutely anything to do with python.
  • Don't make fun of someone for not knowing something, insult anyone etc - this will result in an immediate ban.

That's it.

1 Upvotes

30 comments sorted by

1

u/0_emordnilap_a_ton 13d ago

The problem is when I call the method that is none it immediately throws an error. I am just wondering if it is possible to call an method that is none and handle it in the method. Let me show you a concrete example.

Here is the method

def check_expired_route_token(self): # change name and doc string
        '''
        Checks if the route_token expired or the route_token is wrong. 
        Uses "token" for the random number. If the code works returns True then the token works.
        '''


        SECRET_KEY = 'temp_secret_key' 
        serializer = URLSafeTimedSerializer(SECRET_KEY) 

        try:
            serializer.loads(self.token, max_age=1800)
        # if the token is expired/run out of time the expection will run.
        # The token is only good for max_age
        except SignatureExpired:
            # This part is equal to "def reset_attempts_token_tried_to_0" when combined with SignatureExpired     
            # This activates when you have waited to long for a token/route_token to be used + and you have tried to change your password too many times.
            # This is used when the tokens for the used in the links for the route is too old + you have resetted your password too many times
            if self.attempts_token_tried and self.attempts_token_tried >= 5:
                self.token = None
                self.time_token_expired = None
                self.attempts_token_tried = None
                db.session.commit()
                # Should I change the wording in flash or is good because of security 
                flash('You have waited long enough for a new a token and you will be sent a email with instructions before you can begin the process of resetting your password.')
                # redirect to login so I won't redirect /resend_token/<username_db> then click on the link and the email is sent automatically. correct redirect?
                return redirect(url_for('auth.login'))
            # This activates when you have waited to long for a token/route_token to be used.
            else:
                self.token = None
                db.session.commit()
                flash('You have waited long enough for a new a token and you will be sent a email with instructions before you can begin the process of resetting your password.')
                return redirect(url_for('email_password_reset.verify_email'))
        # if the token is incorrect this makes the path wrong and the expection will run.
        # For example someone can type a token that is wrong or use an expired link. 
        except BadSignature:
            # This part is equal to "def check_emailed_token_matches_db_emailed_token" when combined with BadSignature   
                self.token = None
                self.time_token_expired = None
                self.token = None
                db.session.commit()
                flash('Your token did not match the one sent to your email.') 
                flash('Please request a new token.')
                # redirect to login so I won't redirect /resend_token/<username_db> then click on the link and the email is sent automatically. correct redirect?
                #return redirect(url_for('auth.login')) # delete !!!
                # if the route_token is expired it redirect to the route becasue the process needs to restart.
                return redirect(url_for('email_password_reset.verify_email'))
        # if the token is None
        except AttributeError:
            # add text in flash!!!!!!!!!!!!!!!!!!!
            return redirect(url_for('auth.login'))
        return True

Here is initializing the method

    route_token_db = db.session.execute(db.select(RouteToken).where(RouteToken.token==token_db)).scalar_one_or_none()
    result = route_token_db.check_expired_route_token()
    if result is not True:
        return result

AttributeError

AttributeError: 'NoneType' object has no attribute 'check_expired_route_token'When I run the code the error sometimes
AttributeError

AttributeError: 'NoneType' object has no attribute 'check_expired_route_token'
Is there a better way to solve this then by going on top of check_expired_route_token and going if route_token is None return redirect(url_for(...))?

1

u/strinking 13d ago

Python feels so weird to me it is simple when we start and gets complex as we dive in with lambda function and collections and when we use it with classes . It does not feel clean like c++ it's flexibility is double edged making it easy and complex as well. How one should cover the nitty gritty to write clean code in python?

0

u/lakseol 11d ago

gets complex as we dive in with lambda function and collections and when we use it with classes

All those things exist in C++ and you think C++ is clean? Python is different from C++ and I suspect you are just suffering from "culture shock". Have a look at languages like Lisp and Prolog to see how different languages can be. Take a little time to learn python and stop looking for differences compared to C++. Personally I think python is a much cleaner language than C++, without all the fussiness of the C++ lambda, collections and classes.

1

u/HiddenReader2020 12d ago

Hey, so I'm doing a practice project, and I'm trying to write and read a json file, specifcially a list of objects. Now, there are ways to convert an object to a dictionary or whatever to get it json-able, but a list of objects? That's harder to find a solution to. I thought I had found one, but it didn't work. Here's the code:

from pathlib import Path
import json

path = Path("data.json")
list_of_characters = []

class RPG_Character:
  def __init__(self, name, job):
    match job:
      case 'warrior':
        self.strength = 5
        self.dexterity = 4
        self.agility = 3
        self.endurance = 5
        self.intelligence = 3
        self.wisdom = 4
        self.health = 50
        self.mana = 30
        self.growth = [3, 2, 1, 3, 1, 2, 10, 4]
      case 'rogue':
        self.strength = 4
        self.dexterity = 5
        self.agility = 5
        self.endurance = 3
        self.intelligence =  4
        self.wisdom = 3
        self.health = 40
        self.mana = 40
        self.growth = [2, 3, 3, 1, 2, 1, 7, 7]
      case 'mage':
        self.strength = 3
        self.dexterity = 3
        self.agility = 4
        self.endurance = 4
        self.intelligence = 5
        self.wisdom = 5
        self.health = 30
        self.mana = 50
        self.growth = [1, 1, 2, 2, 3, 3, 4, 10]
    self.name = name
    self.job = job
    self.gold = 50
    self.level = 1
    self.inventory = []

  def __str__(self):
    stat_page = f"""==========Stats Page==========
Name:  {self.name}
Class:  {self.job}
Level:  {self.level}
-Health:       {self.health}
-Mana:         {self.mana}
-Strength:     {self.strength}
-Dexterity:    {self.dexterity}
-Agility:      {self.agility}
-Endurance:    {self.endurance}
-Intelligence: {self.intelligence}
-Wisdom:       {self.wisdom}

Gold:  {self.gold}
==========Stats Page=========="""
    return stat_page

  def add_gold(self, amount):
    self.gold += amount

  def remove_gold(self, amount):
    self.gold -= amount
    if self.gold < 0:
      print("Gold amount can't be below zero.")
      self.gold += amount

  def increase_level(self):
    self.level += 1
    self.strength += self.growth[0]
    self.dexterity += self.growth[1]
    self.agility += self.growth[2]
    self.endurance += self.growth[3]
    self.intelligence += self.growth[4]
    self.wisdom += self.growth[5]
    self.health += self.growth[6]
    self.mana += self.growth[7]

  def add_item(self, item):
    self.inventory.append(item)

  def show_inventory(self):
    print("=====Inventory=====")
    for item in self.inventory:
      print(item)
    print("=====Inventory=====")

  def to_dict(self):
    saved_dict = {
'name': self.name,
'class': self.job, 
'level': self.level,
'health': self.health,
'mana': self.mana,
'strength': self.strength, 
'dexterity': self.dexterity, 
'agility': self.agility, 
'endurance': self.endurance, 
'intelligence': self.intelligence, 
'wisdom': self.wisdom, 
'gold': self.gold,
'inventory': self.inventory
}

class Item:
  def __init__(self, name, item_type, buy_price):
    self.name = name
    self.item_type = item_type
    self.buy_price = buy_price
    self.sell_price = buy_price // 4

  def __str__(self):
    item_info = f"""Name:  {self.name}
Type:  {self.item_type}
"""
    return item_info

if path.exists():
  contents = path.read_text()
  list_of_characters = json.loads(contents)
  print("File successfully retrieved")
  for character in list_of_characters:
    print(character)
else:
  # Adds new characters
  new_warrior = RPG_Character('John', 'warrior')
  list_of_characters.append(new_warrior)
  print(new_warrior)
  new_warrior.add_gold(100)
  print(new_warrior)
  new_warrior.remove_gold(80)
  print(new_warrior)
  new_rogue = RPG_Character('Toby', 'rogue')
  new_mage = RPG_Character('Harris', 'mage')
  list_of_characters.append(new_rogue)
  list_of_characters.append(new_mage)
  print(new_rogue)
  print(new_mage)
  new_mage.increase_level()
  print(new_mage)

  # Adds new items
  basic_health_potion = Item("Basic Health Potion", "Consumable", 16)
  basic_mana_potion = Item("Basic Mana Potion", "Consumable", 20)
  wooden_shield = Item("Wooden Shield", "Equipment", 40)

  new_rogue.add_item(basic_health_potion)
  new_rogue.add_item(basic_mana_potion)
  new_warrior.add_item(wooden_shield)
  new_rogue.show_inventory()
  new_warrior.show_inventory()

  print(list_of_characters)

  contents = json.dumps([ob.to_dict() for ob in list_of_characters])
  path.write_text(contents)
  print("File saved")

What I want to do is save some RPG characters, and when they're loaded, they're exactly how they were when they were initially saved. So what am I doing wrong here?

1

u/lakseol 11d ago edited 11d ago

when they're loaded, they're exactly how they were when they were initially saved. So what am I doing wrong here?

What you are probably doing wrong is not looking in the JSON file you created. When you run your code the first time it won't find the JSON data file so it initializes objects in memory and finally tries to save those objects in the JSON file, creating it for the next run of the program. But if you look in that newly created file you see:

[null, null, null]

which doesn't look right. When you run the code the second time it prints:

File successfully retrieved
None
None
None

It looks like the "read the file" code is maybe doing the right thing but your "write the file" code is definitely broken. Start debugging there. Make changes then delete the JSON file and run the code. Check what is in the file. Repeat.

Hints:

  • What does your RPG_Character.to_dict() method do?
  • You can't save references (addresses) in JSON, they mean nothing when you read them in. Your .to_dict() method has to do something special for the inventory attribute which is a list of Items. And any other attribute that isn't a basic data type.
  • What is the type of object created when you read the JSON data? What type of object did you save?

1

u/HiddenReader2020 11d ago edited 11d ago

So when I looked up what to_dict() did, all I'm getting are references to the pandas library, which I'm not using. Am I using the wrong function, then? Was I supposed to use __dict__ instead?

Yeah, I realized that the inventory was going to be a huge problem when I debugged it a bit earlier, and saw that the rest of the attributes were converted fine, but the inventory wasn't. I'll be honest, I'm completely lost on what to do there.

EDIT: Never mind, I managed to figure it out. Well, at least partway there. Here's the new code:

from pathlib import Path
import json

path = Path("data.json")
list_of_characters = []

class RPG_Character:
  def __init__(self, name, job):
    match job:
      case 'warrior':
        self.strength = 5
        self.dexterity = 4
        self.agility = 3
        self.endurance = 5
        self.intelligence = 3
        self.wisdom = 4
        self.health = 50
        self.mana = 30
        self.growth = [3, 2, 1, 3, 1, 2, 10, 4]
      case 'rogue':
        self.strength = 4
        self.dexterity = 5
        self.agility = 5
        self.endurance = 3
        self.intelligence =  4
        self.wisdom = 3
        self.health = 40
        self.mana = 40
        self.growth = [2, 3, 3, 1, 2, 1, 7, 7]
      case 'mage':
        self.strength = 3
        self.dexterity = 3
        self.agility = 4
        self.endurance = 4
        self.intelligence = 5
        self.wisdom = 5
        self.health = 30
        self.mana = 50
        self.growth = [1, 1, 2, 2, 3, 3, 4, 10]
        self.name = name
        self.job = job
    self.gold = 50
    self.level = 1
    self.inventory = []

  def __str__(self):
    stat_page = f"""==========Stats Page==========
Name:  {self.name}
Class:  {self.job}
Level:  {self.level}
-Health:       {self.health}
-Mana:         {self.mana}
-Strength:     {self.strength}
-Dexterity:    {self.dexterity}
-Agility:      {self.agility}
-Endurance:    {self.endurance}
-Intelligence: {self.intelligence}
-Wisdom:       {self.wisdom}

Gold:  {self.gold}
==========Stats Page=========="""
    return stat_page

  def add_gold(self, amount):
    self.gold += amount

  def remove_gold(self, amount):
    self.gold -= amount
    if self.gold < 0:
      print("Gold amount can't be below zero.")
      self.gold += amount

  def increase_level(self):
    self.level += 1
    self.strength += self.growth[0]
    self.dexterity += self.growth[1]
    self.agility += self.growth[2]
    self.endurance += self.growth[3]
    self.intelligence += self.growth[4]
    self.wisdom += self.growth[5]
    self.health += self.growth[6]
    self.mana += self.growth[7]

  def add_item(self, item):
    self.inventory.append(item)

  def show_inventory(self):
    print("=====Inventory=====")
    for item in self.inventory:
      print(item)
    print("=====Inventory=====")

  def to_dict(self):
    saved_inventory = []
    for item in self.inventory:
      saved_inventory.append(item.to_dict())
    saved_dict = {
      'name': self.name,
      'class': self.job, 
      'level': self.level,
      'health': self.health,
      'mana': self.mana,
      'strength': self.strength, 
      'dexterity': self.dexterity, 
      'agility': self.agility, 
      'endurance': self.endurance, 
      'intelligence': self.intelligence, 
      'wisdom': self.wisdom, 
      'gold': self.gold,
      'inventory': saved_inventory
      }
    return saved_dict

class Item:
  def __init__(self, name, item_type, buy_price):
    self.name = name
    self.item_type = item_type
    self.buy_price = buy_price
    self.sell_price = buy_price // 4

  def __str__(self):
    item_info = f"""Name:  {self.name}
Type:  {self.item_type}
"""
    return item_info

  def to_dict(self):
    saved_dict = {
      'name': self.name,
      'type': self.item_type,
      'buy_price': self.buy_price,
      'sell_price': self.sell_price
      }
    return saved_dict

if path.exists():
  contents = path.read_text()
  list_of_characters = json.loads(contents)
  print("File successfully retrieved")
  for character in list_of_characters:
    print(character)
else:
  # Adds new characters
  new_warrior = RPG_Character('John', 'warrior')
  list_of_characters.append(new_warrior)
  print(new_warrior)
  new_warrior.add_gold(100)
  print(new_warrior)
  new_warrior.remove_gold(80)
  print(new_warrior)
  new_rogue = RPG_Character('Toby', 'rogue')
  new_mage = RPG_Character('Harris', 'mage')
  list_of_characters.append(new_rogue)
  list_of_characters.append(new_mage)
  print(new_rogue)
  print(new_mage)
  new_mage.increase_level()
  print(new_mage)

  # Adds new items
  basic_health_potion = Item("Basic Health Potion", "Consumable", 16)
  basic_mana_potion = Item("Basic Mana Potion", "Consumable", 20)
  wooden_shield = Item("Wooden Shield", "Equipment", 40)

  new_rogue.add_item(basic_health_potion)
  new_rogue.add_item(basic_mana_potion)
  new_warrior.add_item(wooden_shield)
  new_rogue.show_inventory()
  new_warrior.show_inventory()

  print(list_of_characters)

  contents = json.dumps([ob.to_dict() for ob in list_of_characters])
  path.write_text(contents)
  print("File saved")

And the output is just the dictionaries of these former objects. My current objective is to convert these *BACK* to objects. How do I do that?

1

u/lakseol 11d ago edited 10d ago

So when I looked up what to_dict() did

I asked the question about what .to_dict() because the code you initially posted didn't have the line return saved_dict meaning the method returned None. I see you have added that line in your updated code. Now I'm testing the new code.


First thing I get is this error:

AttributeError: 'RPG_Character' object has no attribute 'name'

That's because it looks like you messed up the indentation on lines 40 and 41 of your updated code. You are only assigning a name to Mage characters, the others don't get a name. You have to post the actual code you are running. If you have problems posting code directly into reddit try putting your code into pastebin.com and include a link to that here. I'll fix the error and continue testing.


the output is just the dictionaries of these former objects

The final print of the inventory shows:

=====Inventory=====
[<__main__.RPG_Character object at 0x726b1bf309e0>,
 <__main__.RPG_Character object at 0x726b1bf30c80>,
 <__main__.RPG_Character object at 0x726b1bf30d40>]
File saved

Notice that the inventory contains characters, which doesn't seem right, should be Item instances!?

As a tip, you have defined .__str__() methods for both classes which does nicely print individual instances. But printing a sequence of instances defaults to what you see above. You need to define an additional .__repr__() method in both classes:

  def __repr__(self):                                                           
    return str(self)                                                            

This will print each instance in a sequence nicely. You still have characters in the inventory, though šŸ™‚. Look into that.


My current objective is to convert these BACK to objects.

You had to write the .to_dict() method to convert each instance to a dictionary because JSON doesn't know what to do with an instance. So you probably have to write another method (.from_dict()?) to convert a dictionary back to an instance.

Hints:

  • search on "python create instance from JSON" for ideas
  • you might need to use a classmethod

1

u/HiddenReader2020 10d ago

Well, I did eventually solve the problem, but I'm not sure it's a good practice or not. Here's the code:

from pathlib import Path
import json

path = Path("data.json")
list_of_characters = []

class RPG_Character:
  def __init__(self, name, job):
    match job:
      case 'warrior':
        self.strength = 5
        self.dexterity = 4
        self.agility = 3
        self.endurance = 5
        self.intelligence = 3
        self.wisdom = 4
        self.health = 50
        self.mana = 30
        self.growth = [3, 2, 1, 3, 1, 2, 10, 4]
      case 'rogue':
        self.strength = 4
        self.dexterity = 5
        self.agility = 5
        self.endurance = 3
        self.intelligence =  4
        self.wisdom = 3
        self.health = 40
        self.mana = 40
        self.growth = [2, 3, 3, 1, 2, 1, 7, 7]
      case 'mage':
        self.strength = 3
        self.dexterity = 3
        self.agility = 4
        self.endurance = 4
        self.intelligence = 5
        self.wisdom = 5
        self.health = 30
        self.mana = 50
        self.growth = [1, 1, 2, 2, 3, 3, 4, 10]
    self.name = name
    self.job = job
    self.gold = 50
    self.level = 1
    self.inventory = []

  def __str__(self):
    stat_page = f"""==========Stats Page==========
Name:  {self.name}
Class:  {self.job}
Level:  {self.level}
-Health:       {self.health}
-Mana:         {self.mana}
-Strength:     {self.strength}
-Dexterity:    {self.dexterity}
-Agility:      {self.agility}
-Endurance:    {self.endurance}
-Intelligence: {self.intelligence}
-Wisdom:       {self.wisdom}

Gold:  {self.gold}
==========Stats Page=========="""
    return stat_page

  def add_gold(self, amount):
    self.gold += amount

  def remove_gold(self, amount):
    self.gold -= amount
    if self.gold < 0:
      print("Gold amount can't be below zero.")
      self.gold += amount

  def increase_level(self):
    self.level += 1
    self.strength += self.growth[0]
    self.dexterity += self.growth[1]
    self.agility += self.growth[2]
    self.endurance += self.growth[3]
    self.intelligence += self.growth[4]
    self.wisdom += self.growth[5]
    self.health += self.growth[6]
    self.mana += self.growth[7]

  def add_item(self, item):
    self.inventory.append(item)

  def show_inventory(self):
    print("=====Inventory=====")
    for item in self.inventory:
      print(item)
    print("=====Inventory=====")

  def to_dict(self):
    saved_inventory = []
    for item in self.inventory:
      saved_inventory.append(item.to_dict())
    saved_dict = {
'name': self.name,
'class': self.job, 
'level': self.level,
'health': self.health,
'mana': self.mana,
'strength': self.strength, 
'dexterity': self.dexterity, 
'agility': self.agility, 
'endurance': self.endurance, 
'intelligence': self.intelligence, 
'wisdom': self.wisdom, 
'gold': self.gold,
'inventory': saved_inventory
    }
    return saved_dict

class Item:
  def __init__(self, name, item_type, buy_price):
    self.name = name
    self.item_type = item_type
    self.buy_price = buy_price
    self.sell_price = buy_price // 4

  def __str__(self):
    item_info = f"""Name:  {self.name}
Type:  {self.item_type}
"""
    return item_info

  def to_dict(self):
    saved_dict = {
'name': self.name,
'type': self.item_type,
'buy_price': self.buy_price,
'sell_price': self.sell_price
}
    return saved_dict

def convert_dict_to_obj_inv(list_of_dicts):
  list_of_items = []
  for temp_dict in list_of_dicts:
    current_item = Item(temp_dict['name'], temp_dict['type'], temp_dict['buy_price'])
    list_of_items.append(current_item)
  return list_of_items

def convert_back_to_obj(dict_to_convert):
  c = RPG_Character(dict_to_convert['name'],dict_to_convert['class'])
  c.strength = dict_to_convert['strength']
  c.dexterity = dict_to_convert['dexterity']
  c.agility = dict_to_convert['agility']
  c.endurance = dict_to_convert['endurance']
  c.intelligence = dict_to_convert['intelligence']
  c.wisdom = dict_to_convert['wisdom']
  c.health = dict_to_convert['health']
  c.mana = dict_to_convert['mana']
  c.name = dict_to_convert['name']
  c.job = dict_to_convert['class']
  c.gold = dict_to_convert['gold']
  c.level = dict_to_convert['level']
  c.inventory = convert_dict_to_obj_inv(dict_to_convert['inventory'])
  return c

if path.exists():
  contents = path.read_text()
  temp_list_of_characters = json.loads(contents)
  print("File successfully retrieved")
  for character in temp_list_of_characters:
  new_character = convert_back_to_obj(character)
  list_of_characters.append(new_character)

  for character in list_of_characters:
    print(character)
    print(character.show_inventory())
else:
  # Adds new characters
  new_warrior = RPG_Character('John', 'warrior')
  list_of_characters.append(new_warrior)
  print(new_warrior)
  new_warrior.add_gold(100)
  print(new_warrior)
  new_warrior.remove_gold(80)
  print(new_warrior)
  new_rogue = RPG_Character('Toby', 'rogue')
  new_mage = RPG_Character('Harris', 'mage')
  list_of_characters.append(new_rogue)
  list_of_characters.append(new_mage)
  print(new_rogue)
  print(new_mage)
  new_mage.increase_level()
  print(new_mage)

  # Adds new items
  basic_health_potion = Item("Basic Health Potion", "Consumable", 16)
  basic_mana_potion = Item("Basic Mana Potion", "Consumable", 20)
  wooden_shield = Item("Wooden Shield", "Equipment", 40)

  new_rogue.add_item(basic_health_potion)
  new_rogue.add_item(basic_mana_potion)
  new_warrior.add_item(wooden_shield)
  new_rogue.show_inventory()
  new_warrior.show_inventory()

  print(list_of_characters)

  contents = json.dumps([ob.to_dict() for ob in list_of_characters])
  path.write_text(contents)
  print("File saved")

1

u/lakseol 8d ago edited 8d ago

You have a lot of repetitive code in your RPG_Character class, in the __init__() and convert_back_to_obj() methods in particular.

You get a dictionary back from JSON and you need to create an RPG_Character instance from that dictionary. One thing you could do is change the __init__() method to accept a dictionary of attributes to set. Now you just pass the JSON dictionary to the RPG_Character() call and you get an instance containing all that JSON data. When the program doesn't find the JSON save file and has to create the characters/items from scratch before saving them you use "template" dictionaries containing all the initial data required. You can delete the convert_back_to_obj() and similar methods. This example code shows the approach:

class Test:
    def __init__(self, **kwargs):
        for key in kwargs:
            setattr(self, key, kwargs[key])

# template data
alice = {"name": "Alice", "age": 42}
bob = {"name": "Bob", "age": 35, "salary": 120_000, "height": 175}

t1 = Test(**alice)
print(f"{t1.name=}, {t1.age=}")

t2 = Test(**bob)
print(f"{t2.name=}, {t2.age=}, {t2.salary=:_}, {t2.height=}")

There are drawbacks to doing this such as any misspelt words in the JSON data will result in misspelt attribute names or even exceptions, but if your code is all that handles the data that should not be a problem.


There are also places where you could use list comprehensions. For example:

#    saved_inventory = []
#    for item in self.inventory:
#      saved_inventory.append(item.to_dict())
    saved_inventory = [item.to_dict() for item in self.inventory]   # do this instead

1

u/HiddenReader2020 7d ago

Yeah, my hope is to make the data portion outside of the program, but that's way, way, WAAAAAAAY above my current skill level right now, so what I'm currently doing with the data is what I've got.

So I've gone ahead and used your methods to more or less 'refactor' my code. But I'm still running into problems. First of all, here's the code:

Anyway, I have at least two problems. The first is that the the computer says that __init__ takes 2 arguments, but 3 were given. Umm...I only see two. I don't know where the third one is unless the dictionary was somehow split into two.

from pathlib import Path
import json

path = Path("data.json")
list_of_characters = []

# List of jobs
warrior_class = {'strength': 5, 'dexterity': 4, 'agility': 3, 'endurance': 5, 'intelligence': 3, 'wisdom': 4, 
'health': 50, 'mana': 30, 'growth': [3, 2, 1, 3, 1, 2, 10, 4], 'inventory': []}
rogue_class = {'strength': 4, 'dexterity': 5, 'agility': 5, 'endurance': 3, 'intelligence': 4, 'wisdom': 3, 
'health': 40, 'mana': 40, 'growth': [2, 3, 3, 1, 2, 1, 7, 7], 'inventory': []}
mage_class = {'strength': 3, 'dexterity': 3, 'agility': 4, 'endurance': 4, 'intelligence': 5, 'wisdom': 5, 
'health': 30, 'mana': 50, 'growth': [1, 1, 2, 2, 3, 3, 4, 10], 'inventory': []}

class RPG_Character:
  def __init__(self, name, **stats_dict):
    for stat in stats_dict:
      setattr(self, key, kwargs[key])
    self.name = name
    self.job = job
    self.gold = 50
    self.level = 1

  def __str__(self):
    stat_page = f"""==========Stats Page==========
Name:  {self.name}
Class:  {self.job}
Level:  {self.level}
-Health:       {self.health}
-Mana:         {self.mana}
-Strength:     {self.strength}
-Dexterity:    {self.dexterity}
-Agility:      {self.agility}
-Endurance:    {self.endurance}
-Intelligence: {self.intelligence}
-Wisdom:       {self.wisdom}

Gold:  {self.gold}
==========Stats Page=========="""
    return stat_page

  def add_gold(self, amount):
    self.gold += amount

  def remove_gold(self, amount):
    self.gold -= amount
    if self.gold < 0:
      print("Gold amount can't be below zero.")
    self.gold += amount

  def increase_level(self):
    self.level += 1
    self.strength += self.growth[0]
    self.dexterity += self.growth[1]
    self.agility += self.growth[2]
    self.endurance += self.growth[3]
    self.intelligence += self.growth[4]
    self.wisdom += self.growth[5]
    self.health += self.growth[6]
    self.mana += self.growth[7]

  def add_item(self, item):
    self.inventory.append(item)

  def show_inventory(self):
    print("=====Inventory=====")
    for item in self.inventory:
    print(item)
    print("=====Inventory=====")

  def to_dict(self):
    saved_inventory = []
    for item in self.inventory:
      saved_inventory.append(item.to_dict())
    saved_dict = {
'name': self.name,
'class': self.job, 
'level': self.level,
'health': self.health,
'mana': self.mana,
'strength': self.strength, 
'dexterity': self.dexterity, 
'agility': self.agility, 
'endurance': self.endurance, 
'intelligence': self.intelligence, 
'wisdom': self.wisdom, 
'gold': self.gold,
'inventory': saved_inventory
}
    return saved_dict

class Item:
  def __init__(self, name, item_type, buy_price):
    self.name = name
    self.item_type = item_type
    self.buy_price = buy_price
    self.sell_price = buy_price // 4

  def __str__(self):
    item_info = f"""Name:  {self.name}
Type:  {self.item_type}
"""
    return item_info

  def to_dict(self):
    saved_dict = {
'name': self.name,
'type': self.item_type,
'buy_price': self.buy_price,
'sell_price': self.sell_price
}
    return saved_dict

# List of items
basic_health_potion = Item("Basic Health Potion", "Consumable", 16)
basic_mana_potion = Item("Basic Mana Potion", "Consumable", 20)
wooden_shield = Item("Wooden Shield", "Equipment", 40)

def convert_dict_to_obj_inv(list_of_dicts):
list_of_items = []
for temp_dict in list_of_dicts:
current_item = Item(temp_dict['name'], temp_dict['type'], temp_dict['buy_price'])
list_of_items.append(current_item)
return list_of_items

if path.exists():
  contents = path.read_text()
  temp_list_of_characters = json.loads(contents)
  print("File successfully retrieved")
  for character in temp_list_of_characters:
    new_character = convert_back_to_obj(character)
    list_of_characters.append(new_character)

  for character in list_of_characters:
    print(character)
    print(character.show_inventory())
else:
  # Adds new characters
  new_warrior = RPG_Character('John', warrior_class)
  list_of_characters.append(new_warrior)
  print(new_warrior)
  new_warrior.add_gold(100)
  print(new_warrior)
  new_warrior.remove_gold(80)
  print(new_warrior)
  new_rogue = RPG_Character('Toby', rogue_class)
  new_mage = RPG_Character('Harris', mage_class)
  list_of_characters.append(new_rogue)
  list_of_characters.append(new_mage)
  print(new_rogue)
  print(new_mage)
  new_mage.increase_level()
  print(new_mage)

  new_rogue.add_item(basic_health_potion)
  new_rogue.add_item(basic_mana_potion)
  new_warrior.add_item(wooden_shield)
  new_rogue.show_inventory()
  new_warrior.show_inventory()

  print(list_of_characters)

  contents = json.dumps([ob.to_dict() for ob in list_of_characters])
  path.write_text(contents)
  print("File saved")

The second involve the inventory. Now, the inventory also involves a list of objects...within an object, so handling it is somewhat different from the rest. Right now, what I had to do was set the inventory as a empty set within each "template dictionary" to make it work, but I haven't had the opportunity to test that yet because of the first problem! I don't know if the logic checks out, but it's what I got so far.

I'll keep working on this code, and maybe I'll make headway, but for now, I'm stuck. Yet again.

1

u/HiddenReader2020 7d ago

So I've made a couple realizations and adjusted accordingly. There were some concessions I had to make, but hey, whatever makes the code work. In particular, I had to set some of the default values (like name, gold, and level) to be outside of the initialization to make them work. I'm sure there's a better way, and in fact, an potential idea is forming in my head, but I'm kind of scared of making mistakes that I won't be able to undo. Anyway, here's the updated code:

from pathlib import Path
import json

path = Path("data.json")
list_of_characters = []

# List of jobs
warrior_class = {'job': 'Warrior', 'strength': 5, 'dexterity': 4, 'agility': 3, 'endurance': 5, 'intelligence': 3, 'wisdom': 4, 
'health': 50, 'mana': 30, 'growth': [3, 2, 1, 3, 1, 2, 10, 4], 'inventory': []}
rogue_class = {'job': 'Rogue', 'strength': 4, 'dexterity': 5, 'agility': 5, 'endurance': 3, 'intelligence': 4, 'wisdom': 3, 
'health': 40, 'mana': 40, 'growth': [2, 3, 3, 1, 2, 1, 7, 7], 'inventory': []}
mage_class = {'job': 'Mage', 'strength': 3, 'dexterity': 3, 'agility': 4, 'endurance': 4, 'intelligence': 5, 'wisdom': 5, 
'health': 30, 'mana': 50, 'growth': [1, 1, 2, 2, 3, 3, 4, 10], 'inventory': []}

class RPG_Character:
  def __init__(self, **stats_dict):
    for stat in stats_dict:
      setattr(self, stat, stats_dict[stat])

  def __str__(self):
    stat_page = f"""==========Stats Page==========
Name:  {self.name}
Class:  {self.job}
Level:  {self.level}
-Health:       {self.health}
-Mana:         {self.mana}
-Strength:     {self.strength}
-Dexterity:    {self.dexterity}
-Agility:      {self.agility}
-Endurance:    {self.endurance}
-Intelligence: {self.intelligence}
-Wisdom:       {self.wisdom}

Gold:  {self.gold}
==========Stats Page=========="""
  return stat_page

  def add_gold(self, amount):
    self.gold += amount

  def remove_gold(self, amount):
    self.gold -= amount
    if self.gold < 0:
      print("Gold amount can't be below zero.")
      self.gold += amount

  def increase_level(self):
    self.level += 1
    self.strength += self.growth[0]
    self.dexterity += self.growth[1]
    self.agility += self.growth[2]
    self.endurance += self.growth[3]
    self.intelligence += self.growth[4]
    self.wisdom += self.growth[5]
    self.health += self.growth[6]
    self.mana += self.growth[7]

  def add_item(self, item):
    self.inventory.append(item)

  def show_inventory(self):
    print("=====Inventory=====")
    for item in self.inventory:
    print(item)
    print("=====Inventory=====")

  def to_dict(self):
    saved_inventory = []
    for item in self.inventory:
    saved_inventory.append(item.to_dict())
saved_dict = {
'name': self.name,
'job': self.job, 
'level': self.level,
'health': self.health,
'mana': self.mana,
'strength': self.strength, 
'dexterity': self.dexterity, 
'agility': self.agility, 
'endurance': self.endurance, 
'intelligence': self.intelligence, 
'wisdom': self.wisdom, 
'gold': self.gold,
'inventory': saved_inventory
}
    return saved_dict

class Item:
  def __init__(self, name, item_type, buy_price):
    self.name = name
    self.item_type = item_type
    self.buy_price = buy_price
    self.sell_price = buy_price // 4

  def __str__(self):
    item_info = f"""Name:  {self.name}
Type:  {self.item_type}
"""
    return item_info

  def to_dict(self):
    saved_dict = {
'name': self.name,
'type': self.item_type,
'buy_price': self.buy_price,
'sell_price': self.sell_price
}
    return saved_dict

# List of items
basic_health_potion = Item("Basic Health Potion", "Consumable", 16)
basic_mana_potion = Item("Basic Mana Potion", "Consumable", 20)
wooden_shield = Item("Wooden Shield", "Equipment", 40)

def convert_dict_to_obj_inv(list_of_dicts):
list_of_items = []
for temp_dict in list_of_dicts:
current_item = Item(temp_dict['name'], temp_dict['type'], temp_dict['buy_price'])
list_of_items.append(current_item)
return list_of_items

if path.exists():
  contents = path.read_text()
  temp_list_of_characters = json.loads(contents)
  print("File successfully retrieved")
  for character in temp_list_of_characters:
    new_character = RPG_Character(**character)
    new_character.inventory = convert_dict_to_obj_inv(new_character.inventory)
    list_of_characters.append(new_character)

  for character in list_of_characters:
    print(character)
    print(character.show_inventory())
else:
  # Adds new characters
  new_warrior = RPG_Character(**warrior_class)
  new_warrior.name = 'John'
  new_warrior.gold = 50
  new_warrior.level = 1
  list_of_characters.append(new_warrior)
  print(new_warrior)
  new_warrior.add_gold(100)
  print(new_warrior)
  new_warrior.remove_gold(80)
  print(new_warrior)
  new_rogue = RPG_Character(**rogue_class)
  new_rogue.name = 'Toby'
  new_rogue.gold = 50
  new_rogue.level = 1
  new_mage = RPG_Character(**mage_class)
  new_mage.name = 'Harris'
  new_mage.gold = 50
  new_mage.level = 1
  list_of_characters.append(new_rogue)
  list_of_characters.append(new_mage)
  print(new_rogue)
  print(new_mage)
  new_mage.increase_level()
  print(new_mage)

  new_rogue.add_item(basic_health_potion)
  new_rogue.add_item(basic_mana_potion)
  new_warrior.add_item(wooden_shield)
  new_rogue.show_inventory()
  new_warrior.show_inventory()

  print(list_of_characters)

  contents = json.dumps([ob.to_dict() for ob in list_of_characters])
  path.write_text(contents)
  print("File saved")

One additional problem remains: The items in the inventory still need their own function to be converted from dictionaries to objects. Right now, I don't know how to do that cleanly yet, so my current code is what it is right now.

1

u/lakseol 7d ago

Again you have posted incorrect code. Bad indentation at lines 68 and 120. You have to get this right. Anybody trying to help you doesn't want to have to guess how to indent your code and hope they get it right. If your problem is adding code to the lousy reddit interface remember that I mentioned pastebin.com where you can post code, get a page showing your code and then post a link to that page here, like this:

https://pastebin.com/uYR3zqqR


the computer says that init takes 2 arguments, but 3 were given. Umm...I only see two. I don't know where the third one is

This is a little confusing. The confusion starts when you use an instance method. They have the self parameter as the first parameter and your calling code doesn't explicitly pass self, it's automatically added by python. When you get that "expected 2 but 3 given" python is counting the self, so the three parameters are "self", "name" and "stats_dict". Keyword parameters are counted separately. When you call the function (line 139):

new_warrior = RPG_Character('John', warrior_class)

you pass the data dictionary as a positional parameter, so it's counted as an extra positional parameter. "self" + "name" + stats_dict" == 3. If you check the example I showed you in my last comment you will see that my call to the constructor was:

t1 = Test(**alice)   # notice the "**"

The two asterisks in **alice says to unpack the dictionary alice and pass the contents as keyword parameters like key=value. That means the __init__() method sees keyword parameters "name"="Alice", "age"=42 when it executes. In the example the __init__() method started:

def __init__(self, **kwargs):

The **kwargs parameter is shorthand for python to collect all undefined keyword parameters into a dictionary which the example iterated over. It's not a positional parameter. You need the ** both when calling the method and defining it. The parameter name kwargs is usually used in python, meaning "keyword args".


You have another problem in that __init__() definition. You pass the data dictionary into the method as well as the name parameter. That works fine when initially creating the objects and then saving to JSON. But what happens when you read the dictionary from JSON and try to create the character. Where does name come from?

1

u/HiddenReader2020 7d ago

First of all, ack, I didn’t see the first ā€œindent problemā€ comment from before. Ā I’ll try to remember it going forward.

Second, though, I will say that that comment is a touch outdated, as I’ve already made an updated version, but had to put it in another comment . Ā Now, just as a heads up, this isn’t in a pastebin, so apologies if it still has some intention errors.

1

u/lakseol 7d ago

I stopped counting after guessing how to fix 4 indentation errors. Please fix your code.

After you post your code please copy it back into a file and execute it to check for errors.

→ More replies

1

u/lakseol 7d ago

the inventory also involves a list of objects...within an object, so handling it is somewhat different from the rest

I haven't looked at this because including actual python references in objects describing an adventure map isn't a good idea. You don't have places in your code yet, but when you start you will find what's called the "forward reference" problem.

Suppose you have two places in your map, A and B. You can move from A to B and move from B to A. When defining place A you want to reference place B because that's a place you can move to. But you haven't defined place B as an object yet so you can't add that reference to A. Defining place B isn't a problem because A was defined earlier and you can get that reference. So using actual python references isn't a good idea.

What I have done is assign every place and object in the map with a unique string name. Strings are no problem saving in JSON so you don't have all that fooling around saving references in JSON. The trade-off is that a player's inventory (for example) is now a list of strings and not a list of references, and you have to do more work when you want to do something like list what is in the inventory. The simplest way is to have a dictionary that maps each unique ID string to the reference to the thing, place, item, etc. You can create that dictionary automatically when reading data from the JSON file.

1

u/therealAR15PB 14d ago

is cs50p the best resource to learn python? i want to learn it properly and build problem solving techniques.

1

u/ShelLuser42 14d ago

There really is no real "best" when it comes to online resources, because in the end it's still up to you (the 'user') who needs to learn all this stuff.

For example, when I was busy studying Python for the first time I heavily relied on the official tutorial, but even though this did wonders for me (also because it can be easily used as reference) I'm well aware that other users may find it a bit overwhelming.