Declaring nested objects in nested for loops - python 3 - python

I'm a freshman in the fantastic world of python and at the moment I'm struggling with this problem...
That's an example of what I've coded:
class League():
def __init__(self, teams=[]):
self.teams = teams
def initLeague(self):
for a in range(2):
self.teams.append(Team())
self.teams[a].name = "Team" + str(a)
for b in range(3):
self.teams[a].players.append(Player())
self.teams[a].players[b].name = "Name-" + str(a) + "-" + str(b)
def printLeague(self):
for team in self.teams:
print(team.name)
for player in team.players:
print(player.name)
class Team():
def __init__(self, name=None, players=[]):
self.name = name
self.players = players
class Player():
def __init__(self, name=None):
self.name = name
nba = League()
nba.initLeague()
nba.printLeague()
The output looks like that:
Team0
Name-1-0
Name-1-1
Name-1-2
None
None
None
Team1
Name-1-0
Name-1-1
Name-1-2
None
None
None
[Finished in 0.051s]
So I would like to know where do these None come from? I noticed they depend on range(n)... it's like if the 'for a' loop is repeating inside the 'for b' loop.
Another problem is that the first part of the output should be:
Team0
Name-0-0
Name-0-1
Name-0-2
...
Could someone help me? Thank you!

If you replace your Team() class to this:
class Team():
def __init__(self, name=None):
self.name = name
self.players = []
Your output will become this:
Team0
Name-0-0
Name-0-1
Name-0-2
Team1
Name-1-0
Name-1-1
Name-1-2
which I think is what you're after. See this link: "Least Astonishment" and the Mutable Default Argument to see why.
The problem is that when using your current Team() initialiser, the players parameter is not passed with a default value of [] as you are expecting, instead, the value of players is persisted for each team made.
Each team has a reference to the same list, which is why the same list is printed twice.
The reason None is printed three times is because each time a team is created, 3 more players are added, but your inner for loop only modifies the names of the first 3 players, leaving the last 3 players untouched.

I agree with 0liveradam8's response and just to add on - if you would like to maintain the constructor signature you could try this:
class Team():
def __init__(self, name=None, players=None):
if players is None:
players = []
self.name = name
self.players = players
so you can still specify a list of players in the constructor if necessary.

The reason you are getting None is that you haven't returned the value in the functions. Therefore, when you run your script, it is set to nothing. Here's a link to look into it more.
http://interactivepython.org/runestone/static/pip2/Functions/Returningavaluefromafunction.html
The answer to your second question is that the
for a in range(2):
Should be like
for a in range(1):
Hope this helps!

Related

Why can't I add elements to this list? (Object oriented programming in python)

Hi everyone I am currently creating a student class to record down exam results and find the average score. Here's the code I have right now:
class Student:
def __init__(self, name):
self.name = name
self.all_grades = []
def add_exam(self, newest_grade):
self.newest_grade = newest_grade
self.all_grades = self.all_grades.append(newest_grade)
def get_mean(self):
self.average = sum(self.all_grades)/len(self.all_grades)
Josh = Student()
Josh.add_exam(72)
However, when I try to put print(Josh.all_grades), it doesn't return anything and type(Josh.all_grades) returns a none type.
I am really lost and I don't understand why it doesn't return [72]
You've to just type self.all_grades.append(newest_grade) instead of typing self.all_grades=self.all_grades.append(newest_grade). When you type append(), it adds element to list. But it returns None. So when you type <list_variable>.append(<element>) it adds the element, but when you type <list_variable>=<list_variable>.append(<element>) then the value of whole list is now None

(Classname) has no attribute (attribute)

I'm trying to create a DnD style dungeon crawler game. I'm using the 5E SRD and other publicly available information as the base for my characters and gameplay.
Currently I'm working on the character generator, and it seems to be going well, but I've hit a roadblock when trying to assign the racial bonuses. I've got the races set up as their own subclasses, each with it's unique bonuses. When I try to assign the appropriate bonuses based on the character's race I get a (Classname)has no attribute (attribute) error.
python
class Race:
def __init__(self, race):
self.name = race
self.racial_str_bonus = 0
self.racial_char_bonus = 0
class Dragonborn(Race):
def __init__(self):
super()
self.name = "Dragonborn"
self.racial_str_bonus = +2
self.racial_char_bonus = +1
def get_racial_bonus(race):
race = race
racial_str_bonus = 0
racial_char_bonus = 0
if race == "Dragonborn":
racial_str_bonus = Dragonborn.racial_str_bonus
racial_char_bonus = Dragonborn.racial_char_bonus
return racial_str_bonus, racial_char_bonus
class BaseCharacter:
def __init__(self, racial_str_bonus, racial_char_bonus):
self.racial_str_bonus = racial_str_bonus
self.racial_char_bonus = racial_char_bonus
#classmethod
def generate_player_character(cls):
cls.race = input("Race: ")
get_racial_bonus(cls.race)
BaseCharacter.generate_player_character()
What I'm looking for is something along the line of:
'''
Race: Dragonborn
print(my_player_char.racial_str_bonus)
2
'''
Where am I goofing up?
Thanks, everyone for the feedback. In cleaning up the code to get it minimally reproducible, I figured out the issue. Per Jonrsharpe's note, I corrected the inhertance invocation to 'super().init(self)'. Once that was correct, I realized that the way they had been defined, I had to include parentheses in the property call: "Dragonborn().racial_str_bonus".
Thanks again, and I will remember to improve my submissions in the future.

Appending to array in object appends to all instances

I'm working on a text based game. I've tried to make this as organized and professional as possible by trying to follow all conventions.
I have a Map class, shown below:
import logging
#local imports
import Npc
class Map:
def __init__(self, name, npcs = []):
self.name = name
connections = []
if all(isinstance(item, Npc) for item in npcs):
self.npcs = npcs
else:
raise Exception("An NPC was not an instance of NPC")
def addConnection(self, connection):
if(connection == self):
return
self.name = connection.name
self.connections.append(connection)
My Main class creates two instances of these maps named forest, and village.
The point of this code is to add village into the connections array of forest:
village = Map("Village")
forest = Map("Forest")
forest.addConnection(village)
It seems simple enough. But for some reason, when forest.addConnection(village) is run, or even if i do forest.connections.append(village), the Map instance "village" gets added to the connections array of both forest, and village.
According to the debugger, after forest.addConnection(village) is run,
my two objects look as shown:
village (Map)
|------> name="village"
|------> connections = [village]
forest (Map)
|------> name="forest"
|------> connections = [village]
Why is this happening? Nowhere in my code do I add anything to village's connections array. Is there something about object oriented programming in Python I'm not understanding? Should I make village and forest classes that inherit/extend the Map class?
Thanks in advance for all the help.
Try to avoid call a constructor as default argument of a function.
This is the cause of your issue.
Exemple :
>>> class Map():
... def __init__(self, a=list()): # do __init__(self, a=[]) produce same result
... print(a)
... a.append("hello")
...
>>> b = Map()
[]
>>> b = Map()
['hello']
>>> b = Map()
['hello', 'hello']
>>> b = Map()
['hello', 'hello', 'hello']
>>> b = Map()
['hello', 'hello', 'hello', 'hello']
So insead of doing :
def __init__(self, name, npcs = []):
self.name = name
...
do
def __init__(self, name, npcs = None):
if npcs is None:
npcs = []
self.name = name
...
Found the issue. #iElden got me looking in the right place.
In the constructor, I set connections = [], not self.connections = [].
Thanks for the responses!

Python - Can't create instances from a list or random instances

I am making a small text RPG to help me learn the Python language, and I am trying to create multiple instances of a class from a list of names.
I have a class of enemies (Named: Enemy) and would like to create between 1 and 3 "goblin" enemies at at time.
class Enemy:
def __init__(self, health):
self.health = health
How I have approached the problem so far is to use a for loop to run through letters 'a', 'b', and 'c' and append a list of enemies.
for i in ['a', 'b', 'c']:
enemylist.append('goblin' + (i))
This gives me a list of three goblins:
['goblina', 'goblinb', 'goblinc']
Now I would like to take each newly appended "goblin" in the list and create an instance of the enemy class using that goblin's name (Example: "goblina = enemy(10)"... "goblinb = enemy(10)...)
But when I try to create an instance using any number of ways including the following (which is probably the absolutely wrong way to):
for i in range (1, 3):
enemylist[i] = enemy(10)
All that I get is a single instance named enemylist[i].
Can someone please help me. Like I said, I am new to the language so please be gentle with the explanation but I am a fast learner and willing to read and research.
I spent the better part of 2 days (on and off) trying to get to the bottom of this and could not find a solution that worked.
Perhaps it would make more sense to keep the enemy's name as a member of the Enemy class as well:
import random
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def create_enemies():
enemies = []
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies.append(Enemy(name, health))
It's hard to make a suggestion without knowing how you're going to use the enemies. If you want to be able to look an enemy up by name, a dictionary would be a better data structure in which to store them:
def create_enemies():
enemies = {} # Initialize empty dict
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies[name] = Enemy(name, health)
return enemies
def main()
enemies = create_enemies()
ga = enemies['goblina']
I think adding a name to the class is good, but creating a dict with a key that's equal to the same name, but also storing the name and health as a dict value seems a bit redundant (as shown in answer above).
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def __repr__(self):
return self.name
This will give you a name to access. You can still store each instance of the enemy class in a list, using list comprehension if you like (or any other list creation):
for i in enemylist:
i = Enemy(i, 10)
enemies.append(i)
Now you have a list enemies, consisting of three instantiated objects of the class Enemy, that have a name that can be accessed. For instance
goblina.__name__
will return goblina and
isintance(goblina, Enemy)
will return True.

Python: Preventing duplication of data when using dictionaries and lists

Hello Stack Overflow!
I am executing a simple command in a program that compiles a report of all the books contained in a library. The library contains a list of shelves, each shelves contains a dictionary of books. However, despite my best efforts, I am always duplicating all my books and placing them on every shelf, instead of the shelf I've instructed the program to place the book on.
I expect I have missed out on some kind of fundamental rule with object creation and organization.
I believe the culprits are the enshelf and unshelf methods in the book class.
Thank you so much for your time,
Jake
Code below:
class book():
shelf_number = None
def __init__(self, title, author):
super(book, self).__init__()
self.title = title
self.author = author
def enshelf(self, shelf_number):
self.shelf_number = shelf_number
SPL.shelves[self.shelf_number].books[hash(self)] = self
def unshelf(self):
del SPL.shelves[self.shelf_number].books[hash(self)]
return self
def get_title(self):
return self.title
def get_author(self):
return self.author
class shelf():
books = {}
def __init__(self):
super(shelf, self).__init__()
def get_books(self):
temp_list = []
for k in self.books.keys():
temp_list.append(self.books[k].get_title())
return temp_list
class library():
shelves = []
def __init__(self, name):
super(library, self).__init__()
self.name = name
def make_shelf(self):
temp = shelf()
self.shelves.append(temp)
def remove_shelf(shelf_number):
del shelves[shelf_number]
def report_all_books(self):
temp_list = []
for x in range(0,len(self.shelves)):
temp_list.append(self.shelves[x].get_books())
print(temp_list)
#---------------------------------------------------------------------------------------
#----------------------SEATTLE PUBLIC LIBARARY -----------------------------------------
#---------------------------------------------------------------------------------------
SPL = library("Seattle Public Library")
for x in range(0,3):
SPL.make_shelf()
b1 = book("matterhorn","karl marlantes")
b2 = book("my life","bill clinton")
b3 = book("decision points","george bush")
b1.enshelf(0)
b2.enshelf(1)
b3.enshelf(2)
print(SPL.report_all_books())
b1.unshelf()
b2.unshelf()
b3.unshelf()
OUTPUT:
[['decision points', 'my life', 'matterhorn'], ['decision points', 'my life', 'matterhorn'], ['decision points', 'my life', 'matterhorn']]
None
[Finished in 0.1s]
..instead of [["decision points"],["my life"],["matterhorn"]]
Use dict.pop() instead of del.
Add self.books = {} to shelf's __init__. Don't declare books outside of the __init__, because if you do so, all of the instances of that class are going to refer to the same thing. Instead, this makes each instance have its own dictionary, which is of course what you want since a book can't be in two shelves at once.
Do the same for library and its shelves and book and its shelf_number.
Pass a library instance as an argument to enshelf and unshelf. When you refer to SPL from within your objects' methods, Python finds that there is no local SPL defined, so it searches for one outside of the local scope; but if you were to try to assign something to SPL or do some other sort of mutative business, you would get an UnboundLocalError.
Bonuses:
class book(object), class shelf(object), and class library(object). (Won't fix your problem, but you should do that anyway.)
You don't need to hash the keys before using them, they will be hashed (if they are hashable, but if you're hashing them, then they are).
There is no need to call super() unless you are inheriting from something, in which case you can delegate a method call to a parent or sibling using it - but you aren't doing that.
get_books() can be implemented as nothing more than return [self.books[k].get_title() for k in self.books.iterkeys()]
Likewise for report_all_books(): return [shlf.get_books() for shlf in self.shelves]. Note that I am not iterating over the indices, but rather over the elements themselves. Try for c in "foobar": print(c) in the interactive shell if you want to see for yourself.

Categories

Resources