Symbols as parameters?

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Fri Jan 22 03:20:44 CET 2010


On Thu, 21 Jan 2010 22:51:34 +0100, Martin Drautzburg wrote:

> Thanks for all the answers. Let me summarize
> 
> (1) I fail to see the relevance of
>  >>> def move( direction ):
> ...   print( "move " + str( direction ) ) ...
>  >>> move( "up" )
> move up

I'm glad it's not just me then.


> not only in the context of my question. And I don't see an abuse of the
> language either. Maybe this could pass as a Zen Puzzle.
> 
> (2) Using enum's was suggested. That is good to know, but again it is
> just a way to define constants in the caller's namespace.

I think this really is the correct solution for your problem. In Python, 
the standard place to have such public constants is at the module level, 
not the function or class. I think you're worrying unnecessarily about 
"namespace pollution" -- the module namespace is *exactly* the right 
place for them. If two functions both need UP, DOWN, etc symbols, then 
either:

(1) they can just use the same symbols; or
(2) if they can't, then they don't belong in the same module.

An example from the standard library: the re module defines constants I, 
L, M, etc. representing flags that are passed to the re.compile. They are 
implemented as integers so they can easily be combined with &, but 
another implementation might use symbols. You will notice that they're 
not limited to the re.compile function itself.

The caller may very well want to do something like this:

# Get some flags for compile:
flags = re.I & re.M
# ...
# much later on
# x = re.compile(s, flags)


You would force them to do this:

# Get some flags for compile:
flags = re.compile.I & re.compile.M
# ...
# much later on
# x = re.compile(s, flags)

which is, in my opinion, a needless level of indirection and possibly in 
violation of Demeter's Law.



> (3) Then somone suggested to tie the constants to the function itself,
> as in
> def move(direction):
>     print "moving %s" % direction
> 
> move.UP = 'up'
> move.DOWN = 'down'
> 
> This is quite nice.

I would call it a horrible, horrible, horrible code smell. A stench in 
fact. In my opinion, such attributes tied to the function should be 
treated as internal to the function, and not the public interface.

I wouldn't go quite so far as to say they should be treated as private, 
but having the caller use them should be rare and unusual.


> Then again the "move." is just some object which
> allows attributes, and which only happens to have the same name as the
> function. Well in this case it IS the function, alright, but I could
> just as well have used a Class as in
> 
> class m: pass
> m.UP = 'up'

Either way, when you go to *use* the direction, you're still passing a 
string. There's no difference between:

move(m.UP)

and just

move("up")


Furthermore, the extra layer of indirection with the m.* doesn't give you 
anything useful. Think about using this:

# choose a direction at random
direction = random.choice([m.UP, m.DOWN, m.LEFT, m.RIGHT])
move(direction)

What benefit is the extra layer of indirection? It just adds noise to the 
code. Surely this is better?

UP, DOWN, LEFT, RIGHT = "up down left right".strip()

direction = random.choice([UP, DOWN, LEFT, RIGHT])
move(direction)

That's much clearer.

In my opinion, if you want to prohibit users from passing a string (or 
integer) equal to your constants, so that

move('up')

does not work (in other words, they are forced to use the constants you 
provide) then Ben Finney's enum solution is probably the correct way to 
do it.

But if you don't care, then the simplest solution is to define the 
constants you care about in the module, using either strings or ints, and 
then let the caller choose between using your named constants or not:

move(UP)
move('up')


> (4) Finally someone mentioned DSLs. I guess thats absolutely correct.
> This is what I am struggeling to achieve. I did a little googling ("how
> to write DSLs in python"), but haven't found anything appealing yet. Any
> pointers would be appreciated.

That truly is using a bulldozer to crack a peanut.



 
> (5) Here is something I came up with myself:
> 
> def symbols(aDict):
>     aDict["foo"] = "bar"
> 
> def someFunction(aFoo):
>     print aFoo
> 
> symbols(locals())
> someFunction (foo) #Eh voila: foo is magically defined
> prints: bar
> 
> The call to symbols(locals()) is the "magic, magic" I supected would be
> required in my original posting. If someFunction was a member of a
> class, the symbols would be properly tied to that class (albeit not the
> individual function), but still good enough. 


I disagree about it being "proper" to tie such public symbols to the 
class. But in any case, what you're trying to do is not supported by 
Python. If it works, that's a happy accident.

"The contents of this dictionary should not be modified; changes may not 
affect the values of local and free variables used by the interpreter."


http://docs.python.org/library/functions.html#locals



> I suppose I could wrap it
> in a decorator, which would also do the "unmagic".
> 
> In any case, getting the context right seems to be the biggest problem.
> If I don't want to pollute my namespace, 

It's not pollution. The module namespace is the right place for such 
public constants.



-- 
Steven



More information about the Python-list mailing list