mentby.com
Blog | Jobs | Help | Signup | Login

dict vs several variables?



I'm building a program that uses one of my own modules for a bunch of
formula defs and another module for the tkinter GUI stuff. There are
half a dozen input variables and about the same in calculated variables.
Is it better/cleaner to just build a global dict and have everything go
into it or pass multiple arguments to each function and have it return
the calculated value?

Thanks!

Leam


Leam Hall Fri, 17 Feb 2012 01:48:27 -0800

The latter. It makes the dependencies explicit to a reader of the function,
it simplifies unit tests, allows it to reuse functions in a different
context, and it is more likely to work in a multi-threaded environment.


Peter Otten Fri, 17 Feb 2012 04:13:53 -0800

Thanks Peter!

My concern with variables is that they have to be passed in specific
order to the function, and they may have to have their type set
multiple times so that you can perform the right functions on them. In
a dict you could set it on insert and not have to worry about it.

Thanks!

Leam

--
Mind on a Mission < http://leamhall.blogspot.com/> >


Leam Hall Fri, 17 Feb 2012 05:06:49 -0800

There is **kwargs which is behaves like a dict. This is very powerful
but comes with the drawback that you have to make sure you get all the
variables you need for your function to work properly.

I found [1] that is a decent explanation of how these work. Other
might have better explanations.

Greets
Sander
[1]  http://basicpython.com/understanding-arguments-args-and-kwar[..]


Sander Sweers Fri, 17 Feb 2012 05:35:54 -0800

Please don't top-post.  Put your remarks after the parts you're quoting.

You're asking for best-practice, presumably as part of your transition
from small snippets to larger scale.  First habit that beginners have to
break is the tendency to keep things in globals.  Can it make things
"easier"?  Yes, until you try to return to that code and make changes,
and discover that one change affects an unknown number of other parts.  
Yes, until you decide you want to use one of those functions in a
multithreaded environment.  Yes, until somebody else has to use your code.

Variable can't get their type set.  They are bound at any moment to an
object, and that object has a type.  Period.

Passed in a specific order?  Of course.  Very few functions, even binary
ones, are commutative on their arguments.  Do you expect
     divide(7, 42) to give the same answer as  divide(42, 7) ?
You can also use keyword arguments to help disambiguate the order of
arguments.  That's especially useful when some arguments are optional.

Real question is whether some (seldom all) of those variables are in
fact part of a larger concept.  If so, it makes sense to define a class
for them, and pass around objects of that class.  Notice it's not
global, it's still passed as an argument.  This can reduce your
parameters from 20 to maybe 6.  But make sure that the things the class
represents are really related.

Dictionaries are a built-in collection class, as are lists, sets, and
tuples.  But you can write your own.  An example of needing a class
might be to hold the coordinates of a point in space.  You make a
Location class, instantiate it with three arguments, and use that
instance for functions like
    move_ship(ship, newlocation)

There are times to have arbitrary lists or dictionaries to pass into a
function, and Python has added syntax (*args and **kwargs) to make that
convenient. But  the times that is needed are very specialized.

--

DaveA


Dave Angel Fri, 17 Feb 2012 05:47:09 -0800

Yes, unless you use keywords. You can invoke

def div(x, y):
   return x // y

a = div(3, 2)
b = div(y=3, x=2)
assert a == b

I have no idea what you mean by "have their type set". Can you give an
example?

Instead you'll have to worry about the contents of the dict which I suspect
will be even harder to verify in a non-trivial script.


Peter Otten Fri, 17 Feb 2012 05:54:46 -0800

Understood. In this case, the first half dozen variables are input and
the rest are derived from the first ones. A class might make sense and
though I understand them a little, not enough to make a good judgement
on it.

The task is to take parameters for a scuba dive; depth, gas mix, time,
air consumption rate, and compute the O2 load, gas required, etc.

Leam

--
Mind on a Mission < http://leamhall.blogspot.com/> >


Leam Hall Fri, 17 Feb 2012 06:08:08 -0800

Peter,

The variables input seem to be assumed to be strings and I need them
to be an integer or a float most of the time. Doing simple math on
them.

Thanks!

Leam

--
Mind on a Mission < http://leamhall.blogspot.com/> >


Leam Hall Fri, 17 Feb 2012 06:12:00 -0800

There are two ways to think of a class.  One is to hold various related
data, and the other is to do operations on that data.  If you just
consider the first, then you could use a class like a dictionary whose
keys are fixed (known at "compile time").

class MyDiver(object):
     def __init__(self, depth, gasmix, time, rate):
         self.depth = int(depth)
         self.gasmix = int(gasmix)
         self.time = datetime.datetime(time)
         self.rate  = float(rate)

Now if you want to use one such:

     sam = MyDiver(200, 20, "04/14/2011",  "3.7")
     bill = MyDiver(.....)

And if you want to fetch various items, you'd do something like:
    if sam.depth < bill.depth

instead of using   sam["depth"]  and bill["depth"]

just methods on the class

class MyDiver(object):
     def __init__( ... as before)

     def get_load(self):
         return self.gasmix/self.rate            (or whatever)

and used as     print sam.get_load()

that last could be simplified with a decorator

     @property
     def load(self):
         return self.gasmix/self.rate

now it's used as though it's a regular data attribute

     print sam.load

--

DaveA


Dave Angel Fri, 17 Feb 2012 06:28:50 -0800

If you are processing user input you should convert that once no matter what
the structure of your program is. Example:

#WRONG, integer conversion in many places in the code
def sum(a, b): return int(a) + int(b)
def difference(a, b): return int(a) - int(b)
a = raw_input("a ")
b = raw_input("b ")
print "a + b =", sum(a, b)
print "a - b =", difference(a, b)

#BETTER, integer conversion in a single place
def sum(a, b): return a + b
def difference(a, b): return a - b
def get_int(prompt):
    return int(raw_input(prompt))
a = get_int("a ")
b = get_int("b ")
print "a + b =", sum(a, b)
print "a - b =", difference(a, b)

The second form has the added benefit that you can easily make get_int()
more forgiving (allow the user to try again when his input is not a valid
integer) or make other changes to the code (e. g. allow floating point
input).


Peter Otten Fri, 17 Feb 2012 06:45:34 -0800

They are not assumed to be strings, they *are* strings. Users can only
type characters at the keyboard (the usual source of input). Your
program has to interpret those characters and convert to the right type.

It's good practice to do that as soon as possible which is why you often
see code like:

numVal = int( raw_input("Type a number: ") )
fltVal = float( raw_input("Type a number: ") )
strVal = raw_input("Type your name: ")

This also has the advantage that you can catch user input errors early
and request re-input. If you just store whatever the user types
("four" say) and then try to convert during the math you get an error
far too late to fix. eg. try

print pow(int("four"), 2)

You can convert them to strings for display later but usually you
don't want to, because you will use string formatting to improve
their appearance (especially with floats).

--
Alan G
Author of the Learn to Program web site http://www.alan-g.me.uk/


Alan Gauld Fri, 17 Feb 2012 10:57:57 -0800

I think a class is the way to go for a couple reasons. First, of course,
is that it pushes me to learn more. It also lets me encapsulate a lot of
the work into one thing and keep the methods there.

Thanks!

Leam


Leam Hall Fri, 17 Feb 2012 15:56:25 -0800

A global dict is like the Dark Side of the Force: easier, quicker, simpler,
but it leads to pain and anguish and great suffering.

I assume you understand why global variables should be avoided as much as
possible? E.g.

# Bad! Wrong! Do not do this!
x = ''
y = ''
z = ''

def get_user_input():
     global x, y
     x = raw_input("Please enter x: ")
     y = raw_input("Please enter y: ")

def do_calculation():
    global z
    z = "The answer is %s" % (x.upper() + y.lower())

def main()
     get_user_input()
     do_calculation()
     print z

If you're not familiar with the reasons to avoid global variables, you should
google for "Global variables considered harmful", or start here:

http://c2.com/cgi/wiki?GlobalVariablesAreBad

Well, using a single global dict is *almost* as bad, and for most of the same
reasons:

# Do not do this either.
values = {'x': '', 'y': '', 'z': ''}

def get_user_input(values):
     values['x'] = raw_input("Please enter x: ")
     values['y'] = raw_input("Please enter y: ")

def do_calculation(values):
    x = values['x']
    y = values['y']
    values['z'] = "The answer is %s" % (x.upper() + y.lower())

def main()
     get_user_input(values)
     do_calculation(values)
     print values['z']

This is a mild improvement, at least you can pass in an alternative dict if
needed, but it still suffers from failure of encapsulation (all functions that
have access to the dict have access to all variables, whether they need them
or not) and other problems.

Just about the only valid use of this pattern I can think of is for global
settings that apply application-wide. Such settings tend to be write-once,
which mitigates the disadvantages of global and pseudo-global variables.

By the way, in case it isn't obvious, changing from a dict to a instance with
named attributes values.x values.y values.z is just a cosmetic change, it
doesn't change the basic problems.

For a toy problem like the above, it might seem hardly worth the hassle of
de-globalising the functions, but for real code this really pays off in fewer
bugs and easier maintenance:

def get_user_input():
     x = raw_input("Please enter x: ")
     y = raw_input("Please enter y: ")
      return (x, y)

def do_calculation(x, y):
    return "The answer is %s" % (x.upper() + y.lower())

def main()
     a, b = get_user_input()
     result = do_calculation(a, b)
     print result

I assume the problem you are solving is more than just a toy. In that case,
passing individual variables to only the functions that need them is a better
solution. RELATED variables that MUST stay together can be collected into data
structures such as tuples, lists, dicts, or custom classes. But don't be
tempted to dump everything into one giant dict -- that's barely better than
using globals.

--
Steven


Steven D'Aprano Fri, 17 Feb 2012 20:05:02 -0800

One word of caution. If you make just one do-it-all class with a single
instance and use instance attributes as if they were global variable the
improvement over the global dictionary is minimal. You still should strive
to make dependencies explicit, e. g. prefer

def add(a, b): return a + b

over

class DoItAll:
    def add_a_and_b(self):
        self.c = self.a + self.b


Peter Otten Sat, 18 Feb 2012 00:41:51 -0800

Understood. In this case I'm starting the application and there are two
separate bits. I'm studying tkinter for the class I'm in so the GUI
stuff is one module and the formulas are in another module. The reason I
considered a global dict or a class is because all of the data bits are
directly related.

    class MyDiver(object):
        def __init__(self, depth, gasmix, time, rate):
            self.depth = int(depth)
            self.gasmix = int(gasmix)
            self.time = datetime.datetime(time)
            self.rate  = float(rate)

Which is a pretty good start, makes me think he's done scuba before.  :)

The object in question is a scuba dive. The user types in how deep they
want to go, what gas mix they are on, how long they plan on staying, and
what they think their air consumption rate will be. The formulas
calculate absolute atmospheres which are used in other formulas, O2
concentration (a risk factor), how much of the daily increased O2
exposure the dive will create, and how much gas is needed to make that
dive.

If the program grew the only thing that might be pulled out is the air
consumption rate. That would likely go into a diver object so there was
a profile for each diver. However, at the moment the app is just giving
you dive profile information so you can plan your bottom time and gas
consumption.

That's my thinking as of this morning. I need to go back and build
unittests; will do so while moving it into a class. Should be fun and
good learning!

Leam


Leam Hall Sat, 18 Feb 2012 02:25:28 -0800



Related Topics

Post a Comment