Combine two per-row boolean tests with Python's and in a loop to build a single mask, then keep matching rows. With pandas, element-wise & combines two boolean Series before indexing the DataFrame — and and or are not valid for Series and will raise an error.

By hand

Loop over records, apply both conditions with and, and append the combined bool to mask. Loop over indices to collect names where mask[i] is True.

naive.py
Replay: real traced execution (multi-file project)
# trace: ignore records
records = [
    {'name': 'al', 'age': 20, 'score': 55},
    {'name': 'bo', 'age': 25, 'score': 82},
    {'name': 'cy', 'age': 22, 'score': 71},
    {'name': 'di', 'age': 28, 'score': 90},
]
lo, hi = 21, 88
mask = []
for r in records:
    mask.append(r['age'] > lo and r['score'] < hi)
names = []
for i in range(len(records)):
    if mask[i]:
        names.append(records[i]['name'])
print('RESULT:', names)
  1. {'name': 'al', 'age': 20, 'score': 55},

    2records = [3    {'name': 'al', 'age': 20, 'score': 55},4    {'name': 'bo', 'age': 25, 'score': 82},
  2. {'name': 'bo', 'age': 25, 'score': 82},

    3{'name': 'al', 'age': 20, 'score': 55},4{'name': 'bo', 'age': 25, 'score': 82},5{'name': 'cy', 'age': 22, 'score': 71},
  3. {'name': 'cy', 'age': 22, 'score': 71},

    4{'name': 'bo', 'age': 25, 'score': 82},5{'name': 'cy', 'age': 22, 'score': 71},6{'name': 'di', 'age': 28, 'score': 90},
  4. {'name': 'di', 'age': 28, 'score': 90},

    5    {'name': 'cy', 'age': 22, 'score': 71},6    {'name': 'di', 'age': 28, 'score': 90},7]
  5. records = [

    1# trace: ignore records2records = [3    {'name': 'al', 'age': 20, 'score': 55},
  6. hi ← 88, lo ← 21

    7]8lo, hi = 21, 889mask = []
    values this step88hi21lo
  7. mask ← []

    8lo, hi = 21, 889mask = []10for r in records:
    values this step[]mask
  8. r ← {'name': 'al', 'age': 20, 'score': 55}

    9mask = []10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)
    values this step{'name': 'al', 'age': 20, 'score': 55}r
  9. mask ← [False]

    10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)12names = []
    values this step[] [False]mask
  10. r ← {'name': 'bo', 'age': 25, 'score': 82}

    9mask = []10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)
    values this step{'name': 'al', 'age': 20, 'score': 55} {'name': 'bo', 'age': 25, 'score': 82}r
  11. mask ← [False, True]

    10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)12names = []
    values this step[False] [False, True]mask
  12. r ← {'name': 'cy', 'age': 22, 'score': 71}

    9mask = []10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)
    values this step{'name': 'bo', 'age': 25, 'score': 82} {'name': 'cy', 'age': 22, 'score': 71}r
  13. mask ← [False, True, True]

    10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)12names = []
    values this step[False, True] [False, True, True]mask
  14. r ← {'name': 'di', 'age': 28, 'score': 90}

    9mask = []10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)
    values this step{'name': 'cy', 'age': 22, 'score': 71} {'name': 'di', 'age': 28, 'score': 90}r
  15. mask ← [False, True, True, False]

    10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)12names = []
    values this step[False, True, True] [False, True, True, False]mask
  16. for r in records:

    9mask = []10for r in records:11    mask.append(r['age'] > lo and r['score'] < hi)
  17. names ← []

    11    mask.append(r['age'] > lo and r['score'] < hi)12names = []13for i in range(len(records)):
    values this step[]names
  18. i ← 0

    12names = []13for i in range(len(records)):14    if mask[i]:
    values this step0i
  19. if mask[i]:

    13for i in range(len(records)):14    if mask[i]:15        names.append(records[i]['name'])
  20. i ← 1

    12names = []13for i in range(len(records)):14    if mask[i]:
    values this step0 1i
  21. if mask[i]:

    13for i in range(len(records)):14    if mask[i]:15        names.append(records[i]['name'])
  22. names ← ['bo']

    14    if mask[i]:15        names.append(records[i]['name'])16print('RESULT:', names)
    values this step[] ['bo']names
  23. i ← 2

    12names = []13for i in range(len(records)):14    if mask[i]:
    values this step1 2i
  24. if mask[i]:

    13for i in range(len(records)):14    if mask[i]:15        names.append(records[i]['name'])
  25. names ← ['bo', 'cy']

    14    if mask[i]:15        names.append(records[i]['name'])16print('RESULT:', names)
    values this step['bo'] ['bo', 'cy']names
  26. i ← 3

    12names = []13for i in range(len(records)):14    if mask[i]:
    values this step2 3i
  27. if mask[i]:

    13for i in range(len(records)):14    if mask[i]:15        names.append(records[i]['name'])
  28. for i in range(len(records)):

    12names = []13for i in range(len(records)):14    if mask[i]:
  29. stdout ← RESULT: ['bo', 'cy']

    15        names.append(records[i]['name'])16print('RESULT:', names)
    values this stepRESULT: ['bo', 'cy']stdout

With pandas

(df['age'] > lo) & (df['score'] < hi) combines two boolean Series elementwise. Parentheses around each condition are required because & has higher operator precedence than > and <.

library.py
import pandas as pd
from dalib.display import set_display
set_display()

records = [
    {'name': 'al', 'age': 20, 'score': 55},
    {'name': 'bo', 'age': 25, 'score': 82},
    {'name': 'cy', 'age': 22, 'score': 71},
    {'name': 'di', 'age': 28, 'score': 90},
]
lo, hi = 21, 88
df = pd.DataFrame(records)
mask = (df['age'] > lo) & (df['score'] < hi)
filtered = df[mask]
print('mask:', mask.tolist())
print('names:', filtered['name'].tolist())
print('shape:', filtered.shape)
print('RESULT:', filtered['name'].tolist())
mask: [False, True, True, False]
names: ['bo', 'cy']
shape: (2, 3)
RESULT: ['bo', 'cy']

Implementation notes

  • Use & (bitwise AND) and | (bitwise OR) to combine boolean Series — NOT Python's and / or. and/or call bool() on the whole Series, which raises ValueError: The truth value of a Series is ambiguous.
  • Parentheses around each condition are mandatory: (df['a'] > lo) & (df['b'] < hi). Without them, & binds tighter than > and the expression parses incorrectly.
  • This is the same operator rule as NumPy: (a > lo) & (a < hi) — see combine-two-masks (python-numpy ch06).
  • For more than two conditions, chain & or |: (c1) & (c2) & (c3). Alternatively, df.query("age > 21 and score < 88") allows Python-style and/or syntax inside a string.