Reputation: 7887
Okay I may just be having a bad workday but to my knowledge all()
should short circuit according to the docs.
all()
should be equivalent to:
def all(iterable):
for element in iterable:
if not element:
return False
return True
Which is also equivalent to daisy chaining and
.
So the following should do the same thing:
all((True, print('This should print and stop'), print("this shouldn't print")))
True and print('This should print and stop') and print("this shouldn't print")
Yet the top one does both prints (i.e. fully evaluated each part) but the bottom short circuited properly.
Is this a bug with Python (3.8.2) or am I not getting this right all of a sudden?
Repl with example: https://repl.it/join/scxbpnhc-syntactical01
Upvotes: 2
Views: 107
Reputation: 104072
Python's all
take an iterable as an argument
During the construction of the example in your question, in this case a tuple, the functions inside the tuple literal are called and the return value in inserted into the tuple literal; no differently than:
>>> tup=(True, print('this'), False, print('not this'))
this
not this
If you want to have a form of assemble on the fly you could do something like this:
def example_func(*args):
return args
g2all=(True, (print, 'this', 'and this'), False, (print, 'not this'), (example_func,1,2,3,4))
This is relying on the fact that you can refer to a function by name and, without the ()
call operator the function is not called yet:
>>> g2all
(True, (<built-in function print>, 'this', 'and this'), False, (<built-in function print>, 'not this'), (<function example_func at 0x10d0a9430>, 1, 2, 3, 4))
You can then use that in an all
with a generator:
>>> all(f[0](*tuple(f[1:])) if isinstance(f, tuple) and callable(f[0]) else f for f in g2all)
this and this
False
and the calling of the functions stop when all
finds a Falsie value.
Upvotes: 1
Reputation: 532033
The iteration inside all
short-circuits, but each call to print
has to evaluate in order to create the tuple
you pass as an argument before all
is ever called.
There's no simple way to create an arbitrary lazy sequence in Python without a def
statement. You can write
def foo():
yield True
yield print("This should print and stop")
yield print("this shouldn't print")
all(foo())
but there's no pure expression equivalent. You need to iterate over something to use a generator expression, for example
(x(y) for x, y in zip([id, print, print],
[True,
"This should print and stop",
"this shouldn't print"]))
would work as you expect.
>>> all(x(y) for x, y in zip([id, print, print], [True, "This should print and stop", "this shouldn't print"]))
This should print and stop
False
Now, print
is called inside all
, rather than as part of evaluating the argument to pass to all
.
Upvotes: 4