By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
443,994 Members | 1,166 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 443,994 IT Pros & Developers. It's quick & easy.

answers.py v0.0.1 - source

P: n/a
#!/usr/bin/python

################################################## ##
# answers.py --- A simple answer bot
# Copyright (C) 2006 Logan Lee
#
# MESSAGE:
#
# This program is a simple text-console program
#+that asks for a problem and its solution then
#+persists the problem-solution dictionary to a
#+pickled file.
#
# Feel free to do whatever you like with the code.
# If you have any comments please reply to the
#+usenet article which this source was published.
#
# I am particularly interested in your comments on
#+correct usage of programming design patterns,
#+better coding practise, or ideas about features.
#
################################################## ###

global ANSWER_PATH

###############################################
# IMPORTANT: Set ANSWER_PATH to your preference
###############################################
ANSWER_PATH=""
###############################################

global VERSION
VERSION=0.01

class Problem:
def __init__(self):self.problem=""
def set(self,problem):self.problem=problem
def get(self):return self.problem

class Solution:
def __init__(self):self.solution=""
def set(self,solution):self.solution=solution
def get(self):return self.solution

class Storage:
def add(self,problem,solution):
self.answers[problem]=solution
def save(self):
import pickle
try: pickle.dump(self.answers,file(ANSWER_PATH+".answer s","w"));print "Answers saved"
except: print "Couldn't save answers. Something went wrong"

def load(self):
import pickle
try: self.answers=pickle.load(file(ANSWER_PATH+".answer s"));print "Answers loaded"
except: print "Couldn't load answers. Something went wrong or this may be your first time running"

def getAnswer(self,problem):
try: answer=self.answers[problem]
except: print "I don't have the answer to that problem"
return answer

def __init__(self,answers={}):
self.answers=answers

class Console:
class Menu:
global storage
storage=Storage()

class Level0:
def do(self):
import sys
action=sys.exit()

class Level1:

def do(self):
problem=Problem();solution=Solution()
text=("Do you have a problem? $ ","Do you have the solution? $ ","no idea","Try again with your common sense")
commands=("answer","load(disabled, already loaded)","quit","help")
problem.set(raw_input(text[0]))

if problem.get()==commands[0]:return 4
#elif problem.get()==commands[1]:storage.load();return 1
elif problem.get()==commands[2]:return 0
elif problem.get()==commands[3]:print commands;return 1

solution.set(raw_input(text[1]))

if solution.get()==text[2]:print text[3];solution.set(raw_input(text[1]))

if raw_input("Your solution to '%s' is '%s', correct? $ " % (problem.get(),solution.get()))=="yes":storage.add (problem.get(),solution.get());print "Answer confirmed";return 2
else: return 1

class Level2:
def do(self):
if raw_input("Should I save the answer? $ ")=="yes":
storage.save()
return 3
else: return 1

class Level3:
def do(self):
text=("Do you have another problem?"+" $ ","Do you want to quit?"+" $ ")
if raw_input(text[0])=="yes":
return 1
elif raw_input(text[1])=="yes":return 0
else: return 3

class Level4:
def do(self):
text=("What answer do you seek? $ ","Do you want to ask again? $ ")
problem=raw_input(text[0]);answer=storage.getAnswer(problem)
print "Answer to '%s' is '%s'" % (problem,answer)
if raw_input(text[1])=="yes":return 4
else: return 1

def start(self):
storage.load()
level=1
while 1:
if level==0:
Console.Menu.Level0().do()
if level==1:
level=Console.Menu.Level1().do()
if level==2:
level=Console.Menu.Level2().do()
if level==3:
level=Console.Menu.Level3().do()
if level==4:
level=Console.Menu.Level4().do()

def start(self):Console.Menu().start()

if __name__=="__main__":
Console().start()
Dec 28 '06 #1
Share this Question
Share on Google+
4 Replies


P: n/a
In <87************@pyenos.pyenos.org>, Pyenos wrote:
global ANSWER_PATH
``global`` at module level has no effect. That keyword is used to declare
names in functions or methods global that would be local to the function
otherwise and it's only needed if you want to *assign* to the global name
from within a function. As this seems to be a constant it might be even
an "error" to reassign the name.
global VERSION
VERSION=0.01
Version numbers aren't really numbers, so it's better to use strings here.
class Problem:
def __init__(self):self.problem=""
def set(self,problem):self.problem=problem
def get(self):return self.problem
You like it compact right? :-)

Take a look at the style guide: http://www.python.org/dev/peps/pep-0008/

Use more spaces. Around binary operators and the ``=`` in assignments.
And newlines after the colon that introduces a body of a method or branch
in an ``if``/``else`` statement and so on.

Next thing are getters and setters: Don't use them it's just unnecessary
typing. You don't loose the flexibility to decide to replace the simple
attribute lookup by a calculated access later because Python has
"properties". Look at the docs of the `property()` function.

So the `Problem` can be simplified to:

class Problem(object):
def __init__(self, problem):
self.problem = problem

Maybe add docstrings to the classes and methods.
class Storage:
def add(self,problem,solution):
self.answers[problem]=solution
def save(self):
import pickle
try: pickle.dump(self.answers,file(ANSWER_PATH+".answer s","w"));print "Answers saved"
except: print "Couldn't save answers. Something went wrong"
Many people put ``import`` statements at the top of the module so it's
very easy to find the dependencies of the module.

Paths can be concatenated with `os.path.join()` in a platform independent
way and pickles should always be opened in binary mode. Otherwise they
won't survive transfers between operating systems in all cases.

Using the ``;`` to put more than one statement on one line is even more
uncommon than putting something after the ``:``. And the style guide
suggests not to use lines longer than 80 characters.

Don't use a bare ``except``! This catches every exception. If you
mistyped a name you get a `NameError` or `AttributeError` which will be
"replaced" by this rather meaningless text that's printed. In more
complex programs such ``excepts`` can mask really hard to find bugs.

You don't close the file which is at least a bit unclean.
def load(self):
import pickle
try: self.answers=pickle.load(file(ANSWER_PATH+".answer s"));print "Answers loaded"
except: print "Couldn't load answers. Something went wrong or this may be your first time running"
You might want to make load a `staticmethod` or a `classmethod` so you
don't need an instance to load the answers. But this is a bit more
advanced stuff.
def __init__(self,answers={}):
self.answers=answers
Default arguments are only evaluated *once* when the ``def`` is executed,
so all your `Storage` classes share the same dictionary. One idiom to
solve this is:

def __init__(self, answers=None):
self.answers = answers or dict()

I would expect the `__init__()` method to be the first method in a class
definition followed by other special double underscore methods.

Now comes a very strange and convoluted piece of code. You are "misusing"
a class as namespace for other classes and nested classes with just one
method that doesn't even use the `self` argument. That's an overly
complex way to write a function.
class Console:
class Menu:
global storage
storage=Storage()
If you want `storage` to be at module level why do you hide the binding in
that nested class?
class Level0:
def do(self):
import sys
action=sys.exit()

class Level1:

def do(self):
problem=Problem();solution=Solution()
text=("Do you have a problem? $ ","Do you have the solution? $ ","no idea","Try again with your common sense")
Why do you put all the texts into tuples so later there's a not very
descriptive `text[x]` reference and one has to search the definition at
another place?
commands=("answer","load(disabled, already
loaded)","quit","help") problem.set(raw_input(text[0]))

if problem.get()==commands[0]:return 4 #elif
problem.get()==commands[1]:storage.load();return 1 elif
problem.get()==commands[2]:return 0 elif
problem.get()==commands[3]:print commands;return 1
Here you are doing something that doesn't belong here IMHO. You put
potential commands into a `Problem` object. You are mixing the main menu
and the input of a problem/solution in one function. The menu code would
fit better in the main loop.

Damned, I trimmed to much and my newsreader doesn't let me insert that
overlong lines without reformatting. So without your code: You are
storing the *text* of a `Solution` and a `Problem`, not the objects, so
why do you have those classes at all?
def start(self):
storage.load()
level=1
while 1:
if level==0:
Console.Menu.Level0().do()
if level==1:
level=Console.Menu.Level1().do()
if level==2:
level=Console.Menu.Level2().do()
if level==3:
level=Console.Menu.Level3().do()
if level==4:
level=Console.Menu.Level4().do()
Level numbers and the function names are quite dull. It's really hard to
get an idea what happens here without reading all the code and taking
notes.

Such constructs can be written with a dictionary which maps level numbers
to functions. In this case even a list would do. Assuming the code is in
ordinary functions named `level0` to `level4`:

def dispatch():
levels = [level0, level1, level2, level3, level4]
next_level = 1
while True:
next_level = levels[next_level]()

Giving the numbers meaningful names helps readability. Or even better
give the functions better names and get rid of the numbers by returning
the next to call function object directly.

Then the `dispatch()` is just:

def start_func():
...
if some_condition:
return spam_func
else:
return eggs_func

def spam_func():
...

....

def dispatch():
func = start_func
while True:
func =*func()

Ciao,
Marc 'BlackJack' Rintsch
Dec 28 '06 #2

P: n/a
Marc 'BlackJack' Rintsch wrote:
*****def*__init__(self,answers={}):
*********self.answers=answers

Default arguments are only evaluated once when the ``def`` is executed,
so all your `Storage` classes share the same dictionary.**One*idiom*to
solve this is:

****def*__init__(self,*answers=None):
********self.answers*=*answers*or*dict()
Nitpick: the expression 'answers or dict()' is false economy. It may provoke
even subtler errors, because an empty dict is False in a boolean context.
Example:

# here the answers are shared:
shared_answers = {"question": "answer"}
first = Storage(shared_answers)
second = Storage(shared_answers)
print second.answers is first.answers # True

# here the answers aren't shared:"
shared_answers = {}
first = Storage(shared_answers)
second = Storage(shared_answers)
shared_answers["question"] = "answer"
print second.answers is first.answers # False

To avoid the confusion I recommend the following:

def __init__(self, answers=None):
if answers is None:
answers = {}
self.answers = answers

Peter
Dec 28 '06 #3

P: n/a
Marc 'BlackJack' Rintsch:

You give lot of comments and I agree with most of them.
One idiom to solve this is:
def __init__(self, answers=None):
self.answers = answers or dict()
I suggest to avoid such usage of or/and (expecially for newbies), that
is triky and can produce bugs, and to use a more explicit if:

def __init__(self, answers=None):
if answers is None:
self.answers = {}
else:
self.answers = answers

If you are sure you can use 2.5+ (the enclosing of the if is optional,
but helps readability):

def __init__(self, answers=None):
self.answers = ({} if answers is None else answers)

Bye,
bearophile

Dec 28 '06 #4

P: n/a
#!/usr/bin/python

################################################## ##############
# answers.py --- A simple answer-bot.
# Copyright 2006 Logan Lee
#
# RELEASE NOTES:
#
# - I have fixed an error where the program crashes on query of
#+unknown string to answers dictionary.
# - Rephrased 'Do you have a problem?' message in menu to
#+"What is your problem?"
#
# MESSAGE:
#
# Now the program is perfectly usable to an extent that was
#+intended! Previous advice on the code v0.01 is by in large
#+not implemented in this version, but I will learn from it.
#+Thanks.
#
################################################## ##############

################################################## ##############
# IMPORTANT: DON'T FORGET TO SET ANSWER_PATH TO YOUR PREFERENCE!
global ANSWER_PATH
ANSWER_PATH=""
################################################## ##############

VERSION="0.01001"

class Problem:
def __init__(self):self.problem=""
def set(self,problem):self.problem=problem
def get(self):return self.problem

class Solution:
def __init__(self):self.solution=""
def set(self,solution):self.solution=solution
def get(self):return self.solution

class Storage:
def add(self,problem,solution):
self.answers[problem]=solution
def save(self):
import pickle
try: pickle.dump(self.answers,file(ANSWER_PATH+".answer s","w"));print "Answers saved"
except: print "Couldn't save answers. Something went wrong"

def load(self):
import pickle
try: self.answers=pickle.load(file(ANSWER_PATH+".answer s"));print "Answers loaded"
except: print "Couldn't load answers. Something went wrong or this may be your first time running"

def getAnswer(self,problem):
answer=""
try: answer=self.answers[problem]
except: print "I don't have the answer to that problem"
return answer

def __init__(self,answers={}):
self.answers=answers

class Console:
class Menu:
global storage
storage=Storage()

class Level0:
def do(self):
import sys
action=sys.exit()

class Level1:

def do(self):
problem=Problem();solution=Solution()
text=("What is your problem? $ ","Do you have the solution? $ ","no idea","Try again with your common sense")
commands=("answer","load(disabled, already loaded)","quit","help")
problem.set(raw_input(text[0]))

if problem.get()==commands[0]:return 4
#elif problem.get()==commands[1]:storage.load();return 1
elif problem.get()==commands[2]:return 0
elif problem.get()==commands[3]:print commands;return 1

solution.set(raw_input(text[1]))

if solution.get()==text[2]:print text[3];solution.set(raw_input(text[1]))

if raw_input("Your solution to '%s' is '%s', correct? $ " % (problem.get(),solution.get()))=="yes":storage.add (problem.get(),solution.get());print "Answer confirmed";return 2
else: return 1

class Level2:
def do(self):
if raw_input("Should I save the answer? $ ")=="yes":
storage.save()
return 3
else: return 1

class Level3:
def do(self):
text=("Do you have another problem?"+" $ ","Do you want to quit?"+" $ ")
if raw_input(text[0])=="yes":
return 1
elif raw_input(text[1])=="yes":return 0
else: return 3

class Level4:
def do(self):
text=("What answer do you seek? $ ","Do you want to ask again? $ ")
problem=raw_input(text[0]);answer=storage.getAnswer(problem)
print "Answer to '%s' is '%s'" % (problem,answer)
if raw_input(text[1])=="yes":return 4
else: return 1

def start(self):
storage.load()
level=1
while 1:
if level==0:
Console.Menu.Level0().do()
if level==1:
level=Console.Menu.Level1().do()
if level==2:
level=Console.Menu.Level2().do()
if level==3:
level=Console.Menu.Level3().do()
if level==4:
level=Console.Menu.Level4().do()

def start(self):Console.Menu().start()

if __name__=="__main__":
Console().start()

--
Logan Lee aka Pyenos
Dec 28 '06 #5

This discussion thread is closed

Replies have been disabled for this discussion.