Ah what the heck, here goes my explanation:
| Babushka (grandma) matryoshka dolls |
See the following function declarations:
def wrap(f) # Add before behaviour print "start wrap" # Do something with f # Add after behaviour print "end wrap" return f # Pass f to the next label or babushka/function @wrap def fun(): pass # do something interesting...
The previous rows are equivalent to
fun = wrap(fun)
The wrapping function (wrap) must return a final function to relabel the newly decorated function with the original unwrapped label, which is fun in our example.
You could also decorate more than once:
@huge @medium @small def core() pass
Which is equivalent to the following:
core = huge(medium(small(core)))In this example, one must also keep passing the decorated function to the next decorator with a return statement.
This year I decided to improve my mental arithmetic skills, so I wrote a python application to train on. I was also quite curious as to what the whole python decorator thing was about. I combined my curiosity for python decorators and the desire for a mental arithmetic trainer. The following script is the (partial) end result.
def timeit(fun):
from time import time
def timed(*args):
ts = time()
mistakes, op_symbol = fun(*args)
te = time()
time_elapsed = te - ts
print "Time elapsed:", time_elapsed
return time_elapsed, mistakes, op_symbol
return timed
class Query(object):
def __init__(self, op_symbol):
self.op_symbol = op_symbol
def __call__(self, f):
# NOTE: you can count the function arguments later to try on a n-ary functions
def wrapped_binary_f(*args):
a,b = tuple(args)
#print "%s %s %s = ?" % (a, self.op_symbol, b) # infix notation
print a, self.op_symbol, b, " = ?"
correct_answer = f(a,b)
user_answer = None
mistakes = 0
while not user_answer or user_answer != correct_answer:
user_answer = None
try:
user_answer = input("answer: ")
if user_answer != correct_answer:
mistakes+=1
print "Please try again... %s" % str(mistakes)
else:
print "Correct."
break
except SyntaxError:
mistakes+=1
print "Please try again... %s" % str(mistakes)
except NameError:
mistakes+=1
print "Please try again... %s" % str(mistakes)
return mistakes, self.op_symbol
return wrapped_binary_f
@timeit
@Query('*')
def multiply(a, b):
return a * b
@timeit
@Query('-')
def subtract(a, b):
return a - b
@timeit
@Query('+')
def add(a, b):
return a + bThis example demonstrates three techniques, namely:
- Passing arguments to a decorator function (Query class's __init__ function);
- Using multiple decorator functions on multiple functions (timeit, Query);
- Manipulating arguments passed to the decorator function and the called functions (multiply, subtract, add) and mixing them up
One can also add arguments to the decorator function by way of a class declaration. First the __init__ function is called to create the object of the class arguments passed, the arguments are stored in the object, then the object is called via the __call__ function. The __call__ function can access the arguments from the members of the object.
I have included the complete application here, try it out, improve it, enjoy!
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
'''
Command-line interactive script to train mental arithmetic,
also a demonstration of python decorators.
Created on May 22, 2010 and refactored several times after...
'''
def timeit(fun):
from time import time
def timed(*args):
ts = time()
mistakes, op_symbol = fun(*args)
te = time()
time_elapsed = te - ts
print "Time elapsed:", time_elapsed
return time_elapsed, mistakes, op_symbol
return timed
class Query(object):
def __init__(self, op_symbol):
self.op_symbol = op_symbol
def __call__(self, f):
# NOTE: you can count the function arguments later to try on a n-ary function
def wrapped_binary_f(*args):
a,b = tuple(args)
#print "%s %s %s = ?" % (a, self.op_symbol, b) # infix notation
print a, self.op_symbol, b, " = ?"
correct_answer = f(a,b)
user_answer = None
mistakes = 0
while not user_answer or user_answer != correct_answer:
user_answer = None
try:
user_answer = input("answer: ")
if user_answer != correct_answer:
mistakes+=1
print "Please try again... %s" % str(mistakes)
else:
print "Correct."
break
except SyntaxError:
mistakes+=1
print "Please try again... %s" % str(mistakes)
except NameError:
mistakes+=1
print "Please try again... %s" % str(mistakes)
return mistakes, self.op_symbol
return wrapped_binary_f
@timeit
@Query('*')
def multiply(a, b):
return a * b
@timeit
@Query('-')
def subtract(a, b):
return a - b
@timeit
@Query('+')
def add(a, b):
return a + b
@timeit
@Query('/')
def divide(a, b):
return float(a) / float(b)
# from decimal import Decimal, getcontext
# from fractions import Fraction # check this shit out, useful if user enters fraction and computer calculates float or Decimal... then check if the same
# getcontext().prec=2
# return Decimal(a) / Decimal(b)
def generate_samples_naive(maxdecimals, nsamples):
from random import choice
return zip([ choice(xrange(10**maxdecimals)) for _i in xrange(nsamples) ],
[ choice(xrange(10**maxdecimals)) for _j in xrange(nsamples) ])
def generate_samples_positive(maxdecimals, nsamples):
"""
The lambda here swaps the values in case subtraction results are negative
"""
from random import choice
return map (lambda (x,y): (x,y) if x>=y else (y,x), \
[ (choice(xrange(10**maxdecimals)), choice(xrange(10**maxdecimals))) \
for _i in xrange(nsamples) ])
def get_stats(li):
"""
Tuple of total time spent solving the problems
and total number of mistakes
"""
return sum([ t for t,_,_ in li ]), \
sum([m for _,m,_ in li])
def get_detailed_stats(li):
"""
Total number of mistakes per operator type,
total number of time spent per operator type
"""
d = {}
for op_symbol in ['*','-','+','/']:
d[op_symbol] = (sum([ t for t,_,o in li if o == op_symbol ]),
sum([ m for _,m,o in li if o == op_symbol ]))
return d
def show_stats(results):
(total_time, total_mistakes) = get_stats(results)
print "Total solved:", len(results)
print "Total time elapsed:", total_time
print "Total mistakes made:", total_mistakes
print "Detailed results per operator (time, mistakes):" #, get_detailed_stats(results)
d = get_detailed_stats(results)
for k in d.keys():
(time, mistakes) = d[k]
print k, "%.2f" % (float(time)/total_time), "%.2f" % (float(mistakes)/total_mistakes)
print "Done!"
def main(av):
from optparse import OptionParser
usage = "usage: %prog [options] arg"
parser = OptionParser(usage)
# parser.add_option...
# parser.add_option("-m", "--max-tries", dest='maxtries', type='int', default=3,
# help='Number of tries before going to the next example')
parser.add_option("-n", "--nr-samples", dest="nsamples", type="int", default=10,
help="Per arithmetic operation train on this number of SAMPLES", metavar="SAMPLES")
# parser.add_option("--mind", "--min-decimals", dest="maxdecimals", type="int", default=0,
# help="Define the lowest number to train expressed as the number of DECIMALS", metavar="DECIMALS")
parser.add_option("--maxd", "--max-decimals", dest="maxdecimals", type="int", default=2,
help="Define the largest number to train expressed as the number of DECIMALS", metavar="DECIMALS")
# parser.add_option("-s", "--no-stats", dest="sendstats", action="store_false", default=True,
# help="Don't send any statistical data to servers, over the internet.")
# parser.add_option("-a", "--anonymous", dest="anonymous", action="store_true", default=False,
# help="Don't send credentials or any data revealing user's identity over the internet")
parser.add_option("--nd", "--no-division", dest="train_division", action="store_false", default=False,
help="Don't train user on division")
parser.add_option("--na", "--no-addition", dest="train_addition", action="store_false", default=True,
help="Don't train user on division")
parser.add_option("--ns", "--no-subtraction", dest="train_subtraction", action="store_false", default=True,
help="Don't train user on subtraction")
parser.add_option("--nm", "--no-multiplication", dest="train_multiplication", action="store_false", default=True,
help="Don't train user on multiplication")
parser.add_option("--nh", "--no-history", dest="store_history", action="store_false", default=True,
help="Don't store user history, mind you, the same training samples might be repeated very often")
parser.add_option("-o", "--ordered-training", dest="ordered", action="store_false", default=True,
help="Train in this order: addition, subtraction, multiplication, division")
parser.add_option("-e", "--e-mail-addr", dest="email_addr", type="string", default="anon@anon.org",
help="This script will modify itself to reflect the e-mail ADDRESS of the user in question", metavar="ADDRESS")
parser.add_option("-p","--positive-only", dest="positive_only", action="store_true", default=False,
help="The result from the operation on a pair of samples will only be POSITIVE.", metavar="POSITIVE")
(options, _args) = parser.parse_args(av)
if options.positive_only:
samples = generate_samples_positive(options.maxdecimals, options.nsamples)
else:
samples = generate_samples_naive(options.maxdecimals, options.nsamples)
results = []
if options.ordered:
if options.train_subtraction:
for (a,b) in samples:
results.append(subtract(a,b))
if options.train_addition:
for (a,b) in samples:
results.append(add(a,b))
if options.train_multiplication:
for (a,b) in samples:
results.append(multiply(a,b))
if options.train_division:
for (a,b) in samples:
results.append(divide(a,b))
else:
op_options = []
if options.train_addition:
op_options.append(add)
if options.train_multiplication:
op_options.append(multiply)
if options.train_subtraction:
op_options.append(subtract)
if options.train_divison:
op_options.append(divide)
fns = op_options*options.nsamples
import random
random.shuffle(fns)
for (nr,(a,b)) in enumerate(3*samples): # TODO - come division, change to four
results.append(fns[nr](a,b))
show_stats(results)
if __name__ == '__main__':
from sys import argv as av
main(av)
Update 30-07-2010: I just realized that you could grab this code and put a gui on top quite easily, by replacing the Query class with something with a gui. Done!
No comments:
Post a Comment
Please help to keep this blog clean. Don't litter with spam.