[Python-ideas] With expressions

Robert Vanden Eynde robertve92 at gmail.com
Fri Aug 3 06:49:24 EDT 2018


Thanks for answering each line. If someone wants "too long didn't read",
just check my code at the paragraph "readlines is a toy example, but maybe
the code would be more creative".

Le ven. 3 août 2018 à 03:07, Steven D'Aprano <steve at pearwood.info> a écrit :

> On Thu, Aug 02, 2018 at 03:13:25PM +0200, Robert Vanden Eynde wrote:
>
> > This brings the discussion of variable assignement in Expression.
> Functional
> > programming community seems to be more interested in python.
>
> I'm not sure what you mean there. Your English grammar is just slightly
> off, enough to make your meaning unclear,

sorry.
>

When I say "functional programming", I speak about the paradigm used in
language like Haskell. In language like those, all constructs are
"expression-based". I consider the code "result = 5 if condition else 2"
more "functional style" than "if condition: result = 5; else: result = 2".
Functional style focus on the result, uses expressions. Imperative focus on
the process, "we must do a condition, then we set the variable, else, we
set a variable to something else".


>
> > lines = (f.readlines() with open('hello') as f)
>
> readlines has the same problems as read, as I described earlier, and the
> same trivial three-line solution.
>
>
> > digit = (int('hello') except ValueError: 5)
>
> try...except exceptions have been proposed before and rejected.
>

I'm wondering why, that must have been the same reasons of not accepting
"with".

if condition:
    something = x
else:
    something = y

Can be refactored

something = x if condition else y

Or

something = (x if condition else
                        y)

But,

try:
    something = x
except:
    something = y

Can't?

The use cases seems similar.

One can use the first form, using more of a imperative programming, or the
second line, which is more "functional programming", more expressions
oriented.


>
> > value = (x+y**2 where x,y = (2,4))
>
> A "where" *statement* is interesting, but this is not a good example of
> it. The above is better written in the normal syntax:
>
>     value = 2 + 4**2


That's the discussion we had on the list called "variable assignement in
expressions". What you did here is inlining the variables, technically it's
not possible if you're calling a function and using the variable more than
once.

So we're really comparing it to :

x,y = (2,4)
value = x+y**2

Or

x = 2
y = 4
value = x+y**2

Where the idea is to separate the steps of a computation, introducing
temporary variables with a meaningful name is useful (And as mentioned, if
a function is called and the variable reused, it's called once, but that's
not the main point).

In Haskell there is "value = (x = 2 in y = 4 in x+y**2)" or similar.

position = initial + speed * time
position_inch = distance / 2.54

Vs

position_inch = (initial + speed * time) / 2.54

The programmer chooses what's more clear given the context and the audience.

Or maybe he wants to emphasize that the code creates a position_inch
variable usable in the code after ?

Or both, he wants to explain how he computes position_inch using a
temporary variable but doesn't want the rest of the code depend o
"position" ?).

Yes the del is generally useless, more about  leaking below.


> no need to introduce temporary variables that exist only to obfuscate
> the code.
>
>
> > values = [x+y**2 for x in range(5) for y in range(7)]
> > values = [x+y**2 for x,y in product (range(5), range(7))]
> > y = 5 if condition else 2
>
> These already exist, because they are useful.
>

I see those as a refactoring of imperative programming style as well
(values = []; for ...: values.append(...))

>
> > y = (lambda x: x+2)(x=5)
>
> This is not a good example of the use of a lambda. Better:
>
>     y = 5 + 2
>

Same thing as in the where syntax. However, some constructs are easier to
refactor as

def meaningfulname(x):
     return x+2

y = meaningfulname(5)


> Why bother writing a function with such a simple body if you are going
> to immediately call it on the spot? Unless the body is more complex, or
> you are going to call it elsewhere, or call it repeatedly, the lambda
> adds nothing.
>

Indeed, in complex expressions.

Or if I want to separate the steps of a computation.

position = initial + speed * time
position_inch = distance / 2.54


> Nobody denies that *some* statements are well-suited and useful as
> expressions. The question is whether "with" is one of those.
>

I'm just pointing out those constructs are very similar, it kind of makes
sense to compare them.

Of course I don't know about real world examples that would simplify a
code. But often as I'm a "expression first" guy

I write:

result = ...
# code using result and that doesn't care about the temporary variable it
uses.

Then I figure how to compute result, without polluting the namespace. Again
adding temporary variables before "result = ..." Is totally fine and that's
the way to go in imperative programming.


>
> > with open('hello') as f:
> >     lines = f.readlines()
> > del f  # f is leaked !
>
> 99% of the time, I would think that "del f" was a waste of time. If that
> code is in function, then f will be closed when the "with" block is
> exited, and garbage collected when the function returns.
>

Yes of course, but what if "f" was defined before? We lose its value, even
if "f" was created only as a temporary variable to have the variables lines.

Maybe it came from:

lines = ...
# code using lines

Yes naming it "f" where one has a 'f' in the same block is confusing, yes
it could be named "_". But maybe we are in a script and we have a lots of
variables? That kind of questions arise, when we wanted a temporary
variable.

readlines is a toy example, but maybe the code would be more creative,
something like :

if condition:
     filename = "session-{}-{}.txt".format(x, y*10)
else:
     filename = "data_{}_{}.txt".format(w, z)

with open('hello') as f:
    lines = [l.strip('\n') for l in f
                  if len(l) > 0
                  if not l.stri().startswith('#')]

parsed_data = [
    ...  # list expression mapping lines
]

The extra indent seems a bit weird, opening a file is just a secondary
effect of wanting to compute "lines = ..."

filename = ("session-{}-{}.txt".format(x, y*10) if condition else
                     "data_{}_{}.txt".format(w,z))

lines = ...  # Implement that later

parsed_data = [
    ...  # list expression mapping lines
]

To:

filename = ("session-{}-{}.txt".format(x, y*10) if ... else
                     "data_{}_{}.txt".format(x, y))

lines = ([l.strip('\n') for l in f
                  if len(l) > 0
                  if not l.stri().startswith('#')] with open (filename) as
f)

parsed_data = [
    ...  # list expression mapping lines
]

The creator chose to keep exactly the three variables because they have a
meaning, f is just an implementation detail. The creator wanted to
emphasize that the code is simply:

filename = ...
lines = ...
parsed_data = ...

But the "..." being not so useful to have their own function.

#Very verbose
def compute_filename(x,y,w,z):
    ...
def compute_lines(..., filename):
    ...
def compute_parsed_data(..., lines, filename):
    ...
filename = compute_filename(...)
lines = compute_lines(...)
parsed_data = compute_parsed_data(...)

The functions there should be anonymous, but lambda invoked directly are
not very readable.


> If you are worried about the memory efficiency of one tiny closed file
> object, then Python is the wrong language for you.
>

Yes I don't talk about the "free" of C or "delete" of C++.

>
> If that code is in the top-level of a script, who cares about f? You
> surely don't delete all your variables when you are done with them:
>
>     name = input("what is your name?")
>

    print("Hello,", name)
>     del name
>     play_game()
>
>
> The only time I would explicitly delete f like you do above was if I was
> writing a library which specifically and explicitly supported the "from
> module import *" syntax, AND there were too many exportable names to
> list them all in __all__ by hand. Only in that very rare case would I
> care about tidying up the global namespace by using del.
>

Yes that's a good example of me not wanting to "pollute the namespace, not
wanting to create simple functions, not wanting to indent sometimes, but
wanting to have tempory variables".

If someone reads the module to see its content, they look for all the lines
at zero indent begining with 'something = ...' or 'def ...'


>
> > x,y = 2,4
> > value = x+y**2
> > del x, y  # x,y are leaked !
>
> If you are writing code like this, you are just obscuring the code. Much
> better to just use the values where you need them, not to invent
> unnecessary temporary variables that you don't need!
>
>     value = 2 + 4**2
>

Temporary variable has always been a choice to the programmer, they explain
more the intermediary steps, but focus less on the final result.


>
> [...]
> > If we add one, it's Logical to add the others to be consistent.
>
> My car has round wheels. Since we use one shape (circle) for wheels, it
> must be "logical" to make wheels in all other shapes, to be consistent:
>
> - triangular wheels
> - square wheels
>
> etc. Consistency just for the sake of consistency is *not* a virtue.
> Unless those expression forms justify *themselves* on their own merits,
> it is just a waste of time and effort to let them sneak into the
> language "for consistency".
>

I agree, that's why I said earlier "I didn't think so much about the use
cases". But trust me, when I write code, I'm really often in "something =
..." Case. And sometimes the ... must depending on a try except, sometimes
on a with, sometimes on other variables that are temporary.


>
> > Of course, one can always write functions like read_text but the ide of
> > those construction is like the lambda, we want anonymous.
>
> We can say:
>
>     text = read('filename')
>
>     text = f.read() with open('filename') as f
>
> and both are equally unanonymous (both use a named variable), or we can
> say:
>
>     process(spam, eggs, read('filename'), foo, bar)
>
>     process(spam, eggs, f.read with open('filename') as f, foo, bar)
>
> and both are equally anonymous.
>
> If Python had a built-in function "read", surely you wouldn't refuse to
> use it because it was a named function? We don't complain that map() and
> filter() are named functions and demand "anonymous" ways to do the same
> thing. A read function should be no different.
>
> The only point of difference is that it is not built-in, you have to
> write it yourself. But not every trivial three-line function must be
> built-in.
>

Of course naming stuff is important (that's one of the reasons to build
temporary variables).

One difficultly of finding use cases, it that it's about changing the
paradigm, probably all cases have a really readable implementation in
current python / imperative style. But when I see:

try:
    a_variable = int(input("..."))
except ValueError:
    try:
       a_variable = fetch_db()
    except DbError:
       a_variable = default

I really think "why can't I put it one one line like I do with if".

a_variable = (int(input("...")) except ValueError:
                       fetch_db() except DbError:
                       default)

For "with", I'm just wondering "why do I have to indent, it will lose the
focus of the reader on the result itself".


> --
> Steve
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180803/decf4c31/attachment-0001.html>


More information about the Python-ideas mailing list