I have found an interesting performance optimization. Instead of using all():
def matches(self, item):
    return all(c.applies(item) for c in self.conditions)
I have profiled that it's faster when a loop is used instead:
def matches(self, item):
    for condition in self.conditions:
        if not condition.applies(item):
            return False
    return True
With all() the profiler shows 1160 additional <genexpr> calls:
         4608 function calls (4600 primitive calls) in 0.015 seconds
   Ordered by: internal time
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      580    0.002    0.000    0.008    0.000 rule.py:23(matches)
     1160    0.002    0.000    0.005    0.000 rule.py:28(<genexpr>)
With the for loop, there are no <genexpr> calls:
         2867 function calls (2859 primitive calls) in 0.012 seconds
   Ordered by: internal time
   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      580    0.002    0.000    0.006    0.000 rule.py:23(matches)
My question is where does the difference come from? My first though was that all evaluates all conditions, but that's not the case:
def foo():
    print('foo')
    return False
all(foo() for _ in range(1000))
foo
 
    