I'm having problems when adding objects to a list. When I append objects to the end of a list and then try to loop through the it, every spot on the list just gives me back the most recently added object.
The script compares info from different projects in Excel spreadsheets. I'm using Python for Windows and the win32com.client to access the speadsheets I'm interested in. I read about a few others on Stack Overflow having problems adding unique objects to a list, but I'm pretty sure I don't have the same mistakes that they did (initializing a list in a loop, not providing input attributes when creating a class object).
I can comment out the object creation in the loop and simply add numbers to the list and am able to print out all three unique values, but as soon as I put the object creation call back in, things go wrong. The code below just prints three of the most recently added project. Any help would be greatly appreciated, thanks!
class Project:
"""
Creates an instance for each project
in the spreadsheet
"""
def __init__(self, bldg, zone, p_num, p_name, p_mgr,
const_mgr, ehs_lias, ehs_const, status,
p_type, start, finish):
self.bldg = bldg
self.zone = zone
self.p_num = p_num
self.p_name = p_name
self.p_mgr = p_mgr
self.const_mgr = const_mgr
self.ehs_lias = ehs_lias
self.ehs_const = ehs_const
self.status = status
self.p_type = p_type
self.start = start
self.finish = finish
def quickPrint(self):
""" prints quick glance projects details """
if p_name is None:
pass
else:
print 'Building ' + str(bldg.Value)
print str(p_name.Value)
print str(p_type.Value) + " -- " + str(p_mgr.Value)
print str(start.Value) + " - " + str(finish.Value)
projects = []
for i in range(25, 28):
bldg = excel.Cells(i,1)
zone = excel.Cells(i,2)
p_num = excel.Cells(i,3)
p_name = excel.Cells(i,4)
p_mgr = excel.Cells(i,5)
const_mgr = excel.Cells(i,6)
ehs_lias = excel.Cells(i,7)
ehs_const = excel.Cells(i,8)
status = excel.Cells(i,9)
p_type = excel.Cells(i,10)
start = excel.Cells(i,11)
finish = excel.Cells(i,12)
projects.append(Project(bldg, zone, p_num, p_name, p_mgr,
const_mgr, ehs_lias, ehs_const,
status, p_type, start, finish))
projects[0].quickPrint()
projects[1].quickPrint()
projects[2].quickPrint()
I think you have defined quickPrint incorrectly. As far as it is concerned, p_name, p_type, p_mgr, etc. are not defined, so it looks further up the scope resolution tree, or whatever it's called, and then eventually finds them - where you last defined them in the for loop, which is why it gives you the last value.
Because you have used the same variable names in your loop, you are hiding this issue, and making it more confusing.
def quickPrint(self):
""" prints quick glance projects details """
if self.p_name is None:
pass
else:
print 'Building ' + str(self.bldg.Value)
print str(self.p_name.Value)
print str(self.p_type.Value) + " -- " + str(self.p_mgr.Value)
print str(self.start.Value) + " - " + str(self.finish.Value)
example:
class Project(object):
def __init__(self, argument):
self.argument = argument
def __repr__(self):
return str(argument)
projects = []
for i in range(10):
argument = i
projects.append(Project(argument))
print projects
this outputs [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
changing the __repr__(self): definition to have self.argument fixes it.
Related
I have a program that I want to be able to print all of the instances of each variable using my method that I created. Problem is I can't figure out a way to print them since each are listed under a different variable that aren't configured from hardcoding them in and I need a way to automatically recall them in my code.
class fit:
def __init__(self,day,did,workout='Not Recorded',time='An unknown amount of',calories='An unknown amount of'):
self.day = day
self.did = did
if did.lower()=='no':
self.workout = 'Not Recorded'
self.time = "An unknown amount of Minutes"
self.calories = "An unknown amount of Calories"
else:
self.workout = workout
self.time = "{} Minutes".format(time)
self.calories = "{} Calories".format(calories)
def formate(self):
self.formate = "{}:\n\nDid you work out: {}\nWorkout focus: {}\nYou worked out for: {}\nYou burned: {}\n\n----------------------------------------------------------".format(self.day,self.did,self.workout,self.time,self.calories)
return self.formate
def reader(day,index):
file = open('readme.txt')
file = file.read()
stripped = file.rsplit("\n")
for i in range(len(stripped)):
stripped[i] = stripped[i].rsplit(" ")
del stripped[-1]
if int(index) >= len(stripped[day-1]):
return "none"
else:
return stripped[day-1][index]
x = 0
def create_new_instance(class_name,instance_name):
globals()[instance_name] = class_name(reader(x,0),reader(x,1),reader(x,2),reader(x,3),reader(x,4))
print('Class instance {} created'.format(instance_name))
while True:
try:
x+=1
ins = 'day_' + str(x)
create_new_instance(fit,ins)
except:
break
break
def printer(instance):
print(.formate())
while True:
x+=1
inst = 'day_' + str(x)
printer(inst)
An example of this might be that I have 8 lines of data from a text document and I have a system that creates instances of day_1, day_2, day_3 ect until day_8 and then I want to print each of those instances out, but again I don't have those instances directly hardcoded into my code so I don't know how I'd do it. I've tried looking into maybe a while loop and increasing a variable by 1 and concatenating it with day and trying to make a variable out of that but the my limited experience with python isn't helping.
A very unpythonic and ugly way would be to use exec, for example:
day_3=5
x = 'day_'+'3'
exec("print("+x+")")
I would recommend another way to store your variables though.
So I'm writing a script to keep track of my correspondence.
It takes the name of someone who emailed me, looks for its associated 'Friend object' in a shelved dictionary of Friend instances (or creates itself a new instance and stores it in the dictionary), then appends the current time to that instance's list of timestamps.
So for example, if the script runs with 'John Citizen' as the input, it finds the key 'John Citizen' in the dictionary, gets the instance associated with that key, goes to that instance's list and appends to that list a timestamp.
This is all working as I want it to, except for the appending.
Here's the friend object:
class Friend():
def __init__(self, name):
self.name = name
self.timestamps = []
def add_timestamp(self, timestamp):
self.timestamps.append(timestamp)
Here's the global function that processes the input into a name string. (The input comes from AppleScript and is always in this format:
Input: "First [Middles] Last <emailaddress#email.com>"
def process_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("sender", help="The output from AppleScript")
args = parser.parse_args()
full_name = args.sender
## gets just the name
words = full_name.split(' ')
words.pop()
full_name = ' '.join(words)
## takes away all the non alpha characters, newlines etc.
alpha_characters = []
for character in full_name:
if character.isalpha() or character == " ":
alpha_characters.append(character)
full_name = ''.join(alpha_characters)
return full_name
And then here's the script handling that full_name string.
## Get the timestamp and name
now = datetime.datetime.now()
full_name = process_arguments()
## open the shelf to store and access all the friend information
shelf = shelve.open('/Users/Perrin/Library/Scripts/friend_shelf.db')
if full_name in shelf:
shelf[full_name].add_timestamp(now)
shelf.close
else:
shelf[full_name] = Friend(full_name)
shelf.close
I've tried to debug it and full_name in shelf evaluates to True and the problem is still happening.
I just can't seem to get that self.timestamps list to populate. I would really appreciate any help!
You need to extract, mutate and store the object back in the shelf to persist it.
e.g
# extract
this_friend = shelf[full_name]
# mutate
this_friend.add_timestamp(now)
# re-add
shelf[full_name] = this_friend
shelf.close()
You can see an example of this in the python docs.
The other option is pass the writeback parameter as True to shelve.open and it will allow you to write to the keys directly.
#paulrooney answered this.
Objects in the shelf need to be removed from the shelf, assigned to a name, mutated (to add time stamp), then put back in the shelf. This code works fine.
shelf = shelve.open('/Users/Perrin/Library/Scripts/friend_shelf.db')
if full_name in shelf:
this_friend = shelf[full_name]
this_friend.add_timestamp(now)
shelf[full_name] = this_friend
print shelf[full_name].timestamps
shelf.close
else:
shelf[full_name] = Friend(full_name)
this_friend = shelf[full_name]
this_friend.add_timestamp(now)
shelf[full_name] = this_friend
shelf.close
This is concatenated to the question I asked earlier today ("List" Object Not Callable, Syntax Error for Text-Based RPG). Now my dilemma resides in adding the herb to the player's herb list.
self.herb = []
is the starting herb list. The function collectPlants:
def collectPlants(self):
if self.state == 'normal':
print"%s spends an hour looking for medicinal plants." % self.name
if random.choice([0,1]):
foundHerb = random.choice(herb_dict)
print "You find some %s." % foundHerb[0]
self.herb.append(foundHerb)
print foundHerb
else: print"%s doesn't find anything useful." % self.name
with foundHerb being the random choice. How do I add this item to the list in a neat way (currently it prints the herb name, then "None") and allow for having several of the same herb?
Here's the herb class:
class herb:
def __init__(self, name, effect):
self.name = name
self.effect = effect
Sample list of herbs (warning: immaturity):
herb_dict = [
("Aloe Vera", Player().health = Player().health + 2),
("Cannabis", Player().state = 'high'),
("Ergot", Player().state = 'tripping')
]
Use a list.
self.herb = []
foundHerb = 'something'
self.herb.append(foundHerb)
self.herb.append('another thing')
self.herb.append('more stuff')
print 'You have: ' + ', '.join(self.herb)
# You have: something, another thing, more stuff
EDIT: I found the code from which you get foundHerb in one of your other questions (please post it in this question too!), which is:
foundHerb = random.choice(herb_dict)
When I look at herb_dict:
herb_dict = [
("Aloe Vera", Player().health == Player().health + 2),
("Cannabis", Player().state == 'high'),
("Ergot", Player().state == 'tripping')
]
This is wrong, use = for assignment. == is for testing equality.
You need to use a function in the second item in these tuples.
Don't add the second item into the list. Like this:
self.herb.append(foundHerb[0])
In your function, think what would happen if random.choice([0,1]) was 0. it would not run the if block, so no herb would ever be chosen. Perhaps in your function, you can return False to say that no herb was found. Then you can do this:
self.herb = []
myherb = collectPlants() # This will either contain a herb or False
if myherb: # If myherb is a plant (and it isn't False)
self.herb.append(myherb)
I'm trying to keep this as simple as possible. Basically I want the data to be saved to a file, and the retrieve it so that questor.py works and can "remember" everything it was ever taught on your machine. The original code is available on the web at http://www.strout.net/info/coding/python/questor.py
If I'm reading the code right, you end up with an object that looks something like {key:{key:{key:class instance},class instance},class instance} . (rough estimate)
Please ignore the unfished method Save, I'm working on that as soon as I figure out how to pickle the dictionary without losing any of the imbedded instances.
The following is my attempt at trying to save the dict via pickler. Some of the code is unfinished, but you should be able to get an idea of what I was trying to do. So far all I am able to do is retrieve the last question/answer set. Either my pickle isn't saving the imbedded instances, or they're not actually there when I save the pickle. I've followed the spaghetti lines as much as possible, but can't seem to figure out how to set up a way to save to file without losing anything.
Also my file doesn't have to be .txt originally I was going to use .data for the pickle.
# questor.py
# define some constants for future use
kQuestion = 'question'
kGuess = 'guess'
questfile = 'questfile.txt'
## Added
import cPickle as p
# create a file for questor
def questor_file():
try:
questor = open(questfile,'rb')
try:
q = p.Unpickler(questor)
quest = q.load()
questor.close()
return quest
except:
print 'P.load failed'
except:
print 'File did not open'
questor = open('questfile.data', 'wb')
questor.close()
return Qnode('python')
# define a function for asking yes/no questions
def yesno(prompt):
ans = raw_input(prompt)
return (ans[0]=='y' or ans[0]=='Y')
# define a node in the question tree (either question or guess)
class Qnode:
# initialization method
def __init__(self,guess):
self.nodetype = kGuess
self.desc = guess
##Added
## Not sure where I found this, but was going to attempt to use this as a retreival method
## haven't gotten this to work yet
def Load(self):
f = open(self.questfile,'rb')
tmp_dict = cPickle.load(f)
f.close()
self.__dict__.update(tmp_dict)
##Added
# was going to use this as a save method, and call it each time I added a new question/answer
def Save(self,node):
f = open(self.questfile,'wb')
quest = p.pickler(f)
# get the question to ask
def query(self):
if (self.nodetype == kQuestion):
return self.desc + " "
elif (self.nodetype == kGuess):
return "Is it a " + self.desc + "? "
else:
return "Error: invalid node type!"
# return new node, given a boolean response
def nextnode(self,answer):
return self.nodes[answer]
# turn a guess node into a question node and add new item
# give a question, the new item, and the answer for that item
def makeQuest( self, question, newitem, newanswer ):
# create new nodes for the new answer and old answer
newAnsNode = (Qnode(newitem))
oldAnsNode = (Qnode(self.desc))
# turn this node into a question node
self.nodetype = kQuestion
self.desc = question
# assign the yes and no nodes appropriately
self.nodes = {newanswer:newAnsNode, not newanswer:oldAnsNode}
self.save(self.nodes)
def traverse(fromNode):
# ask the question
yes = yesno( fromNode.query() )
# if this is a guess node, then did we get it right?
if (fromNode.nodetype == kGuess):
if (yes):
print "I'm a genius!!!"
return
# if we didn't get it right, return the node
return fromNode
# if it's a question node, then ask another question
return traverse( fromNode.nextnode(yes) )
def run():
# start with a single guess node
# This was supposed to assign the data from the file
topNode = questor_file()
done = 0
while not done:
# ask questions till we get to the end
result = traverse( topNode )
# if result is a node, we need to add a question
if (result):
item = raw_input("OK, what were you thinking of? ")
print "Enter a question that distinguishes a",
print item, "from a", result.desc + ":"
q = raw_input()
ans = yesno("What is the answer for " + item + "? ")
result.makeQuest( q, item, ans )
print "Got it."
# repeat until done
print
done = not yesno("Do another? ")
# Added
# give me the dictionary
return result
# immediate-mode commands, for drag-and-drop or execfile() execution
if __name__ == '__main__':
print "Let's play a game."
print 'Think of something, just one thing.'
print 'It can be anything, and I will try to guess what it is.'
raw_input('Press Enter when ready.')
print
questdata = run()
print
# Added
# Save the dictionary
questor = open(questfile,'wb')
q = p.Pickler(questor)
q.dump(questdata)
questor.close()
raw_input("press Return>")
else:
print "Module questor imported."
print "To run, type: questor.run()"
print "To reload after changes to the source, type: reload(questor)"
# end of questor.py
one way that comes to mind is creating a list of all the nodes and saving that ... they should keep their internal pointers on their own.
declare a list of nodes at the top of your file (and use pickle... just cause Im more familiar with that)
import pickle
kQuestion = 'question'
kGuess = 'guess'
questfile = 'questfile.txt'
nodes = []
....
change your load method to something like
def questor_file():
global nodes
try:
questor = open(questfile,'rb')
try:
nodes= pickle.load(questor)
quest = nodes[0]
questor.close()
return quest
except:
print 'P.load failed'
nodes = []
except:
print 'File did not open'
nodes = []
return Qnode('python')
change your class constructor so that it adds each node to nodes
class Qnode:
# initialization method
def __init__(self,guess):
self.nodetype = kGuess
self.desc = guess
nodes.append(self)
at the end where it says #added save dictionary , save your list of nodes
questor = open(questfile,'wb')
q = pickle.dump(nodes,questor)
make sure you exit the program by typing no when prompted ...
you could also save it to a database or whatever but you would still have to store each node and it might be more complicated... this method should really be fine I think , (although there may be a more natural way to save a tree structure) ...
Question: How do I kill an instantiation or insure i'm creating a new instantiation of the python universal feedparser?
Info:
I'm working on a program right now that downloads and catalogs large numbers of blogs. It has worked well so for except for an unfortunate bug. My code is set up to take a list of blog urls and run them through a for loop. each run it picks a url and sends it down to a separate class which manages the downloading, extracting, and saving of the data to a file.
The first url works just fine. It downloads the entirety of the blog and saves it to a file. But the second blog it downloads will have all the data from the first one as well, I'm totally clueless as to why.
Code snippets:
class BlogHarvester:
def __init__(self,folder):
f = open(folder,'r')
stop = folder[len(folder)-1]
while stop != '/':
folder = folder[0:len(folder)-1]
stop = folder[len(folder)-1]
blogs = []
for line in f:
blogs.append(line)
for herf in blogs:
blog = BlogParser(herf)
sPath = ""
uid = newguid()##returns random hash.
sPath = uid
sPath = sPath + " - " + blog.posts[0].author[1:5] + ".blog"
print sPath
blog.storeAsFile(sPath)
class BlogParser:
def __init__(self, blogherf='null', path='null', posts = []):
self.blogherf = blogherf
self.blog = feedparser.parse(blogherf)
self.path = path
self.posts = posts
if blogherf != 'null':
self.makeList()
elif path != 'null':
self.loadFromFile()
class BlogPeices:
def __init__(self,title,author,post,date,publisher,rights,comments):
self.author = author
self.title = title
self.post = post
self.date = date
self.publisher = publisher
self.rights = rights
self.comments = comments
I included snippets I figured that would probably be useful. Sorry if there are any confusing artifacts. This program has been a pain in the butt.
The problem is posts=[]. Default arguments are calculated at compile time, not runtime, so mutations to the object remain for the lifetime of the class. Instead use posts=None and test:
if posts is None:
self.posts = []
As what Ignacio said, any mutations that happen to the default arguments in the function list will stay for the life of the class.
From http://docs.python.org/reference/compound_stmts.html#function-definitions
Default parameter values are evaluated
when the function definition is
executed. This means that the
expression is evaluated once, when the
function is defined, and that that
same “pre-computed” value is used for
each call. This is especially
important to understand when a default
parameter is a mutable object, such as
a list or a dictionary: if the
function modifies the object (e.g. by
appending an item to a list), the
default value is in effect modified.
This is generally not what was
intended. A way around this is to use
None as the default, and explicitly
test for it in the body of the
function.
But this brings up sort of a gotcha, you are modifying a reference... So you may be modifying a list that the consumer of the class that wasn't expected to be modified:
For example:
class A:
def foo(self, x = [] ):
x.append(1)
self.x = x
a = A()
a.foo()
print a.x
# prints: [1]
a.foo()
print a.x
# prints: [1,1] # !!!! Consumer would expect this to be [1]
y = [1,2,3]
a.foo(y)
print a.x
# prints: [1, 2, 3, 1]
print y
# prints: [1, 2, 3, 1] # !!!! My list was modified
If you were to copy it instead: (See http://docs.python.org/library/copy.html )
import copy
class A:
def foo(self, x = [] ):
x = copy.copy(x)
x.append(1)
self.x = x
a = A()
a.foo()
print a.x
# prints: [1]
a.foo()
print a.x
# prints: [1] # !!! Much better =)
y = [1,2,3]
a.foo(y)
print a.x
# prints: [1, 2, 3, 1]
print y
# prints: [1, 2, 3] # !!!! My list is how I made it