Why doesn't any short circuit in this property?

Viewed 42

self.ts_end is None. I would any expect any to yield True after the first evaluation.

    @property
    def is_active(self):
       return any((self.ts_end is None, self.ts_end > datetime.now(timezone.utc)))

It crashes on the second comparison. however.

TypeError: '>' not supported between instances of 'NoneType' and 'datetime.datetime'

What am I missing here?

3 Answers

It's any's iteration of its argument that can be short-circuited. In this case, the tuple still has to be constructed before any starts iterating over it. It's the same as if you had written

@property
def is_active(self):
   t = (self.ts_end is None, self.ts_end > datetime.now(timezone.utc))
   return any(t)

There's little reason to use any here; just use or:

@property
def is_active(self):
   return (self.ts_end is None
            or self.ts_end > datetime.now(timezone.utc))

This is an inappropriate use of any. It does short circuit, but it isn't even getting called here, because you were first building a tuple with the result of both tests. The intended behaviour can be written self.ts_end is None or self.ts_end > datetime.now(timezone.utc), since or evaluates its left subexpression first. You could also build an iterable that evaluates as needed instead of a tuple, e.g.:

def checker():
    yield self.ts_end is None
    yield self.ts_end > datetime.now(timezone.utc)
return any(checker())

The expected behaviour occurs in lazy languages like Haskell, where structures can evaluate on demand. Python uses more explicit evaluation order, more like C.

You're using a tuple inside your any, so the tuple gets evaluated first, there's no short-circuiting since the value passed (the tuple) is not a generator or iterable. The tuple contents get evaluated before getting to the use of any. You can understant the execution happening in this order:

  1. self.ts_end is None
  2. self.ts_end > datetime.now(timezone.utc)
  3. any(...)
  4. return
Related