Survey: improving the Python std lib docs

justin walters walters.justin01 at gmail.com
Thu May 18 11:08:31 EDT 2017


On Thu, May 18, 2017 at 12:09 AM, Deborah Swanson <python at deborahswanson.net
> wrote:

> Michael Torrie wrote, on Wednesday, May 17, 2017 3:11 PM
> >
> > On 05/17/2017 02:31 PM, Ned Batchelder wrote:
> > > Can you give an example of such a method? Often, that signature is
> > > used because there is no pre-conception of what the arguments might
> > > be.
> >
> > I'm not sure if this afflicts the standard library, but in my
> > own code, since Python doesn't support constructors with
> > different signatures, you pretty much have to rely on kwargs
> > with __init__() to handle different permutations of
> > construction arguments.  Not that we don't know what the
> > arguments might be, we just don't know which of them we'll
> > have to deal with.  Maybe the standard library is better
> > designed than my own code, but I find I have to implement
> > __init__(self, **kwargs) all the time.
>
>
> While I can respect and appreciate your specialized interest in specific
> kwargs, and I read in other's posts that help()'s display of kwargs is
> conditional on several things, I really want to reiterate and clarify my
> beginner's request.
>
> I know of several functions I've used without any *args or **kwargs
> because I had no clue from the docs what they might be. And, I know of
> one function now, from posts in this list, which takes kwargs, but I
> only know one application of one possible kwarg in this function because
> one member of this list, Peter Otten, used it in a suggestion he gave
> me.
>
> This is all there is in the docs for that function:
>
>
> somenamedtuple._replace(kwargs)
>
>     Return a new instance of the named tuple replacing specified fields
> with new values:
> (Examples
> box)---------------------------------------------------------------|
> |    >>>
> |
> |
> |
> |    >>> p = Point(x=11, y=22)
> |
> |    >>> p._replace(x=33)
> |
> |    Point(x=33, y=22)
> |
> |
> |
> |    >>> for partnum, record in inventory.items():
> |
> |    ...     inventory[partnum] =
> record._replace(price=newprices[partnum], |
> |  timestamp=time.now())
> |
> |-----------------------------------------------------------------------
> ----|
>
> From this doc entry's example, I (sort of) get that x is a keyword arg
> that is to be equal to 33 for any use of p._replace() in the code
> following, until
> p._replace(kwarg) is respecified, or the code ends. So the response from
> Python shows me that p has transformed to the Point(x=33, y=22). A
> trivial example that doesn't begin to hint at the poential powers of
> this kwarg.
>
> The inventory[partnum] example is also quite trivial compared to the
> kwarg use that Peter showed me.
>
> When I asked him for an explanation, this is what I and he he said:
>
> Deborah Swanson wrote:
>
> > I know it's your "ugly" answer, but can I ask what the '**' in
> >
> > fix = {label: max(values, key=len)}
> > group[:] = [record._replace(**fix) for record in group]
> >
> > means?
>
> (Peter wrote)
> d = {"a": 1, "b": 2}
> f(**d)
>
> is equivalent to
>
> f(a=1, b=2)
>
> so ** is a means to call a function with keyword arguments when you want
> to
> decide about the *names* at runtime. Example:
>
> >>> def f(a=1, b=2):
> ...     print("a =", a)
> ...     print("b =", b)
> ...     print()
> ...
> >>> for d in [{"a": 10}, {"b": 42}, {"a": 100, "b": 200}]:
> ...     f(**d)
> ...
> a = 10
> b = 2
>
> a = 1
> b = 42
>
> a = 100
> b = 200
>
> Starting from a namedtuple `record`
>
> record._replace(Location="elswhere")
>
> creates a new namedtuple with the Location attribute changed to
> "elsewhere",
> and the slice [:] on the left causes all items in the `groups` list to
> be
> replaced with new namedtuples,
>
> group[:] = [record._replace(Location="elsewhere") for record in group]
>
> is basically the same as
>
> tmp = group.copy()
> group.clear()
> for record in tmp:
>     group.append(record_replace(Location="elsewhere"))
>
> To support not just Location, but also Kind and Notes we need the double
>
> asterisk.
>
>
>
> Now, I understood what he meant from the context of the problem he was
> helping me with, but, how in the world would I have gotten that possible
> use of somenamedtuple._replace(kwargs) from the blurb and examples in
> the docs? Or any other possible kwarg and how they're used, including
> special operators like "**" ?
>
> If there's a tutorial somewhere on kwargs in general, what specific
> types of kwargs there are, what special operators can be specifed for
> them and how the kwargs are used, it would be very helpful to have that
> tutorial referenced in a footnote-link next to kwargs whenever kwargs is
> part of a function specification.
>
> Or, if no such tutorial exists, the footnote-link could point to any
> more detailed information that might explain what this creature "kwargs"
> might be.
>
> I have an item on my todo list to google "python kwargs", but it keeps
> getting pushed deeper and deeper down my list because I have so little
> hope that it will turn up anything useful.
>
> Deborah
>
> --
> https://mail.python.org/mailman/listinfo/python-list
>


I agree that in a lot of situations `**kwargs` can be confusing.

I usually operate under the assumption that a Python function takes two
kinds of values:

`*args`: A (named)? tuple of values. For example:

```
def foo(*args):
    for arg in args:
        print(arg)

foo(1, 2 ,3)
>>> 1
>>> 2
>>> 3
```

However, when naming position arguments:

```
def foo(a, b, *args):
    for arg in args:
        print(arg)

foo(1, 2, 3)
>>> 3
```

Therefore, I find that `*args* can generally be considered as an index in a
(named)? tuple of
function arguments.

`**kwargs`: An unpacked dictionary. Shares most of it's behavior with
`*args` except that
values must have keys.

I find `**kwargs` to be especially useful working with Django's ORM:

Suppose we have the following model class:
```
class Thing(models.Model):
    name = models.CharField(max_length=255)
    is_thing = models.BooleanField(blank=True, default=True)
```

And we want to create a new instance of it from some json data our server
received
from the client:
```
data = json.loads(str(request.body, encoding="utf8"))
new_thing = Thing(**data).save()
```

As long as the keys in the dictionary match the argument names for the
method, they will be passed
to the function as keyword arguments. Any keys that don't match argument
names and aren't explicitly
pulled of of the dict do not get used. in the above case, Django's Model
class does some behind
the scenes work with `hasattr` and `setattr` because it doesn't know until
runtime which Model has
which attributes.

A simple example would be this class:

```
class Bar(object):

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            if hasattr(self, key):
                setattr(self, key, value)
```

We can then inherit from this class and create our own with some attributes:
```
class MyBar(Bar):

    foo = ""
    baz = ""
```

We can populate an instance of our class with the following expressions:
```
data = {"foo": "hello", "baz": "world"}
my_instance = MyBar(**data)

my_instance.foo
>>> "hello"

my_instance.baz
>>> "world"
```

So, args can be treated as a simple (named)? tuple or a simple dictionary.
`*` unpacks a list or tuple
and `**` unpacks a dictionary. I'm sure it's a lot more nuanced than this,
but for practical reasons, the
above explanation has never failed me.

I hope I didn't miss the entire point of the thread. Someone needed help
with `**kwargs` right?



More information about the Python-list mailing list