QuestenaPractice that shows what to review next
Topic lesson

Common pitfalls: track objects, operators and name lookup

About 5 min

Questions stay in the language in which they were published.

These questions collect Python behaviours that often look plausible for the wrong reason. Classify the issue first: object lifetime, division rule, value versus identity, or name lookup.

Use four separate checks

For defaults, attach the default object to the def event, not to each call. If it is mutable, every omitted call receives that same object and can observe earlier mutations. The safe sentinel pattern uses None as the default and creates a fresh list during each omitted call, as in def collect(items=None): items = [] if items is None else items; return items. A list supplied explicitly remains the caller's object.

For division, read the operator before calculating. In value = 9 / 3, slash performs true division and produces a float even when the quotient is whole. Floor division chooses the greatest integer no larger than the exact quotient, so negative results move down rather than towards zero; low = -7 // 3 therefore follows the floor rule. Both operators reject a numeric zero divisor.

For comparisons, ask whether the program needs equal values or one shared object. The expression left == right uses the type's value comparison, while left is right asks whether both references designate exactly the same object. Identity is appropriate for the singleton None, as in result is None. It is not a dependable general value comparison for integers or strings because object reuse is an implementation detail.

For names, follow normal scope lookup. An assignment can hide a built-in: after str = "label", the local name str no longer reaches the constructor. The original remains in the builtins module, so import builtins; builtins.str(7) still reaches it. Renaming the local binding is usually the clearest repair.

Reject these tempting routes

Tempting routeHow to reject it
Create or copy a mutable default for every omitted callThe object was created when def ran. Mutations accumulate across calls that omit the argument.
Change a whole true-division result back into an integerSlash determines the operation and result family; integer inputs still produce a float.
Return zero or infinity when a division operator receives zeroTrue division and floor division both reject a numeric zero divisor by raising an exception.
Use equality to mean one object, or identity to mean equal valuesChoose == for the type's value semantics and is for reference identity.
Generalise the reliable None identity check to integer or string valuesNone is a singleton. Reuse of other immutable objects may vary and is not a value-comparison contract.
Assume a built-in name cannot be rebound or will win automaticallyA nearer user binding wins normal lookup, even when it is not callable. Use a different local name or reach the constructor through builtins.

Try it

Question 111

Classify the None-sentinel pattern when a function creates a new list for None and otherwise uses the received object. Which statements are true? Select all that apply.

  1. None becomes one shared mutable list automatically.
  2. Each omitted call can receive a newly created list.
  3. The pattern still reuses one hidden list across calls.
  4. An explicitly supplied list can still be used as supplied.
Question 113

What is the value of -3 // 2?

  1. -2
  2. -1
  3. 1
  4. -1.5
Question 120

After import builtins and list = [], classify the next operations. Which statements are true? Select all that apply.

  1. list("ab") raises TypeError.
  2. list("ab") automatically calls the built-in constructor.
  3. Shadowing permanently removes the built-in from Python.
  4. builtins.list("ab") returns ['a', 'b'].
Start drillPractice this topic
Common pitfalls: track objects, operators and name lookup · Questena