Codectionary / Developer documentation / Python

Identity and equality

Distinguish two names pointing to the same object from two separate objects with equal values.

Understand the contract

Equality with == asks whether values compare equal, using the types' comparison behavior. Identity with is asks whether two references point to the very same object. Two independent lists may contain equal elements without sharing identity.

Assignment binds another name to an existing object; it does not automatically copy it. Mutating a list through either alias is therefore visible through the other. Copying the list creates a new outer object, although nested elements may still be shared.

Follow the runtime behavior

Use is None and is not None for the None singleton. Use == for strings and numbers: implementation details such as interning can make identity results misleading for immutable values.

A custom class can define equality through __eq__, but cannot redefine the meaning of is. Do not use an object address or an observed caching behavior as a value comparison contract.

Apply it and practise

Use a unique object() sentinel when missing data must differ from a valid None value. Identity provides an unambiguous way to recognize that particular sentinel.

Exercise: create one list, an alias, and a shallow copy. Predict all equality and identity comparisons before appending through the alias. Repeat with a nested list and explain why shallow copying does not isolate its child.

Syntax

left == right
left is right
value is None

Examples

Compare value and identity

Use these checks to diagnose whether two variables share mutable state.

left = [1, 2]
right = [1, 2]
alias = left
print(left == right)
print(left is right)
print(left is alias)
  1. The two list displays create separate objects with equal contents. alias receives the existing left reference, so only that identity comparison succeeds.
  2. Use these checks to diagnose whether two variables share mutable state.
  3. This experiment intentionally uses lists; small integer or string caching is not a reliable identity demonstration.
  4. Expected output: True False True

Observe an alias after mutation

Use a copy when a caller must edit a flat list without modifying the source list.

original = ["a"]
alias = original
copy = original.copy()
alias.append("b")
print(original)
print(copy)
print(original is alias)
  1. append changes the shared list in place. copy refers to a separate outer list made before the mutation, so it still has one element.
  2. Use a copy when a caller must edit a flat list without modifying the source list.
  3. copy is shallow: nested mutable objects would remain shared between the outer lists.
  4. Expected output: ['a', 'b'] ['a'] True

Distinguish missing from None

Use a sentinel when None means an explicit preference rather than an absent key.

missing = object()
settings = {"theme": None}
theme = settings.get("theme", missing)
font = settings.get("font", missing)
print(theme is None)
print(theme is missing)
print(font is missing)
  1. get returns a stored None for theme but the unique sentinel for the absent font. Identity comparisons distinguish these cases without confusing their values.
  2. Use a sentinel when None means an explicit preference rather than an absent key.
  3. Create and reuse one sentinel; comparing against a fresh object() would always miss the original sentinel.
  4. Expected output: True False True

Best practices

  • A custom class can define equality through __eq__, but cannot redefine the meaning of is. Do not use an object address or an observed caching behavior as a value comparison contract.
  • Use a unique object() sentinel when missing data must differ from a valid None value. Identity provides an unambiguous way to recognize that particular sentinel.

Runtime & versions

Python interpreter. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Python language reference

Specifications & further reading