Where may an inline `(?i)` flag appear in a Python `re` pattern, and what does `(?i:...)` do?
answer
- Two forms, distinguished by a colon
- One applies to the entire pattern
- Position stopped being tolerated in 3.11
- The colon form confines the flag
- `(?-i:...)` turns one back off
basics
~20 sA bare (?i) is a global flag: it applies to the whole pattern, and since Python 3.11 it must sit at the very start or compilation raises re.error. (?i:...) is the scoped form and applies only inside its own group.
solid answer
~50 sInline flags come in two shapes. `(?aiLmsux)` — any combination of those letters — is the **global** form: it sets the flag for the entire pattern no matter where it is written, which is precisely why Python now insists it be written where it takes effect. It was deprecated in a non-initial position in 3.6 and, since **Python 3.11**, `re.compile(r"abc(?i)")` raises `re.error: global flags not at the start of the expression`. The **scoped** form `(?i:...)`, added in 3.6, applies the flag only to the sub-pattern inside it, and `(?-i:...)` turns a flag off for that span; both are non-capturing. So `(?i:red)-green` matches `RED-green` but not `RED-GREEN`. Scoped flags are what you want when you compose a pattern from fragments — a case-insensitive keyword list embedded in an otherwise case-sensitive pattern — and they compose with the `flags=` argument of `re.compile`.
code
pycon · 7 lines>>> import re
>>> re.search(r"(?i)abc", "ABC").group()
'ABC'
>>> re.compile(r"abc(?i)")
Traceback (most recent call last):
...
re.error: global flags not at the start of the expression at position 3go deeper
Recall that a flag can be written inside the pattern as (?i) rather than passed as an argument, and that when written that way it belongs at the very beginning. Preferring the flags= argument in your own code is the safe habit here.
Explain the global versus scoped distinction and why position stopped being tolerated: a global flag always applied to the whole pattern, so writing it in the middle only misled the reader. Be able to show (?i:...) confining the flag and (?-i:...) reversing it.
Show why scoping matters where patterns are assembled from fragments — a fragment carrying a bare global flag silently changes its host or fails to compile — and treat pattern text arriving from configuration as input to be validated rather than trusted.
Decide whether rule patterns in your systems are authored as opaque strings at all, and if so what the contract is: which flags a fragment may set, whether composition is allowed, and who reviews them. A pattern language accepted from configuration is an interface with a versioning story.
### Two syntaxes that look almost identical Python's `re` lets you write flags inside the pattern itself rather than passing them as an argument. There are two constructs and the difference between them is a colon. * `(?aiLmsux)` — **global inline flags.** Each letter corresponds to a module flag: `a` = `re.ASCII`, `i` = `re.IGNORECASE`, `L` = `re.LOCALE` (bytes patterns only), `m` = `re.MULTILINE`, `s` = `re.DOTALL`, `u` = `re.UNICODE` (the default for `str`), `x` = `re.VERBOSE`. This form applies to the **whole pattern**, not to the text that follows it. * `(?imsx-imsx:...)` — **scoped inline flags**, added in Python 3.6. The flags before the `-` are enabled and those after it are disabled, for the sub-pattern inside the group only. The group does not capture. ### Why the global form became an error Because a global flag applies to the entire pattern regardless of position, writing it halfway through is actively misleading: the reader assumes the flag starts there, and the engine has already applied it from the first character. Python 3.6 began emitting a `DeprecationWarning` for a global inline flag that is not at the start of the expression, and **Python 3.11 turned that into a hard error**: ```pycon >>> import re >>> re.compile(r"abc(?i)") Traceback (most recent call last): ... re.error: global flags not at the start of the expression at position 3 ``` The fix is either to move it to the front — `(?i)abc` — or, far better if you did mean "only this part", to use the scoped form. ### What scoping buys you ```python import re re.search(r"(?i:red)-green", "RED-green") # matches re.search(r"(?i:red)-green", "RED-GREEN") # None re.search(r"(?i)(?-i:red)-green", "red-GREEN").group() # 'red-GREEN' ``` The first two lines show `i` confined to the `red` fragment. The third shows the inverse: the whole pattern is case-insensitive, and `(?-i:red)` carves out one span that is not. This matters most when a pattern is **assembled** rather than written whole. Suppose a rule engine builds a matcher by interpolating a keyword fragment supplied elsewhere into a fixed skeleton. If the fragment carries a bare `(?i)`, it silently makes the entire assembled pattern case-insensitive — or, since 3.11, blows up at compile time because the interpolation put it in the middle. If the fragment carries `(?i:...)`, it means what it looks like, and the surrounding pattern keeps its own semantics. That property — a fragment that cannot leak its flags into its host — is the real argument for the scoped form, and it is worth making it a convention wherever patterns are composed from parts. It is also the reason to be wary of accepting pattern *text* from configuration at all: flags are one of several things a fragment can change about its host. ### Inline versus the flags argument Flags passed to `re.compile`, `re.search` and friends are combined with the bitwise OR: `re.compile(p, re.IGNORECASE | re.MULTILINE)`. Inline global flags are additive with those — a pattern that begins `(?m)` and is compiled with `re.IGNORECASE` gets both. The compiled pattern object reports its effective flag set, which is a quick way to confirm what you actually got, and you will notice `re.UNICODE` in there for any `str` pattern because it is implied. When do you use which? Prefer the `flags=` argument: it is visible at the call site, it survives someone reformatting the pattern, and tools read it easily. Reach for inline flags when the pattern must travel as a self-contained string — stored in configuration, passed through an API, embedded in a list of rules — because then the flag has nowhere else to live. And reach for the scoped form whenever the intent really is "just this part". ### The one inline flag everyone should use `re.VERBOSE` (`x`, inline `(?x)`) deserves a mention because it changes how the pattern *source* is read: whitespace in the pattern is ignored, and `#` starts a comment to end of line, so a pattern can be written over several lines with explanations. Two details catch people out: whitespace **inside a character class is still significant**, and a space you actually want to match must be escaped as `\ ` or written as `[ ]`. For any pattern longer than a line or two, VERBOSE plus comments is the difference between a maintainable rule and a write-only one. ### What to say in an interview Name the two forms, say that the bare one is global regardless of position, give the 3.11 error as the reason nobody should be writing it mid-pattern any more, and finish with the composition argument for `(?i:...)`. That last point is what makes this a working-engineer answer rather than a syntax recital.
- How do inline flags interact with the `flags` argument passed to `re.compile`?They add together. A pattern beginning `(?m)` compiled with `re.IGNORECASE` gets both MULTILINE and IGNORECASE, and the compiled pattern object reports the combined set. Module-level flags are themselves combined with the bitwise OR, as in `re.IGNORECASE | re.DOTALL`. For a `str` pattern you will also see `re.UNICODE`, which is implied.
- What exactly does `re.VERBOSE` ignore in a pattern, and what does it not ignore?It ignores whitespace in the pattern source and treats `#` as a comment to end of line, so a long pattern can be laid out over several lines. It does **not** ignore whitespace inside a character class, and a space you want to match literally must be escaped as `\ ` or written as `[ ]`. Nothing about what the pattern matches otherwise changes.
- Why is it risky to interpolate a pattern fragment from configuration into a larger pattern?A fragment can change its host in ways the host never agreed to: a bare `(?i)` sets a global flag for the whole assembled pattern (or, since 3.11, fails to compile because interpolation moved it off the front), and unbalanced or nested constructs can change grouping and matching cost. If fragments must be composable, require the scoped `(?i:...)` form and validate what you accept.
saying these in an interview costs you the question
- Thinks a bare `(?i)` affects only the pattern text after it
- Writes `(?i)` mid-pattern and expects it to compile
- Believes `(?:...)` is what disables case sensitivity
- Says `(?i:...)` creates a numbered capture group
- Assumes `re.VERBOSE` also ignores whitespace inside a character class