views:

269

answers:

6

Dear Stackoverflow'ers,

I am developing a medium size program in python spread across 5 modules. The program accepts command line arguments using OptionParser in the main module e.g. main.py. These options are later used to determine how methods in other modules behave (e.g. a.py, b.py). As I extend the ability for the user to customise the behaviour or the program I find that I end up requiring this user-defined parameter in a method in a.py that is not directly called by main.py, but is instead called by another method in a.py:

main.py:

 import a
 p = some_command_line_argument_value
 a.meth1(p)

a.py:

meth1(p):
       # some code
       res = meth2(p)
       # some more code w/ res

meth2(p):
       # do something with p

This excessive parameter passing seems wasteful and wrong, but has hard as I try I cannot think of a design pattern that solves this problem. While I had some formal CS education (minor in CS during my B.Sc.), I've only really come to appreciate good coding practices since I started using python. Please help me become a better programmer!

+2  A: 

If "a" is a real object and not just a set of independent helper methods, you can create an "p" member variable in "a" and set it when you instantiate an "a" object. Then your main class will not need to pass "p" into meth1 and meth2 once "a" has been instantiated.

Kaleb Brasee
+1  A: 

[Caution: my answer isn't specific to python.]

I remember that Code Complete called this kind of parameter a "tramp parameter". Googling for "tramp parameter" doesn't return many results, however.

Some alternatives to tramp parameters might include:

  • Put the data in a global variable
  • Put the data in a static variable of a class (similar to global data)
  • Put the data in an instance variable of a class
  • Pseudo-global variable: hidden behind a singleton, or some dependency injection mechanism

Personally, I don't mind a tramp parameter as long as there's no more than one; i.e. your example is OK for me, but I wouldn't like ...

import a
p1 = some_command_line_argument_value
p2 = another_command_line_argument_value
p3 = a_further_command_line_argument_value
a.meth1(p1, p2, p3)

... instead I'd prefer ...

import a
p = several_command_line_argument_values
a.meth1(p)

... because if meth2 decides that it wants more data than before, I'd prefer if it could extract this extra data from the original parameter which it's already being passed, so that I don't need to edit meth1.

ChrisW
-1: global variables should be last, not first.
S.Lott
The list is not in order of preference.
ChrisW
+7  A: 

Create objects of types relevant to your program, and store the command line options relevant to each in them. Example:

import WidgetFrobnosticator
f = WidgetFrobnosticator()
f.allow_oncave_widgets = option_allow_concave_widgets
f.respect_weasel_pins = option_respect_weasel_pins

# Now the methods of WidgetFrobnosticator have access to your command-line parameters,
# in a way that's not dependent on the input format.

import PlatypusFactory
p = PlatypusFactory()
p.allow_parthenogenesis = option_allow_parthenogenesis
p.max_population = option_max_population

# The platypus factory knows about its own options, but not those of the WidgetFrobnosticator
# or vice versa.  This makes each class easier to read and implement.
David Seiler
Hello David, I was torn between accepting yours and Steve's answer. Both answer my question, but Steve's implements the behaviour I am going for i.e. same behaviour across all instances. Only wish there was a way to accept more than one answer!
Vince
+1  A: 

With objects, parameter lists should normally be very small, since most appropriate information is a property of the object itself. The standard way to handle this is to configure the object properties and then call the appropriate methods of that object. In this case set p as an attribute of a. Your meth2 should also complain if p is not set.

ire_and_curses
+2  A: 

Maybe you should organize your code more into classes and objects? As I was writing this, Jimmy showed a class-instance based answer, so here is a pure class-based answer. This would be most useful if you only ever wanted a single behavior; if there is any chance at all you might want different defaults some of the time, you should use ordinary object-oriented programming in Python, i.e. pass around class instances with the property p set in the instance, not the class.

class Aclass(object):
    p = None
    @classmethod
    def init_p(cls, value):
        p = value
    @classmethod
    def meth1(cls):
        # some code
        res = cls.meth2()
        # some more code w/ res
    @classmethod
    def meth2(cls):
        # do something with p
        pass

from a import Aclass as ac

ac.init_p(some_command_line_argument_value)

ac.meth1()
ac.meth2()
steveha
I said "Jimmy" posted an answer but I think I may have messed up there. I think it was probably David Seiler's answer I was looking at. (I don't even see a "Jimmy" answer here...) Sorry about that.
steveha
+1  A: 

Your example is reminiscent of the code smell Message Chains. You may find the corresponding refactoring, Hide Delegate, informative.

Ewan Todd