Argument Precedence (possible bug?)

V

vbgunz

Hello all,

I am just learning Python and have come across something I feel might
be a bug. Please enlightenment me... The following code presents a
challenge. How in the world do you provide an argument for *arg4?

## ============================================================
def argPrecedence(par1, par2=0, par3=0, *par4, **par5):
print 'par1 =', par1, ' # positional argument'
print 'par2 =', par2, ' # keyword argument'
print 'par3 =', par3, ' # keyword argument'
print 'par4 =', par4, ' # argument converted to tuple'
print 'par5 =', par5, ' # argument converted to dictionary'


argPrecedence('arg1', arg3='arg3', arg2='arg2', arg5='arg5')

# argPrecedence Results:
par1 = arg1 # positional argument
par2 = arg2 # keyword argument
par3 = arg3 # keyword argument
par4 = () # argument converted to tuple
par5 = {'arg5': 'arg5'} # argument converted to dictionary
## ============================================================

The code above is verbose with comments because I am just learning
Python and am creating my own examples and reference materials... Can
you solve the problem? If so, can you share the solution? If not, is
this a bug, limitation or is it a feature?

Thank you for your time on this one!

PS. I could be far off with this codes purpose; to try and create an
example that will house all the different possible parameters
(positional, keyword, * and **) in one single def statement.

--
Best Regards
Victor B. Gonzalez
 
V

vbgunz

Please forgive my call()

Change this:
argPrecedence('arg1', arg3='arg3', arg2='arg2', arg5='arg5')

to this:
argPrecedence('arg1', par3='arg3', par2='arg2', arg5='arg5')

Thanks!
 
V

vbgunz

Hello all,

I am just learning Python and have come across something I feel might
be a bug. Please enlightenment me... The following code presents a
challenge. How in the world do you provide an argument for *arg4?

## ============================================================
def argPrecedence(par1, par2=0, par3=0, *par4, **par5):
print 'par1 =', par1, ' # positional argument'
print 'par2 =', par2, ' # keyword argument'
print 'par3 =', par3, ' # keyword argument'
print 'par4 =', par4, ' # argument converted to tuple'
print 'par5 =', par5, ' # argument converted to dictionary'

argPrecedence('arg1', par3='arg3', par2='arg2', arg5='arg5')

# argPrecedence Results:
par1 = arg1 # positional argument
par2 = arg2 # keyword argument
par3 = arg3 # keyword argument
par4 = () # argument converted to tuple
par5 = {'arg5': 'arg5'} # argument converted to dictionary
## ============================================================

The code above is verbose with comments because I am just learning
Python and am creating my own examples and reference materials... Can
you solve the problem? If so, can you share the solution? If not, is
this a bug, limitation or is it a feature?

Thank you for your time on this one!

PS. I could be far off with this codes purpose; to try and create an
example that will house all the different possible parameters
(positional, keyword, * and **) in one single def statement.

--
Best Regards
Victor B. Gonzalez
 
F

Fredrik Lundh

vbgunz said:
I am just learning Python and have come across something I feel might
be a bug. Please enlightenment me... The following code presents a
challenge. How in the world do you provide an argument for *arg4?

## ============================================================
def argPrecedence(par1, par2=0, par3=0, *par4, **par5):
print 'par1 =', par1, ' # positional argument'
print 'par2 =', par2, ' # keyword argument'
print 'par3 =', par3, ' # keyword argument'
print 'par4 =', par4, ' # argument converted to tuple'
print 'par5 =', par5, ' # argument converted to dictionary'

argPrecedence('arg1', arg3='arg3', arg2='arg2', arg5='arg5')

# argPrecedence Results:
par1 = arg1 # positional argument
par2 = arg2 # keyword argument
par3 = arg3 # keyword argument
par4 = () # argument converted to tuple
par5 = {'arg5': 'arg5'} # argument converted to dictionary
## ============================================================

The code above is verbose with comments because I am just learning
Python and am creating my own examples and reference materials... Can
you solve the problem? If so, can you share the solution? If not, is
this a bug, limitation or is it a feature?

not sure what you think the bug is, but I suspect that you've completely
missed the point of what * and ** do, and how they're used in real code.

</F>
 
P

Peter Hansen

vbgunz said:
I am just learning Python and have come across something I feel might
be a bug. Please enlightenment me... The following code presents a
challenge. How in the world do you provide an argument for *arg4?

Maybe by putting that in the list of arguments? ...

You don't even *have* an arg4 in the list, so how are we supposed to
know what you are asking without guessing? Please consider rephrasing
the question.

-Peter
 
S

Steven D'Aprano

Hello all,

I am just learning Python and have come across something I feel might
be a bug. Please enlightenment me... The following code presents a
challenge. How in the world do you provide an argument for *arg4?

## ============================================================
def argPrecedence(par1, par2=0, par3=0, *par4, **par5):
print 'par1 =', par1, ' # positional argument'
print 'par2 =', par2, ' # keyword argument'
print 'par3 =', par3, ' # keyword argument'
print 'par4 =', par4, ' # argument converted to tuple'
print 'par5 =', par5, ' # argument converted to dictionary'

No, you are confused about * and ** arguments. They don't convert
arguments, they collect them.

def f(a, b=0, *args, **kwargs):
print "a =", a
print "b =", b
print "args =", args
print "kwargs =", kwargs

Now call the function different ways, and see which will work and which
won't.

f(1)
f(1, 2)
f(1, b=2)
f(a=1, b=2)
f(b=2, a=1)

Notice that this does not work:

f(b=3)

Do you understand why not?

Now look at these calls:

f(1, 2, 3)
f(1, 2, 3, 4, 5, 6)
f(1, 2, 3, 4, 5, 6, foo=7, bar=8)

In the function definition f(a, b=0, *args, **kwargs), a must be supplied,
b has a default value so it can be left out. Both a and b can be provided
as positional arguments like f(2, 3) or as keyword arguments.

Any _extra_ positional arguments are collected into a tuple called args,
and any _extra_ keyword arguments are collected into a dictionary called
kwargs. Of course you can use any names you like.

Hope this helps,
 
S

Steven D'Aprano

PS. I could be far off with this codes purpose; to try and create an
example that will house all the different possible parameters
(positional, keyword, * and **) in one single def statement.

This is usually a bad idea, not because Python can't cope with it, but
because it is usually better to learn new things in small pieces, not one
giant enormous piece.

Try creating a number of small functions that do different things:

def f(a, b, c):
print "a =", a, "b =", b, "c =", c

def g(a=0, b=0, c=0):
print "a =", a, "b =", b, "c =", c

Now call them different ways, and see what happens:

f()
f(1)
f(1,2)
f(1,2,3)
f(b=2)

Can you see a pattern?

g()
g(1)
g(1,2)
g(1,2,3)
g(b=2)

Then move on to argument collectors:

def h(a, b, c=0, *d, **e):
print "a =", a, "b =", b, "c =", c
print "d =", d, "e =", e



Also, remember that "positional arguments" and "keyword arguments" aren't
defined differently, they are given when you call the function. For
example, suppose you have this:

def function(x, y):
print x + y

Now you call it with positional arguments: function(3, 7)
Now you call it with keyword arguments: function(x=3, y=7)
Now you call it with both: function(3, y=7)

All from the same definition. An argument is positional or keyword
according to how it is given, not how it is defined.
 
V

vbgunz

I am sorry I hung you up on a typo Peter Hansen. On line 5 *arg4 should
have been *par4. I hope it makes complete sense now. Sorry.
 
V

vbgunz

Please allow me some time to look at your examples. I get hung up over
the smallest details because in my mind, my approach should have just
worked... I learned about these parameters reading "O'reilly Learning
Python 2nd Edition". On page 217 of the paperback or Chapter 13.5.6 in
the ebook, topic: 'Argument matching: The Gritty Details' mentions the
following in verbatim...

....'''
Moreover, Python internally carries out the following steps to match
arguments before assignment:
1. Assign non-keyword arguments by position.
2. Assign keyword arguments by matching names.
3. Assign extra non-keyword arguments to *name tuple.
4. Assign extra keyword arguments to **name dictionary.
5. Assign default values to unassigned arguments in header.
'''...

As you can probably tell, I tried to follow the steps exactly, except
step 5 got me lost so I tried not to question it. Anyhow, thank you
very much for your time and examples. I will get back to you as soon as
I am done putting your response to trial. Thank you!
 
T

Terry Reedy

vbgunz said:
Hello all,

I am just learning Python and have come across something I feel might
be a bug.

I feel it is more likely that there in a bug in the communication process
from the manual to you, on an admittedly somewhat confusing and complex
subject.
Please enlightenment me...

OK. Here is my attempt at a summary.

The parameters for a function are the local variable names listed in the
definition header in ()s after the function name. There may be any number
of regular parameters and up to two collection parameters.

The arguments for a function call are the objects listed in ()s after the
function object (usually but not necessarily indicated by name).
Argument objects are the result of evaluating an expression.
An argument is optionally named by prefixing the expression with 'name=',
with the restriction that all args after a named arg must also be named.

Regular paramenter names (and slots of collection parameters) are bound to
argument objects by position or name according to the rules given in the
manual. At the end of the process, all parameters and arguments must
exactly one binding. (For parameters, the binding may be a default given
in the definition.) If not, an exception is raised and the call aborted.
How in the world do you provide an argument for *arg4?

You obviously mean par4. See below for how it gets a non-empty value.
def argPrecedence(par1, par2=0, par3=0, *par4, **par5):

Par1, par2, and par3 are regular parameters. They can by matched either by
position or name on a per-call basis. Mixing position and name matching in
any one call is strongly discouraged as confusing and error prone.

Par2 and par3 have default arg objects (calculated when the function is
defined). That only means that they do not have to match any of the args
in a particular call. It does not determine whether the binding to
explicit args in a particular call will be by position or name.
print 'par1 =', par1, ' # positional argument'
print 'par2 =', par2, ' # keyword argument'
print 'par3 =', par3, ' # keyword argument'

So these comments are wrong except as they happen to be right for a
particular call.


Par4 and par5 are leftover-argument collection objects bound by default to
() and {} respectively if there are, respectively, no leftover positional
or named args. So the way to get a nonempty tuple bound to par4 is to have
a leftover positional arg which requires that you give at least 4 (since
the first three will be bound to the three regular parameters).
print 'par4 =', par4, ' # argument converted to tuple'
print 'par5 =', par5, ' # argument converted to dictionary'

Wrong. Leftover args are placed in, not individually converted to, a tuple
or dictionary.
PS. I could be far off with this codes purpose; to try and create an
example that will house all the different possible parameters
(positional, keyword, * and **) in one single def statement.

Again, regular parameters are both positional and named, and the matching
on any particular call can be either (subject to the positional, then named
args restriction). In any case, what I think you mean above is not
possible. As stated previously, the *leftover_pos_arg tuple will be
nonempty only if all regular params are positionally matched.

Terry Jan Reedy
 
V

vbgunz

Hello, Steven D'Aprano, Terry Jan Reedy!

I would really like to extend my thanks to you guys. I hope I've got it
right this time!

def posKeyArgs(a, b=2, c=3):
print a, b, c

#posKeyArgs(b=20) # too few positional arguments. a needs an arg.
#posKeyArgs(10, c=30, 20) # pos_args cannot follow any kw_args.

def specialArgs(*args, **kwargs): # Can't call these keywords!
print args
print kwargs

specialArgs(args='arg1', kwargs='kwargs') # Keywords match nothing.
specialArgs('string') # not converted. Collected into: ('string',)

The above can begin to explain why I had problems with my first
example. *args and **kwargs cannot have arguments assigned to them by
keyword. Once an argument is matched by keyword, all following
arguments must also be matched by keyword.

This would explain why using only positional arguments, my first
example would have worked right out of the box. Because, all
positionals would have matched first, then all left overs would have
been collected into *args, then finally any keyword args would be
collected into **kwargs.

So far the general consensus also seems to be not to over complicate a
function definition with such parameters. This makes sense and is why I
chose Python over many other options. I love the idea of explicit over
implicit :) Again though, my first example was only a personal
reference.

Fellas, thank you all so much for helping me understand this much
better. You guys are my angels of Python for sure!
 
T

Terry Reedy

vbgunz said:
Hello, Steven D'Aprano, Terry Jan Reedy!

I would really like to extend my thanks to you guys. I hope I've got it
right this time!

def posKeyArgs(a, b=2, c=3):
print a, b, c

#posKeyArgs(b=20) # too few positional arguments. a needs an arg.

Yes, parameter a must get bound to something.
Any of pKA(10, b=20), pKA(a=10,b=20), pKA(c=30,a=5), etc, should work.
#posKeyArgs(10, c=30, 20) # pos_args cannot follow any kw_args.

Yes, this is an extreme of flexibility not allowed.

Terry Jan Reedy
 

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

Forum statistics

Threads
474,289
Messages
2,571,435
Members
48,121
Latest member
ColinHibne

Latest Threads

Top