function argument dependent on another function argument?

R

Reckoner

I would like to do:

def foo(self,x,y=self.a)

where the default value for y=self.a. Since this is not possible, I
wind up doing


def foo(self,x,y=None)
if not y:
y=self.a

but that seems kind of clumsy.

Is there a better way to do this?

Thanks in advance
 
A

Aaron Brady

I  would like to do:

def foo(self,x,y=self.a)

where the default value for y=self.a. Since this is not possible, I
wind up doing

def foo(self,x,y=None)
  if not y:
    y=self.a

but that seems kind of clumsy.

Is there a better way to do this?

Thanks in advance

No. The only alternative is really niche and probably not what you
want. You'd use a decorator (which I don't have, btw):

@defval( y= selfattrget( 'a' ) )
def foo( self, x, y ).
 
S

Steven D'Aprano

I would like to do:

def foo(self,x,y=self.a)

where the default value for y=self.a. Since this is not possible, I wind
up doing


def foo(self,x,y=None)
if not y:
y=self.a

but that seems kind of clumsy.

It's also incorrect, because if you pass y=0 as an argument, or any other
false value, it will be replaced by self.a.

Is there a better way to do this?

def foo(self, x, y=None):
if y is None:
y = self.a


I don't find that clumsy in the least. I find it perfectly readable and a
standard idiom.
 
P

Paul Rubin

Steven D'Aprano said:
def foo(self, x, y=None):
if y is None:
y = self.a

I don't find that clumsy in the least. I find it perfectly readable and a
standard idiom.

That has the same problem as the earlier version. If the person
passes None, they get self.a. I prefer:

sentinel = object()
...

def foo(x, y=sentinel):
if y is sentinel:
y = self.a
 
A

Aaron Brady

That has the same problem as the earlier version.  If the person
passes None, they get self.a.  I prefer:

    sentinel = object()
    ...

    def foo(x, y=sentinel):
      if y is sentinel:
          y = self.a

It is too bad that it is so much work to detect whether 'y' was passed
in the function call directly. However, sentinel is just as good (or
nearly); at worst, you need one sentinel per argument per function,
which is possible to create, which has a specific meaning. If you are
making systematic function calls, e.g. with a dictionary or list, you
can just use the sentinel in the dictionary.
 
A

Aaron Brady

Aaron Brady wrote inin comp.lang.python:





One per Module should be good enough. The only reason None doesen't
suffice is that it has other legitimate uses.  Though to be honest
I would always use None as the sentinel if it wasn't a legitimate
argument.


IIUYC then, one sentinel is still only needed as the missing argument
is indicated by *both* position and value or by name and value (in the
case of a keyword-dictionary), so seperate distinct sentinel objects
aren't required, for example:

SENTINEL = object()

def f( a, b, c = SENTINEL, d = SENTINEL ):
  print( "values: %r" % ( ( a, b, c, d ), ) )
  if c is SENTINEL:
    print( "c is missing" )
  if d is SENTINEL:
    print( "d is missing" )

f( *( 1, 2, SENTINEL, SENTINEL ) )

f( **dict( a = 1 , b = 2, d = 4 ) )

f( **dict( a = 1 , b = 2, d = 4, c = SENTINEL ) )

Rob.
--http://www.victim-prime.dsl.pipex.com/

I don't have a concrete example, so you may prove to be right, but I'm
not convinced.

If you have one function with an argument that defaults to an empty
list, and calls another with an argument that defaults to an empty
dict, then what is the meaning of passing sentinel to the first one?
Whereas, if each had their own, then passing the first one's default
would mean the empty list, and passing the second one's default would
mean the dict.

(Or, even if that evaluates correctly, perhaps there is no such useful
program.)
 
A

andrew cooke

    sentinel = object()
    ...

    def foo(x, y=sentinel):
      if y is sentinel:
          y = self.a

it just struck me you could also do:

def foo(self, x, *y_args)
y = y_args[0] if y_args self.a

which more directly checks whether an argument was passed, but has the
downside of making the method signature less clear in the declaration.

andrew
 
S

Steven D'Aprano

That has the same problem as the earlier version.

No it doesn't. The earlier version had the problem that *any* false
object is replaced by self.a. I'm just following the standard Python
idiom of using None as a sentinel. Have a look through the standard
library and see how many times it is used.

Built-ins rarely accept None as a sentinel, slice() being a conspicuous
exception. This is sometimes a nuisance when writing wrappers:

def my_find(S, sub, start=None, end=None):
"""Like string.find() only with pre-processing."""
pre_process() # stub for something complicated
if end is None and start is None:
return S.find(sub)
elif end if None:
return S.find(sub, start)
else:
return S.find(sub, start, end)

or if you prefer:

def my_find(S, sub, start=None, end=None):
"""Like string.find()"""
pre_process()
args = [sub]
if start is not None:
args.append(start)
if end is not None:
args.append(end)
return S.find(*args)


Having said that, there are times where you need to pass None as a
legitimate argument and not as a sentinel. In that case, your solution:
If the person passes
None, they get self.a. I prefer:

sentinel = object()
...

def foo(x, y=sentinel):
if y is sentinel:
y = self.a

is an excellent one.
 
P

Paul Rubin

Steven D'Aprano said:
Having said that, there are times where you need to pass None as a
legitimate argument and not as a sentinel.

I don't think it's worth trying to figure out which those times are.
The conclusion can be wrong, or can become wrong later because of
some faraway change in the code. I prefer using the bulletproof
method from the beginning, whether it is needed or not.
 
A

Aaron Brady

    sentinel = object()
    ...
    def foo(x, y=sentinel):
      if y is sentinel:
          y = self.a

it just struck me you could also do:

     def foo(self, x, *y_args)
       y = y_args[0] if y_args self.a

which more directly checks whether an argument was passed, but has the
downside of making the method signature less clear in the declaration.

andrew

Also, if you need to change your calling signature down the line, this
alternative really ties your hands with regard to it. You also lose
the ability to pass 'y' by keyword.

George Sakkis, who I only know from the NG, has a recipe that tests a
call against a function signature to determine what arguments are
being passed. The 'inspect' module also has the 'getargvalues'
function, which does something similar.
 
A

Aaron Brady

Aaron Brady wrote inin comp.lang.python:


I don't have a concrete example, so you may prove to be right, but I'm
not convinced.

I'm afraid I can't think of a use case for passing default values around
eiither, and I suspect if we were to come up with one, a better solution
that didn't involve passing default values around could be found.
If you have one function with an argument that defaults to an empty
list, and calls another with an argument that defaults to an empty
dict, then what is the meaning of passing sentinel to the first one?
Whereas, if each had their own, then passing the first one's default
would mean the empty list, and passing the second one's default would
mean the dict.

If you *mean* to pass an "empty list" or "empty dict"'s you should do
it like:

  function_taking_list( [] )
  function_taking_dict( {} )

Its when you don't (have reason to) care that you need default arguments.

'None' isn't a valid value for many standard library functions. So,
if you try to pass it meaning, "Whatever the default value you usually
use is," you'll get an error. Sometimes, the functions don't even use
a public sentinel, so if you want the default value, you either have
to know what it is, or you can't pass anything to that parameter.
This is usually possible, it just prevents making a uniform call to a
function. If you need to do some calculations to determine what
parameters you're going to pass, you're stuck testing their presence
with 'if-else' combinations, then calling individually.
 
S

Steven D'Aprano

Built-ins rarely accept None as a sentinel, slice() being a conspicuous
exception. This is sometimes a nuisance when writing wrappers:

def my_find(S, sub, start=None, end=None):
"""Like string.find() only with pre-processing.""" pre_process() #
stub for something complicated if end is None and start is None:
return S.find(sub)
elif end if None:
return S.find(sub, start)
else:
return S.find(sub, start, end)

Typical.

As of Python 2.6, string.find accepts None as sentinels. All my
beautiful, beautiful code made obsolete!!! *wink*
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,733
Messages
2,569,440
Members
44,830
Latest member
ZADIva7383

Latest Threads

Top