Appending to array in object appends to all instances - python

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!

Related

Updating __init__ elements through a class method in Python

I have a class that takes a list of strings as an argument and I would like to have
my class methods update the element in the init method. Not sure if this is possible or bad practice, but any advice would be really appreciated! The code would be like:
class TextList:
def __init__(self, listofstrings):
self.strings = listofstrings
def my_method(self):
newstrings = [i.strip() for i in self.strings]
# code that updates 'self.strings' to equal 'newstrings'
So that I could run:
mylist = TextList(mylistofstrings)
mylist = mylist.my_method()
And mylist.strings would equal the output of 'my_method'
Thanks again!
If you just want to update the existing list, you can do that directly.
def my_method(self):
self.strings = [i.strip() for i in self.strings]
Since this method updates self.strings directly, there is no need to return anything.
>>> mylist = TextList(["hi\n", "bye\n"])
>>> mylist.strings
['hi\n', 'bye\n']
>>> mylist.my_method()
>>> mylist.strings
['hi', 'bye']

Declaring nested objects in nested for loops - python 3

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!

Python: Adding to dict of one object in a list changes all dicts of every other object in the list

So Python isn't my strong suit and I've encountered what I view to be a strange issue. I've narrowed the problem down to a few lines of code, simplifying it to make asking this question easier. I have a list of objects, this object:
class FinalRecord():
ruid = 0
drugs = {}
I create them in the shell like this:
finalRecords = []
fr = FinalRecord()
fr.ruid = 7
finalRecords.append(fr)
fr2 = FinalRecord()
fr2.ruid = 10
finalRecords.append(fr2)
As soon as I want to change the drugs dict on one object, it changes it for the other one too
finalRecords[0].drugs["Avonex"] = "Found"
I print out this:
finalRecords[1].drugs
and it shows:
{'Avonex':'Found'}
When I'm expecting it to actually be empty. I know I'm not completely understand how Python is working with the objects, can anyone help me out here?
The reason for this is because drugs is a class attribute. So if you change it for one object it will in fact change in others.
If you are looking to not have this behaviour, then you are looking for instance attributes. Set drugs in your __init__ like this:
class FinalRecord():
def __init__(self):
self.ruid = 0
self.drugs = {}
Take note of the use of self, which is a reference to your object.
Here is some info on class vs instance attributes
So, full demo illustrating this behaviour:
>>> class FinalRecord():
... def __init__(self):
... self.ruid = 0
... self.drugs = {}
...
>>> obj1 = FinalRecord()
>>> obj2 = FinalRecord()
>>> obj1.drugs['stuff'] = 2
>>> print(obj1.drugs)
{'stuff': 2}
>>> print(obj2.drugs)
{}
You define drugs as a class attribute, not an instance attribute. Because of that, you are always modifying the same object. You should instead define drugs in the __init__ method. I would also suggest using ruid as an argument:
class FinalRecord():
def __init__(self, ruid):
self.ruid = ruid
self.drugs = {}
It could then be used as this:
fr = FinalRecord(7)
finalRecords.append(fr)
fr2 = FinalRecord(10)
finalRecords.append(fr2)
Or more simply:
finalRecords.append(FinalRecord(7))
finalRecords.append(FinalRecord(10))

Storing Class Arguments in a List

Recently I've been working on a text-based game as a personal project. No real reason, just for the heck of it. I'm usually pretty fluent in Python (I've completed the entirety of CodeCademy's Python course and done my own research), but I've come across something that I want to make sure will work before implementing.
Essentially, is it possible to nest the arguments for a class into a list?
Here's an example similar to what I'm doing:
class Item(object):
def __init__(self, name, weight):
self.name = name
self.weight = weight
tempDict = {
'flashlight' = ['flashlight',5],
}
flashlight1 = Item(tempDict['flashlight'])
print flashlight1.name
That should return with:
flashlight
I'm really hoping this will work. I have an entire ID structure that relies on this working. If not, how can I do something similar so I can have a prescribed list of items and base values that I can assign to specific items with IDs?
Thanks in advance.
It will work if you unpack tempDict['flashlight'] by placing * in front of it:
>>> class Item(object):
... def __init__(self, name, weight):
... self.name = name
... self.weight = weight
...
>>> tempDict = {
... 'flashlight' : ['flashlight',5],
... }
>>> flashlight1 = Item(*tempDict['flashlight'])
>>> print flashlight1.name
flashlight
>>> print flashlight1.weight
5
>>>
In the above demonstration:
flashlight1 = Item(*tempDict['flashlight'])
is equivalent to:
flashlight1 = Item('flashlight', 5)
Your dictionary of arguments would be even clearer if you used dictionaries, rather than lists, to store the arguments:
tempDict = {
'flashlight': { 'name': 'flashlight', 'weight': 5 }
}
flashlight1 = Item(**tempDict['flashlight'])

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