Discovered this after discovering the all( ) function in Python doesn't shortcircuit as I expect because the expressions inside the list you send are not lazily evaluated. So:
all([
previous_task is not None,
self.task_type == previous_task.task_type,
self.owner is None,
previous_task.owner is None,
self.is_sibling_of(previous_task)
])
In this code for example, if previous_task is None, self.task_type == previous_task.task_type
will still evaluate and throw an Exception while trying to access previous_task.task_type
Although this is easily fixable like this (and actually shortcircuits AND is lazy):
previous_task is not None and all([
self.task_type == previous_task.task_type,
self.owner is None,
previous_task.owner is None,
self.is_sibling_of(previous_task)
])
The core problem actually lies in that the list expression will evaluate the code inside eagerly while creating it.
So the solution is to create a generator, so it actually only evaluates the item one by one. But... generators require a comprenhension from something to actually generate. Or a function with the yield argument.
And although I could do something like this:
def __evaluate(self, previous_task):
yield previous_task is not None
yield self.task_type == previous_task.task_type
yield self.owner is None
yield previous_task.owner is None
yield self.is_sibling_of(previous_task)
Readability suffers, defeating the purpose of all
.
And I found on the PEP that you can use something like this:
(condition
for evaluation
in (
previous_task is not None,
self.task_type == previous_task.task_type,
self.owner is None,
previous_task.owner is None,
self.is_sibling_of(previous_task)
)
)
That wont work either, because the second parentheses is a set literal, not a generator, and it will evaluate the contents of the set on construction just like the list.
Another option is to make a reduce argument, from a list of lambdas, like this:
reduce(
lambda actual, next_function: actual and next_function(),
[
lambda: previous_task is not None
lambda: self.task_type == previous_task.task_type
lambda: self.owner is None
lambda: previous_task.owner is None
lambda: self.is_sibling_of(previous_task)
]
)
Is overly verbose, even though is still quite readable
So... is there something I'm missing? Is there any way to be able to do this in a concise way? It seems like overly hard for just lazy condition evaluation
If these are the only possibilities, I might consider doing an utils library D:
Edit: Forgot the obvious solution, which I didn't make explicit, where you could just chain and
s, but the thing is that this defeats the purpose of all
. And that I thought that knowing how to make generators of unrelated values is much useful in the long way than just spamming and
which hinders readability and forces you to "hard-code" the conditions in an huge expression
(previous_task is not None
and self.task_type == previous_task.task_type
and self.owner is None
and previous_task.owner is None
and self.is_sibling_of(previous_task)
)