views:

570

answers:

5

I have two functions:

def f(a,b,c=g(b)):
    blabla

def g(n):
    blabla

c is an optional argument in function f. If the user does not specify its value, the program should compute g(b) and that would be the value of c. But the code does not compile - it says name 'b' is not defined. How to fix that?

Someone suggested:

def g(b):
    blabla

def f(a,b,c=None):
    if c is None:
        c = g(b)
    blabla

But this doesn't work, because maybe the user intended c to be None and then c will have another value.

+2  A: 

value of c will be evaluated (g(b)) at compilation time. You need g defined before f therefore. And of course you need a global b variable to be defined at that stage too.

b = 4

def g(a):
    return a+1

def test(a, c=g(b)):
    print(c)

test(b)

prints 5.

SilentGhost
It highlights that the problem is not having a function as default value but having b undefined for the that function
luc
Here b is a global variable... in my example it's not
ooboo
well it won't work then! `b` needs to exist at compilation time, when `g(b)` is evaluated!
SilentGhost
+2  A: 

You cannot do it that way.

Inside the function, check if c is specified. If not, do the calculation.

def f(a,b,c=None):
    if c == None:
        c = g(b)
    blabla
codeape
c is None is faster than c == None
Paolo Bergantino
It's true that 'is None' is faster than '== None', but the more important reason 'is None' is preferred is that it's more robust. The equality operator can be overridden such that objects which are not None can still compare equal to None. The proper test for None is debated constantly on comp.lang.python. Unless you really know what you're doing, use 'is'.
John Y
Hey Paolo. Can you explain why c is None is faster than c == None? Is that inherent to objects or should I use 'is' when comparing numeric arguments, too? Thanks!
ooboo
@ooboo: You should definitely NOT use 'is' for comparing numbers! The point of 'is' is to check whether two names refer to the same object. Effectively, it's a pointer comparison. This makes it fast, but inappropriate for numbers and strings, which (in Python) can have multiple copies (i.e. different object instances) that have the same value.
John Y
+16  A: 
def f(a,b,c=None):
    if c is None:
        c = g(b)

If None can be a valid value for c then you do this:

sentinel = object()
def f(a,b,c=sentinel):
    if c is sentinel:
        c = g(b)
Paolo Bergantino
sorry, this is not a good solution. Maybe the user want's c value to be None? and then c will be set to something else by the function g(b) even though that was not the user's intention
ooboo
Then you're into trouble: http://docs.python.org/reference/compound_stmts.html#function-definitions You should re-consider your design or read a good Python book.
Boldewyn
I will give you an example. Suppose b is a big number and g(b) calculates its factors. Suppose, further, that for convinience we don't count the number itself and 1 as factors, so effectively the factorization of a prime number evaluates to None. c is an optional argument for the user to specify if he already knows the factorization. Then why call g(b) which could potentially be very expensive?
ooboo
@ooboo: if you read my answer it is explained there, that in your approach `g(b)` will **always** be evaluated at compilation time. Always. Have a look at my example and try `print(a)` inside `def g`. It will print it even if `test` function is not called.
SilentGhost
You are completely correct, I got it all messed up because of the None example.Originally I simply meant for c to be the index of a particular element in the list b. If the user knows it, no point to calculate it.
ooboo
@ooboo: So if the caller supplies the factorisation, c will be a list, yes/no? A list of factors other that 1 and b? So get them to pass [] meaning "I know the factorisation; there are no non-1 non-b factors, or pass None meaning "factorisation unknown". If you don't like None, use a sentinel with a meaningful name e.g. FactorsUnknown.
John Machin
Okay, so what if g(b) finds the first element of b that immediately precedes an element of a numeric value? This element could potentially be None, if b = ['what?', [1], 2, None, 5], then g(b) was calculated for no reason.
ooboo
@ooboo: Nitpick: in that example g(b) should evaluate to [1], not None. Major problem: you haven't made it apparent why you don't like the general solution of having an out-of-band sentinel with a suitable name that means "I don't know what g(b) is, please calculate it" ... "out of band" meaning it can't be a normal value of c and can't be a result of g(b).
John Machin
Because it's still more complicated...Coming to think about it, I think the best solution is simply two separate functions.
ooboo
A: 
def f(a,b,*args):
    if len(args) == 1:
        c = args[0]
    elif len(args) == 0:
        c = g(b)
    else:
        raise Exception('Function takes 2 or 3 parameters only.')
    blabla

def g(n):
    blabla

You can probably structure it better, but that's the main idea. Alternatively you can use **kwargs and use the function like f(a,b,c=Something), you just have to modify f accordingly.

Documentation

Maiku Mori
SilentGhost fixed it, and Thanks to him for that.Sorry I was rushing to lunch so it came out ugly and with mistakes. But it's a working solution and Paolo Bergantino solution would break if I actually wanted to pass 'sentinel' as argument c and 'sentinel' is extra variable in the namespace now.
Maiku Mori
@Maiku Mon: The whole point of `sentinel` is that it's NOT a normal value for c, it's a special value that means 'please calculate c = g(b) for me; you don't "want" to pass it as a normal value of c.
John Machin
@John, the problem can be solved this way. The OP has `c` pre-calculated. So if he passes it to `f` he doesn't want it to be calculated again. If he doesn't pass it, than it needs to be calculated.
SilentGhost
@John, I understand how it works. The first part of my argument is probably meaningless since chances are no one will ever use 'sentinel' as parameter, but I still don't like it. It's just my biased opinion.
Maiku Mori
A: 

The problem with

sentinel = object()
def f(a, b, c=sentinel):
  if c is sentinel:
    c = g(b)

is that sentinel is global/public unless this code is part of a function/method. So someone might still be able to call f(23, 42, sentinel). However, if f is global/public, you can use a closure to make sentinel local/private so that the caller cannot use it:

def f():
  sentinel = object()
  def tmp(a, b, c=sentinel):
    if c is sentinel:
      c = g(b)
  return tmp
f = f()

If you are concerned that static code analyzers could get the wrong idea about f then, you can use the same parameters for the factory:

def f(a, b, c=object()): #@UnusedVariable
  sentinel = object()
  def tmp(a, b, c=sentinel):
    if c is sentinel:
      c = g(b)
  return tmp
f = f(23, 42)

Regards,

PointedEars

Thomas 'PointedEars' Lahn