Skip to content
QuantMax
QuantMax
  • Overview
  • Curriculum
    • FLUMental maths and numerical fluency
    • MKTMarkets and products
    • CSData structures and algorithms
    • PYPython and data for quants
      • 1Python fundamentals

        • Idiomatic Python: generators, itertools and the gotchas
      • 2NumPy

        • NumPy: broadcasting, axes and vectorisation
      • 3pandas

        • pandas: groupby, joins, resampling and time zones
      • 4Market data handling

        • Market data in pandas, and where look-ahead hides
      • 5Vectorised backtesting

        • Vectorised backtesting: signal to position to P&L
      • 6Performance

        • Performance: profiling, vectorising and when to leave Python
    • NUMNumerical methods
    • SYSSystems and low latency

Practise

  • Question bank
  • Mental arithmetic
  • Market simulator
  • Arbitrage trees
  • Horse racing
  • Bid book
  • Screening tests
  • Mock papers

Reference

  • Formula reference
  • Search

Your record

  • Review queue
  • Progress
  • Leaderboard
  • Profile
  • Invite friends
AccountSend feedback
  1. Curriculum
  2. /Quantitative development
  3. /Python and data for quants
  4. /Python fundamentals

Idiomatic Python: generators, itertools and the gotchas

PY · Chapter 1·11 min read·Asked at Two Sigma, Citadel Securities, Jump, QuantCo

After this lesson you should be able to

  • Use generators and know when laziness matters.
  • Recognise the mutable-default and late-binding traps.
  • Reach for the right tool in collections and itertools.

Python is the working language of quantitative research, and interviewers use it to check two things: whether you write code that reads well, and whether you know where the language will quietly betray you. Both are a short list.

Proposition 1.1

Generators and laziness

A generator produces values on demand rather than building a list, so it uses constant memory and can represent an unbounded sequence. On a large file or a long market-data stream that is the difference between working and exhausting memory.

Holds when

  • A generator expression is a comprehension with parentheses: sum(x*x for x in data) never builds the list.
  • They are single-pass — consuming one exhausts it, and a second loop sees nothing.
  • yield from delegates to another generator and is the clean way to compose them.
TrapWhat happensFix
Mutable default argumentThe default is created once and shared across callsDefault to None and create inside
Late binding in closuresLoop variables are read at call time, not definitionBind with a default argument
is versus ==Identity, not equality; small ints are cached and confuse the issueUse == except for None
Modifying a list while iteratingElements are skipped silentlyIterate over a copy, or build a new list
Shallow copy of nested structuresInner objects are still sharedcopy.deepcopy, or restructure
Table 1.2 · The gotchas. The first two are the ones asked about most, because both produce code that works in testing and fails in a way that looks like a logic error rather than a language feature.

Why mutable defaults behave that way. A default argument is evaluated once, when the def statement runs, not each time the function is called. So a list written as a default is a single object living on the function, and every call that relies on the default mutates the same one. It is not a bug so much as a consequence of functions being ordinary objects with attributes — and knowing *why* means you will also predict the behaviour of a default built from a computation, which is evaluated once for the same reason.

# Mutable default: the same list is reused across calls.
def bad(x, acc=[]):
    acc.append(x)
    return acc

bad(1)      # [1]
bad(2)      # [1, 2]  -- surprising

def good(x, acc=None):
    acc = [] if acc is None else acc
    acc.append(x)
    return acc

# Late binding: every lambda reads i at call time.
fns = [lambda: i for i in range(3)]
[f() for f in fns]            # [2, 2, 2]

fns = [lambda i=i: i for i in range(3)]
[f() for f in fns]            # [0, 1, 2]
Listing 1.3 · The two classic traps. Both examples run without error and return the wrong thing, which is what makes them worth recognising on sight. Time n/a · Space n/a.

The rest of this lesson is in Premium

You have read the opening. 11 more sections follow, including 4 worked examples and 3 quick checks.

Start the free 7-day trialSign in

Nothing is charged for 7 days, and you can cancel before then. Or read Complexity: reading it off, and deriving it in full, free.

NumPy: broadcasting, axes and vectorisation →
On this page
  • Generators and laziness
  • The gotchas
  • The two classic traps

QuantMax · 141 lessons · 1342 questions · c5c0caa

  • Premium
  • Arbitrage trees
  • Horse racing
  • Invite friends
  • Account
  • About QuantMax

Firm names identify publicly reported question patterns and nothing more. QuantMax is not affiliated with, endorsed by, or recruiting for any firm named in the curriculum. Everything you do in lessons and the question bank is kept to your account.