# Python Operators: What Each One Returns, and Where It Bites

> Python operators explained with outputs for every example, plus why is works for 256 but fails for 257 and why and/or rarely return booleans.

- **Author:** Sivaranjani S — Cloud Operations Engineer, Zoho (https://www.1stepgrow.com/authors/sivaranjani-s/)
- **Published:** Jul 17, 2026 · **Updated:** Sep 17, 2026
- **Topic:** Python & Programming · **Format:** Guide · **Read time:** 9 min
- **Canonical URL:** https://www.1stepgrow.com/articles/python-operators/

## Key takeaways

- Use == for value equality and reserve is for None and other singletons — is compares identity, not value.
- Integer caching makes `is` appear to work for small numbers and fail for large ones. Never rely on it.
- and/or return one of their operands, not a boolean, which enables the default-value idiom.
- Chained comparisons like 0 <= x < 10 work as mathematical notation and are idiomatic.
- On a list, += mutates the object in place, so every other name bound to that list sees the change.

Python operators are the symbols and keywords that combine values: arithmetic, comparison, logical, membership, identity, bitwise and assignment. Most take a minute to learn. Three cause real bugs, though: `is` passing for 256 and failing for 257, `and`/`or` returning an operand instead of `True`/`False`, and `+=` quietly changing a list that another name also points to.

This guide is for anyone who writes basic Python and wants to predict what an expression returns before running it. Every example shows its output, and the last section lists the five operator mistakes worth checking for in code review. It sits between [Python strings](https://www.1stepgrow.com/articles/python-strings) and [user-defined functions](https://www.1stepgrow.com/articles/python-functions) in the Python series.

## Python operators at a glance

| Category | Operators | Returns |
|---|---|---|
| Arithmetic | `+ - * / // % ** @` | a number (`@` is matrix multiplication, used by NumPy) |
| Comparison | `== != < <= > >=` | `bool` |
| Logical | `and or not` | an operand (`not` returns `bool`) |
| Identity | `is`, `is not` | `bool` |
| Membership | `in`, `not in` | `bool` |
| Bitwise | `& \| ^ ~ << >>` | an int (elementwise in NumPy and pandas) |
| Assignment | `= += -= *= :=` | nothing, except `:=` which returns the value |

## Arithmetic operators: why / returns a float

The arithmetic operators behave like a calculator except for division: `/` always returns a float, and `//` rounds down toward negative infinity rather than toward zero.

```python
7 + 3       # 10
7 - 3       # 4
7 * 3       # 21
7 / 3       # 2.3333333333333335   - always a float
7 // 3      # 2                    - floor division
7 % 3       # 1                    - remainder
7 ** 3      # 343                  - power
```

Two things surprise people.

**`/` always returns a float**, even when it divides evenly. For example, `6 / 3` is `2.0`, not `2`. Use `//` when you want an integer.

**`//` floors toward negative infinity**, which is not truncation:

```python
7 // 2      # 3
-7 // 2     # -4     - not -3
int(-7 / 2) # -3     - truncation, if that is what you wanted
```

The modulo follows the same rule, so `-7 % 2` is `1`, not `-1`. This is deliberate and useful, because `x % n` always lands in `[0, n)` for positive `n`, which makes it safe for cyclic indexing.

Also watch floating point:

```python
0.1 + 0.2            # 0.30000000000000004
0.1 + 0.2 == 0.3     # False

import math
math.isclose(0.1 + 0.2, 0.3)     # True
```

For money, use `decimal.Decimal` — see our [Python numeric data types guide](https://www.1stepgrow.com/articles/python-numeric-data-types).

## How do chained comparisons work?

A chain such as `a < b <= c` means `a < b and b <= c`, except that `b` is evaluated only once:

```python
5 == 5.0      # True  - value equality across types
5 != 3        # True
3 < 5 <= 5    # True  - chained, reads mathematically
```

The [comparisons section of the language reference](https://docs.python.org/3/reference/expressions.html#comparisons) defines this exactly. Chaining also short-circuits, so `0 <= index < len(items)` is both idiomatic and efficient. Writing `0 <= index and index < len(items)` is longer and no clearer.

## What is the difference between is and == in Python?

`==` compares value, while `is` compares identity. This distinction produces real bugs.

```python
a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b      # True  - same contents
a is b      # False - different objects
a is c      # True  - same object
```

Now the trap:

```python
x = 256
y = int("256")
x is y      # True  - small ints are cached

x = 257
y = int("257")
x is y      # False - a fresh object
```

As of September 2026 (Python 3.14.7), the [CPython integer-object documentation](https://docs.python.org/3/c-api/long.html) still describes an array of pre-allocated integers from -5 to 256, so small values share objects, while larger ones usually do not. The same applies to short strings via interning. To make things murkier, if you write `x = 257` and `y = 257` in the same script, the compiler may reuse one constant and `x is y` becomes `True`. Since Python 3.8, writing `x is 257` directly also triggers a `SyntaxWarning` asking whether you meant `==`.

As a result, `is` on numbers appears to work in testing and fails in production with real data. **Use `==` for values.**

Reserve `is` for `None` and other singletons, as [PEP 8's programming recommendations](https://peps.python.org/pep-0008/#programming-recommendations) advise:

```python
value = None
if value is None:
    print("missing")
if value is not None:
    print("present")

flag = True
if flag:                  # not "if flag is True" or "if flag == True"
    print("enabled")
```

`if value == None` works but is non-idiomatic, and linters will flag it. Similarly, PEP 8 calls `if flag is True:` worse than `== True`; a plain truth test is the right form.

## What do and and or return in Python?

`and` and `or` do not return `True`/`False`. Instead, they return one of their operands, as the [boolean operations reference](https://docs.python.org/3/reference/expressions.html#boolean-operations) states:

```python
"hello" and "world"     # 'world'  - both truthy, returns the last
"" and "world"          # ''       - first is falsy, short-circuits
"hello" or "world"      # 'hello'  - first truthy wins
"" or "default"         # 'default'
None or 0 or "found"    # 'found'  - first truthy
```

That behaviour gives the default-value idiom:

```python
user_input = ""
name = user_input or "Anonymous"          # 'Anonymous'
```

There is one important caveat: this treats every falsy value as absent. If `0` or `""` are legitimate values, use an explicit `None` check:

```python
config = {"timeout": 0}

# bug: a timeout of 0 becomes 30
timeout = config.get("timeout") or 30     # 30

# correct
timeout = config.get("timeout")
if timeout is None:
    timeout = 30                          # stays 0
```

Short-circuiting also guards against errors:

```python
user = None
data = []
if user is not None and user.is_active:      # safe - no AttributeError
    print("active")
if data and data[0] > 10:                     # no IndexError on empty
    print("big first value")
```

## How do in and not in work?

`in` tests containment: an element of a list or set, a key of a dict, or a substring of a string. `not in` is its negation.

```python
3 in [1, 2, 3]              # True
"key" in {"key": 1}         # True - checks KEYS, not values
"ell" in "hello"            # True - substring test
5 not in {1, 2, 3}          # True
```

Note the complexity difference, though. `in` on a list is O(n), whereas on a set or dict it is O(1) on average. Converting a list to a set before repeated membership tests is therefore one of the easiest performance wins there is:

```python
id_list = [101, 102, 103]
valid_ids = set(id_list)                           # once
[r for r in [101, 999, 103] if r in valid_ids]     # [101, 103]
```

## Bitwise operators, and the pandas gotcha

```python
5 & 3       # 1   - AND
5 | 3       # 7   - OR
5 ^ 3       # 6   - XOR
~5          # -6  - NOT
5 << 1      # 10  - left shift
```

These matter in pandas and NumPy, where `&`, `|` and `~` are the **elementwise** operators and `and`/`or`/`not` fail:

```python
df[(df.age > 25) & (df.city == "Pune")]     # correct
df[df.age > 25 and df.city == "Pune"]        # ValueError: truth value is ambiguous
```

The parentheses are required because `&` binds more tightly than `>`. See our [NumPy indexing guide](https://www.1stepgrow.com/articles/numpy-indexing-slicing) for the full explanation.

## Augmented assignment: += is not always a rebind

For immutable types, `x += 1` builds a new object and rebinds the name. For a list, however, `+=` extends the existing object in place:

```python
a = [1, 2]
b = a
a += [3]
print(b)      # [1, 2, 3] - b sees the change

a = [1, 2]
b = a
a = a + [3]
print(b)      # [1, 2]    - a now names a new list
```

This is the same names-versus-objects model covered in [Python basics](https://www.1stepgrow.com/articles/python-basics).

## When is the walrus operator (:=) worth using?

Use `:=` when you need to test a value and keep it in the same step. It assigns inside an expression, and was added in Python 3.8. The language reference calls it an [assignment expression](https://docs.python.org/3/reference/expressions.html#assignment-expressions):

```python
import re
pattern = re.compile(r"id=(\d+)")
line = "user id=42"

# without
match = pattern.search(line)
if match:
    print(match.group(1))

# with
if (match := pattern.search(line)):
    print(match.group(1))     # 42
```

It is genuinely useful in `while` loops and comprehensions where you would otherwise compute twice:

```python
import io
f = io.StringIO("x" * 20000)
while (chunk := f.read(8192)):
    print(len(chunk))         # 8192, 8192, 3616

data = ["1", "x", "3"]
results = [n for s in data if (n := int(s) if s.isdigit() else None) is not None]
# [1, 3]
```

Use it where it removes duplication. Do not use it to compress two clear lines into one dense one.

## Which operator runs first? Precedence in practice

Among the common Python operators, `**` binds tightest, then unary minus, then `*`, `/`, `//` and `%`, then `+` and `-`; comparisons come next, and `not`, `and` and `or` bind loosest. Most surprises come from the edges of that order:

```python
2 + 3 * 4        # 14, not 20
2 ** 3 ** 2      # 512  - power is right-associative: 2**(3**2)
-2 ** 2          # -4   - unary minus binds looser than ** on its left
not True == False   # True - == binds tighter than not
```

The full [operator precedence table](https://docs.python.org/3/reference/expressions.html#operator-precedence) is in the language reference. Still, the practical advice is not to memorise it. Add parentheses when the answer is not instantly obvious to a reader, because it costs nothing and prevents an entire class of bug.

## Common mistakes with Python operators

- **Using `is` to compare numbers or strings.** It depends on caching; use `==`.
- **Using `and`/`or` on pandas Series.** Use `&`/`|` with parentheses.
- **Relying on `or` for defaults when `0` is valid.** Check `is None` instead.
- **Expecting `/` to return an int.** Use `//`, and remember it floors negatives.
- **Forgetting that `+=` mutates lists in place.** Other names bound to the list see the change.

Next in the series, [user-defined functions](https://www.1stepgrow.com/articles/python-functions) shows how to package these expressions into reusable code.



## Related reading

[Python numeric types](https://www.1stepgrow.com/articles/python-numeric-data-types) covers int, float and Decimal in depth. [Python basics](https://www.1stepgrow.com/articles/python-basics) covers the core types these operate on.

## Frequently asked questions

### What is the difference between == and is?

== asks whether two objects have the same value; is asks whether they are literally the same object in memory. Use == for comparisons and reserve is for None and other singletons such as sentinel objects. For booleans, PEP 8 recommends plain truth tests like if flag: rather than comparing with is True or == True.

### Why does `a is b` return True for 256 but False for 257?

CPython pre-allocates small integers from -5 to 256, so identical small values share one object. Larger integers usually get fresh objects, although the compiler may reuse a constant within one script. This is an implementation detail, not a language guarantee, and it is exactly why you should never use is to compare numbers.

### What does the walrus operator do?

The walrus operator, :=, assigns a value inside an expression and was added in Python 3.8. It helps when you need to both test and keep a value, such as if (m := pattern.search(line)):, which avoids a separate assignment line and a second search. Use it where it removes duplication, not to squeeze two clear lines into one.

### What is the difference between / and //?

/ is true division and always returns a float, even when the result is whole. // is floor division, returning the largest integer less than or equal to the result. Note that it floors toward negative infinity, so -7 // 2 is -4, not -3. If you want truncation toward zero instead, use int(-7 / 2).

---
_Source: 1stepGrow (https://www.1stepgrow.com/articles/python-operators/). Cite with the title, "1stepGrow" and a link._
