Calling functions...

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Calling functions...

kirby urner-4
Something I maybe hadn't been clear on
before cramming for a workshop:

Even functions with purely positional
parameters may be passed a dict, in
which case the positional parameters
will be treated like named ones, i.e.
the dict will map to them.

I'll use the new formating protocol:

>>> from __future__ import print_function

>>> def f(a, b, c):
        print("a={0}, b={1}, c={2}".format(a,b,c))

       
>>> f(1,2,3)
a=1, b=2, c=3

Here's what I'm illustrating:

>>> thedict = dict(c=5,a=10,b='cat')

>>> f(**thedict)
a=10, b=cat, c=5

Now what if we try to sneak in an argument
that doesn't map:

>>> thedict = dict(c=5,a=10,b='cat', d='dog')
>>> f(**thedict)

Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    f(**thedict)
TypeError: f() got an unexpected keyword argument 'd'

The d is caught, an exception is raised.

So here we might add the all purpose "pooper scooper"
(yeah, scatological -- sometimes effective pedagogy):

>>> def f(a, b, c, **kwargs):
        print("a={0}, b={1}, c={2}".format(a,b,c))

The d now gets through, though nothing echoes:
       
>>> f(**thedict)
a=10, b=cat, c=5

Lets make sure we see what's in "overflow":

>>> def f(a, b, c, **kwargs):
        print("a={0}, b={1}, c={2}".format(a,b,c))
        print(kwargs)

       
>>> f(**thedict)
a=10, b=cat, c=5
{'d': 'dog'}

Is an all purpose pooper scooper allowed to have
defaults?  No, default arguments are what turn
parameters into named parameters.  *args and
**kwargs (often so named by convention) are
not named parameters so much as "collectors"
of "overflow" arguments (made-up terminology).

>>> def f(a, b, c, d='dog', *e):
        print("a={0}, b={1}, c={2} d={3} e={4}".format(a,b,c,d,e))

       
>>> f(**thedict)
a=10, b=cat, c=5 d=dog e=()
>>>

Speaking of made-up terminology, when I pass
**thedict as an argument, I tend to think of the
** as "explode".

If you don't double-star it, it goes through as just
the one argument and an exception gets raised:

>>> f(thedict)

Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    f(thedict)
TypeError: f() takes at least 3 arguments (1 given)

The same "explode" operator works for tuples in
that it "unpacks" the one argument into len(tuple)
arguments.  The typical use is to make (x,y,z)-tuples
map to some function that expects x, y and z as
separate arguments.

The error messages remind us when we fail to
"explode" one argument into many:

>>> def f(x, y, z):
        return x**2 + y**2 + z**2

>>> f(1,2,3)
14
>>> coords = (1,2,3)
>>> f(coords)

Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    f(coords)
TypeError: f() takes exactly 3 arguments (1 given)
>>> f(*coords)
14
>>> f(dict(y=2,x=1,z=3))

Traceback (most recent call last):
  File "<pyshell#40>", line 1, in <module>
    f(dict(y=2,x=1,z=3))
TypeError: f() takes exactly 3 arguments (1 given)
>>> f(**dict(y=2,x=1,z=3))
14

Kirby
_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig
Reply | Threaded
Open this post in threaded view
|

Re: Calling functions...

David MacQuigg
Hey Kirby!!  This is the best explanation I've seen of Python's argument
passing subtleties.  If you don't mind I would like to post it on our
PyKata website in a section "Tips from the Masters", with you as author,
of course.  We could also put a link to a page on your website, if you
prefer.

-- Dave

************************************************************     *
* David MacQuigg, PhD    email: macquigg at ece.arizona.edu   *  *
* Research Associate                phone: USA 520-721-4583   *  *  *
* ECE Department, University of Arizona                       *  *  *
*                                 9320 East Mikelyn Lane       * * *
* http://purl.net/macquigg        Tucson, Arizona 85710          *
************************************************************     *



kirby urner wrote:

> ...
>
> Even functions with purely positional
> parameters may be passed a dict, in
> which case the positional parameters
> will be treated like named ones, i.e.
> the dict will map to them.
>
> I'll use the new formating protocol:
>
>  
>>>> from __future__ import print_function
>>>>        
>
>  
>>>> def f(a, b, c):
>>>>        
> print("a={0}, b={1}, c={2}".format(a,b,c))
>
>
>  
>>>> f(1,2,3)
>>>>        
> a=1, b=2, c=3
>
> Here's what I'm illustrating:
>
>  
>>>> thedict = dict(c=5,a=10,b='cat')
>>>>        
>
>  
>>>> f(**thedict)
>>>>        
> a=10, b=cat, c=5
>
> Now what if we try to sneak in an argument
> that doesn't map:
>
>  
>>>> thedict = dict(c=5,a=10,b='cat', d='dog')
>>>> f(**thedict)
>>>>        
>
> Traceback (most recent call last):
>   File "<pyshell#11>", line 1, in <module>
>     f(**thedict)
> TypeError: f() got an unexpected keyword argument 'd'
>
> The d is caught, an exception is raised.
>
> So here we might add the all purpose "pooper scooper"
> (yeah, scatological -- sometimes effective pedagogy):
>
>  
>>>> def f(a, b, c, **kwargs):
>>>>        
> print("a={0}, b={1}, c={2}".format(a,b,c))
>
> The d now gets through, though nothing echoes:
>
>  
>>>> f(**thedict)
>>>>        
> a=10, b=cat, c=5
>
> Lets make sure we see what's in "overflow":
>
>  
>>>> def f(a, b, c, **kwargs):
>>>>        
> print("a={0}, b={1}, c={2}".format(a,b,c))
> print(kwargs)
>
>
>  
>>>> f(**thedict)
>>>>        
> a=10, b=cat, c=5
> {'d': 'dog'}
>
> Is an all purpose pooper scooper allowed to have
> defaults?  No, default arguments are what turn
> parameters into named parameters.  *args and
> **kwargs (often so named by convention) are
> not named parameters so much as "collectors"
> of "overflow" arguments (made-up terminology).
>
>  
>>>> def f(a, b, c, d='dog', *e):
>>>>        
> print("a={0}, b={1}, c={2} d={3} e={4}".format(a,b,c,d,e))
>
>
>  
>>>> f(**thedict)
>>>>        
> a=10, b=cat, c=5 d=dog e=()
>  
>
> Speaking of made-up terminology, when I pass
> **thedict as an argument, I tend to think of the
> ** as "explode".
>
> If you don't double-star it, it goes through as just
> the one argument and an exception gets raised:
>
>  
>>>> f(thedict)
>>>>        
>
> Traceback (most recent call last):
>   File "<pyshell#31>", line 1, in <module>
>     f(thedict)
> TypeError: f() takes at least 3 arguments (1 given)
>
> The same "explode" operator works for tuples in
> that it "unpacks" the one argument into len(tuple)
> arguments.  The typical use is to make (x,y,z)-tuples
> map to some function that expects x, y and z as
> separate arguments.
>
> The error messages remind us when we fail to
> "explode" one argument into many:
>
>  
>>>> def f(x, y, z):
>>>>        
> return x**2 + y**2 + z**2
>
>  
>>>> f(1,2,3)
>>>>        
> 14
>  
>>>> coords = (1,2,3)
>>>> f(coords)
>>>>        
>
> Traceback (most recent call last):
>   File "<pyshell#38>", line 1, in <module>
>     f(coords)
> TypeError: f() takes exactly 3 arguments (1 given)
>  
>>>> f(*coords)
>>>>        
> 14
>  
>>>> f(dict(y=2,x=1,z=3))
>>>>        
>
> Traceback (most recent call last):
>   File "<pyshell#40>", line 1, in <module>
>     f(dict(y=2,x=1,z=3))
> TypeError: f() takes exactly 3 arguments (1 given)
>  
>>>> f(**dict(y=2,x=1,z=3))
>>>>        
> 14
>
> Kirby
> _______________________________________________
> Edu-sig mailing list
> [hidden email]
> http://mail.python.org/mailman/listinfo/edu-sig
>
>  


_______________________________________________
Edu-sig mailing list
[hidden email]
http://mail.python.org/mailman/listinfo/edu-sig