osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Do not promote `None` as the first argument to `filter` in documentation.


2018-03-06 14:17 GMT+03:00 Steven D'Aprano <
steve+comp.lang.python at pearwood.info>:

> On Tue, 06 Mar 2018 11:52:22 +0300, Kirill Balunov wrote:
>
> > I propose to delete all references in the `filter` documentation that
> > the first argument can be `None`, with possible depreciation of `None`
> > as the the first argument - FutureWarning in Python 3.8+ and deleting
> > this option in Python 4.
>
> Even if we agreed that it is unfortunate that filter accepts None as an
> argument, since it does (and has done since Python 1.0) there is nothing
> to be gained by deprecating and removing it.
>
> Deprecating and removing it will break code that currently works, for no
> good reason; removing the documentation is unacceptable, as that makes it
> too difficult for people to find out what `filter(None, values)` does.
>

As I wrote, __possible depreciation__, I also do not see the point of just
breaking someone's code. But I didn't see any benefit to explicitly promote
`filter(None, iterable)` form in the documentation as a good style.


> > Instead, it is better to show an example with using
> > `filter(bool, iterable)` which is absolutely
> > equivalent, more readable, but a little bit slower.
>
> So long as `filter(None, ...)` is still documented, I don't mind what
> example is given.
>
> But the idiom `filter(None, ...)` is an old, common idiom, very familiar
> to many people who have a background in functional programming.
>

While this form familiar and common idiom for those who are familiar with
Python from versions < 2.3, before `bool` type was introduced. It looks
kinky for newcomers and not obvious at a glance. In functional programming
we use a predicate, and `None` does not match predicate definition, while
`bool` does!


> It is unfortunate that filter takes the arguments in the order it does.
> Perhaps it would have been better to write it like this:
>
> def filter(iterable, predicate=None):
>     ...
>
>
> Then `filter(values, None)` would be a standard Python idiom, explicitly
> saying to use the default predicate function. There is no difference to
> `filter(None, values)` except the order is (sadly) reversed.
>

If such a form was in Python, I probably would agree with you. Although in
its present form I like it a lot more and find it more intuitive.

> Currently documentation for `None` case uses `identity function is
> > assumed`, what is this `identity` and how it is consistent with
> > truthfulness?
>
> The identity function is a mathematical term for a function that returns
> its argument unchanged:
>
> def identity(x):
>     return x
>
> So `filter(func, values)` filters according to func(x); using None
> instead filters according to x alone, without the expense of calling a do-
> nothing function:
>
> # slow because it has to call the lambda function each time;
> filter(lambda x: x, values)
>
> # fast because filter takes an optimized path
> filter(None, values)
>


> Since filter filters according to the truthy or falsey value of x, it
> isn't actually necessary to call bool(x). In Python, all values are
> automatically considered either truthy or falsey. The reason to call
> bool() is to ensure you have a canonical True/False value, and there's no
> need for that here.


I went over a bit with the question what is identity function :) But I have
a feeling that I perceive all of the above quite the contrary in the
context of a `filter` function. And since filter filters according to the
truthy or falsey value of x. `None` and `bool` should behave totally
equivalent under the hood and I'm 99% sure that it is so.


> So the identity function should be preferred to bool,
> for those who understand two things:
>
> - the identity function (using None as the predicate function)
>   returns x unchanged;
>

Sorry, but how does the above relates to the `filter` discussion?


>
> - and that x, like all values, automatically has a truthy value in a
>   boolean context (which includes filter).
>
>
Yes, and that is why there is no point to `None` since they will do the
same thing in context of `filter` function.


> > In addition, this change makes the perception of `map` and `filter` more
> > consistent,with the rule that first argument must be `callable`.
>
> I consider that a flaw in map. map should also accept None as the
> identity function, so that map(None, iterable) returns the values of
> iterable unchanged.
>
> def map(function=None, *iterables):
>     if len(iterables) == 0:
>         raise TypeError("map() must have at least two arguments.")
>     if function is None:
>         if len(iterables) > 1:
>             return zip(*iterables)
>         else:
>             assert len(iterables) == 1
>             return iter(iterables[0])
>     elif len(iterables) > 1:
>         return (function(*args) for args in zip(*iterables))
>     else:
>         assert len(iterables) == 1
>         return (function(arg) for arg in iterables[0])
>

And what will be the practical reason to have this? :)

With kind regards,
-gdg