Composable Predicates with the &, |, ~ Operators in Python

Define a reusable Predicate class that combines boolean checks with & (AND), | (OR), and ~ (NOT) operators.

Medium Python 3.9+ Aug 9, 2026 OOP & classes 13 views 0 copies

Python code

33 lines
Python 3.9+
class Predicate:
    def __init__(self, func, name=None):
        self.func = func
        self.name = name or getattr(func, "__name__", "predicate")

    def __call__(self, value):
        return self.func(value)

    def __and__(self, other):
        return Predicate(lambda v: self(v) and other(v), f"({self.name} AND {other.name})")

    def __or__(self, other):
        return Predicate(lambda v: self(v) or other(v), f"({self.name} OR {other.name})")

    def __invert__(self):
        return Predicate(lambda v: not self(v), f"(NOT {self.name})")

    def __repr__(self):
        return self.name


is_even = Predicate(lambda n: n % 2 == 0, "is_even")
is_positive = Predicate(lambda n: n > 0, "is_positive")
is_small = Predicate(lambda n: n < 10, "is_small")

is_positive_even = is_even & is_positive
is_small_or_negative_even = is_small | (~is_positive)

if __name__ == "__main__":
    values = [0, 2, 5, -4, 12, 7]
    print("Predicates:", is_positive_even, "and", is_small_or_negative_even)
    for v in values:
        print(v, "->", is_positive_even(v), is_small_or_negative_even(v))

Output

stdout
Predicates: (is_even AND is_positive) and (is_small OR (NOT is_positive))
0 -> False False
2 -> True False
5 -> False True
-4 -> False True
12 -> True False
7 -> False True

How it works

This class wraps a function in a callable object and implements __and__, __or__, and __invert__ so Python's &, |, and ~ operators work as logical AND, OR, and NOT. Each operation returns a new Predicate, keeping the original predicates untouched and enabling chains like a & b | c. The name parameter provides readable labels used in repr, which makes debugging and logging much easier.

Common mistakes

  • Forgetting that `and`/`or` cannot be overloaded; you must use `&`/`|`.
  • Modifying the original predicate when chaining; always return a new instance.
  • Not preserving the function name as a fallback display name.

Variations

  1. Use `functools.partial` or a decorator to create predicate factories without a class.
  2. Implement `__and__` etc. to accept other callables, not just Predicate instances.

Real-world use cases

  • Build a rules engine that combines eligibility checks with AND/OR without nested if-statements.
  • Filter a large dataset using composable criteria, e.g., in a data pipeline or query builder.
  • Express complex feature flags as reusable, combinable conditions in a microservice.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from OOP & classes

Related tutorials and quizzes for this topic.