EN
Python for Security Work
Source: tltaylor1, https://github.com/tltaylor1/anki-decks (CC-BY-4.0), converted for RepeatMe.
O que traz
- What is Python, and how does it differ from a compiled language like C?
- Python is a high-level, interpreted, dynamically-typed language. Code runs through an interpreter at runtime rather than being compiled to machine code ahead of time. (Like PowerShell, it is interpreted and object-oriented, not compiled like C.)
- What does it mean that Python is dynamically typed?
- Variable types are figured out at runtime, not declared up front. The same variable can hold an int then a string. (PowerShell is the same way; you just write $x = 5 or x = 5 without declaring a type.)
- What is duck typing?
- If an object has the methods you need, you can use it regardless of its actual class. If it walks like a duck and quacks like a duck, it is a duck. (Python cares about behavior, not the type name. PowerShell behaves similarly with its object pipeline.)
- What is CPython?
- The standard, reference implementation of Python, written in C. It compiles your source to bytecode, then interprets that bytecode. (When people say Python they almost always mean CPython.)
- What is PEP 8?
- The official style guide for Python: 4-space indentation, naming conventions, spacing, line length. (The rough equivalent of PowerShell's community style conventions, but more formalized and widely enforced by tools.)
- What is the difference between Python 2 and Python 3?
- Python 3 is current; Python 2 is dead (end of life). In 3, print is a function, division of two ints gives a float, and strings are Unicode by default. (If you see print without parentheses, that is old Python 2 code.)
- What is the REPL?
- The interactive Read-Eval-Print Loop you get by typing 'python' with no file. You type code and see results immediately. (Exactly like the interactive PowerShell console where you test commands one line at a time.)
- How do you find out what type an object is?
- type(obj) returns the object's type; you can also use isinstance(obj, SomeType) to test it. (This is the Get-Member / .GetType() habit from PowerShell; type() answers 'what is this thing.')
- How do you see what methods and attributes an object has?
- dir(obj) lists its attributes and methods. (This is the direct equivalent of PowerShell's Get-Member; it is how you explore an unfamiliar object interactively.)
- What does isinstance() do and why prefer it over type() for checks?
- isinstance(obj, int) tests whether an object is that type (and respects inheritance). Prefer it over 'type(obj) == int' because it handles subclasses correctly. (The proper way to ask 'is this a string' before you operate on it.)
- What do hasattr() and getattr() do?
- hasattr(obj, 'name') checks if an attribute exists; getattr(obj, 'name', default) fetches it safely with a fallback. (Useful when you are handed objects of varying shape, like parsed API responses.)
- How do you get help on a function or object in the interpreter?
- help(obj) prints its documentation; obj.__doc__ holds the docstring. (The Get-Help equivalent from PowerShell.)
- What is a docstring?
- A string literal right under a function/class definition that documents it, accessible via help() or __doc__. (Like comment-based help in PowerShell that feeds Get-Help.)
- What are Python's main built-in data types?
- int, float, str, bool, list, tuple, dict, set, and NoneType (None). (PowerShell has close cousins: String, Int, Bool, Array, Hashtable.)
- What is a list, and how do you create one?
- An ordered, mutable collection: my_list = [1, 2, 3]. You can change, add, and remove elements. (The Python equivalent of a PowerShell array @(1,2,3), but mutable and richer.)
- What is a tuple, and how does it differ from a list?
- An ordered, immutable collection: my_tuple = (1, 2, 3). Once made, it cannot change. Use tuples for fixed data, lists for data that changes; tuples can also be dict keys, which lists cannot. Caveat: a one-item tuple is (5,); without the comma it is just a number.
- What is a dictionary, and how do you create one?
- A collection of key-value pairs: my_dict = {'key': 'value'}. Keys are unique and lookups are fast. (This is exactly a PowerShell hashtable @{ Key = 'Value' }.)
- What is a hash table, and how does a Python dict relate to it?
- A hash table stores key-value pairs and uses a hash of the key to find its value in near-constant time. A Python dict is a hash table. (Same underlying idea as a PowerShell hashtable; the name literally comes from this structure.)
- What is a set, and what is it for?
- An unordered collection of unique elements: my_set = {1, 2, 3}. Great for deduplication and fast membership tests. (PowerShell has no direct built-in; you would fake it with a hashtable's keys.)
- What is the difference between a list and a set?
- A list is ordered and allows duplicates; a set is unordered and holds only unique items, with faster membership testing. (Reach for a set when you are asking 'have I seen this before,' like tracking unique IPs.)
- Is a string mutable in Python?
- No. Strings are immutable; any operation that looks like it changes a string actually returns a new one. (Same as .NET strings, which PowerShell also uses.)
- What is None in Python?
- None is Python's null value, meaning 'no value.' It is a single shared object of type NoneType. (The equivalent of $null in PowerShell.)
- How do you check for None correctly?
- Use 'is': if x is None. (Prefer 'is' over '==' here, just like PowerShell teaches you to write $null -eq $x to be safe.)
- What is the difference between == and is?
- == compares values (are these equal); 'is' compares identity (are these literally the same object in memory). Use 'is' only for None and singletons. (== is the everyday comparison; 'is' is about object identity.)
- What values are falsy in Python?
- False, None, 0, 0.0, '' (empty string), [] (empty list), {} (empty dict), and any empty collection. Everything else is truthy. (Handy: 'if my_list:' is true only when the list has items.)
- How do you convert a string to an int, and what can go wrong?
- int('123') converts it; str(123) goes back. int('abc') raises ValueError. (This is casting, like [int]'123' in PowerShell; wrap it in try/except when the input is untrusted.)
- What is the difference between mutable and immutable types?
- Mutable types (list, dict, set) can be changed in place. Immutable types (int, str, tuple) cannot; operations create new objects. (Knowing which is which prevents a lot of surprise bugs.)
- What is the difference between a shallow copy and a deep copy?
- A shallow copy duplicates the outer object but shares the nested objects inside. A deep copy (copy.deepcopy) recursively duplicates everything. (If you have a list of lists and only shallow-copy it, editing an inner list still affects both.)
- Why can modifying a list inside a function change the caller's list?
- Lists are mutable and passed by reference, so the function edits the same object. Reassigning the name inside the function does not affect the caller. (A common gotcha; if you want to be safe, copy the list first.)
- Why is a mutable default argument like def f(x=[]) dangerous?
- The default list is created once and reused across every call, so it accumulates data between calls. Use x=None and create the list inside the function. (One of Python's most famous traps.)
- What is the difference between assigning a list and copying it?
- b = a makes both names point to the SAME list, so editing one edits both. b = a.copy() or b = list(a) makes an independent copy. (Assignment shares the reference; it does not duplicate the data.)
- How do you write an if/elif/else?
-
if cond:
...
elif other:
...
else:
... Indentation defines the blocks; there are no braces. (PowerShell uses braces; Python uses whitespace instead.) - How do you write a for loop over a list?
-
for item in my_list:
print(item). It iterates the items directly. (Like PowerShell's foreach ($item in $list), but the default and only 'for' style.) - How do you loop with both index and value?
- Use enumerate: for i, value in enumerate(my_list). It yields index-value pairs. (Cleaner than tracking a counter yourself.)
- What does range() do?
- Produces a sequence of numbers lazily: range(5) is 0..4; range(2,10,2) is 2,4,6,8. (Used to loop a set number of times; it does not build a full list in memory.)
- How do you exit a loop early, and how do you skip one iteration?
- break exits the loop entirely; continue skips to the next iteration. (Same keywords and meaning as PowerShell.)
- What is a while loop and when do you use it?
- Repeats while a condition holds: while cond:. Use it when you do not know the iteration count in advance. (Same concept as PowerShell's while.)
- What does zip() do?
- Pairs up items from multiple iterables: zip([1,2],['a','b']) yields (1,'a'),(2,'b'). (The go-to when you need to walk two lists together, like usernames and their statuses.)
- What is a list comprehension?
- A compact way to build a list: [x*2 for x in range(5)]. You can filter: [x for x in items if x > 0]. (No PowerShell equivalent syntax; it is one of Python's signature features.)
- What is a dict comprehension?
- Building a dict compactly: {k: v for k, v in pairs}. (Same idea as a list comprehension but produces key-value pairs.)
- What is a generator expression, and how does it differ from a list comprehension?
- Same syntax but with parentheses: (x*2 for x in range(5)). It produces items lazily one at a time instead of building the whole list in memory. (Use it for large data so you do not load everything at once.)
- How do you define a function?
-
def name(param1, param2):
return result. (Like a PowerShell function, but parameters go in the parentheses and you use 'return' or the last expression.) - What is the difference between *args and **kwargs?
- *args gathers extra positional arguments into a tuple; **kwargs gathers extra keyword arguments into a dict. (They let a function take a variable number of arguments, like a flexible param block.)
- What is a default parameter value?
- A value used when the caller omits the argument: def f(x, y=10). (Same concept as [string]$name = 'default' in a PowerShell param block.)
- What is the difference between a parameter and an argument?
- A parameter is the name in the function definition; an argument is the actual value you pass in. (The same distinction PowerShell draws.)
- What is a lambda?
- A small anonymous one-expression function: square = lambda x: x*x. (Handy as a throwaway, often passed to sorted() or filter(); like a quick scriptblock in PowerShell.)
- What does return do, and what if you leave it out?
- return hands a value back and exits the function. With no return, the function returns None. (In PowerShell, uncaptured output goes to the pipeline; Python only returns what you explicitly return.)
- What is variable scope, and what is LEGB?
- LEGB is the lookup order: Local, Enclosing, Global, Built-in. A name in a function is local unless declared otherwise. (PowerShell has similar scopes: local, script, global.)
- What do global and nonlocal do?
- global lets a function reassign a module-level variable; nonlocal lets a nested function reassign one in the enclosing function. (Use sparingly; needed only when you must write to an outer-scope name.)
- What is the difference between a script and a module?
- A script is a .py file you run directly to do work. A module is a .py file you import to reuse its functions elsewhere. The same file can act as both. (This is the exact distinction PowerShell draws between a script and a module.)
- What does if __name__ == '__main__': do, and why use it?
- It runs the code under it only when the file is executed directly, not when it is imported. So your file can be both a runnable script and an importable module. (There is no clean PowerShell equivalent; this is the canonical script-versus-module idiom, worth knowing cold.)
- What is the difference between import x and from x import y?
- import x brings in the module (use x.func()). from x import y brings a specific name in directly (use y()). (Like Import-Module then calling Module\Cmdlet vs importing the cmdlet by name.)
- What is a package in Python?
- A folder of modules, historically marked by an __init__.py file, that you import as a namespace. (A module is one file; a package is a directory of them. Like a PowerShell module folder with a manifest.)
- What is argparse and when do you use it?
- The standard-library module for building command-line tools: it parses flags and arguments and generates help. (This is how a Python CLI tool takes parameters; the equivalent of a PowerShell param() block with validation for a script.)
- How do you read command-line arguments in a simple script?
- sys.argv is a list of the arguments (sys.argv[0] is the script name). For anything real, use argparse. (sys.argv is the quick-and-dirty version, like $args in PowerShell; argparse is the proper param() block.)
- How do you handle errors in Python?
-
try:
risky()
except SomeError as e:
handle(e)
finally:
cleanup(). (Same try/catch/finally idea as PowerShell, but the keyword is 'except' instead of 'catch.') - What is the difference between an error and an exception?
- Most runtime problems are exceptions, which are objects raised during execution and catchable with try/except. A SyntaxError happens before running. (Exception is the base class you usually catch.)
- Why is 'except Exception' better than a bare 'except:'?
- A bare except swallows everything, including Ctrl-C (KeyboardInterrupt) and SystemExit, which you rarely want. except Exception catches normal errors while letting those through. (Bare except is a well-known code smell.)
- What does finally do?
- It always runs, whether or not an exception happened, so you use it for cleanup like closing files or connections. (Same as PowerShell's finally block.)
- How do you raise an exception?
- raise ValueError('message'). You can raise built-in or your own custom exception classes. (Like throw in PowerShell.)
- What exceptions come up parsing untrusted data, and how do you handle them?
- KeyError (missing dict key), ValueError (bad conversion), TypeError, json.JSONDecodeError. Wrap parsing in try/except and validate before using. (Core habit for a security engineer processing logs or API responses.)
- How do you read a Python traceback when debugging?
- Read it bottom-up: the last line is the actual exception and message; the lines above show the call path that led there. (The single most useful debugging skill; the bottom line tells you what broke.)
- How do you read a file safely in Python?
-
with open('file.txt') as f:
data = f.read(). The 'with' auto-closes it even on error. (Get-Content is the PowerShell read; 'with' is the safe-open pattern.) - What is a context manager and why use 'with'?
- An object that sets up and tears down a resource automatically, like a file, lock, or DB connection. 'with open(...) as f:' guarantees the file closes even on error. (It is basically a built-in try/finally for resources; reach for it whenever you open something.)
- How would you read a large log file without loading it all into memory?
- Iterate line by line inside a with block: with open('log') as f: for line in f: process(line). This streams instead of reading the whole file with .read(). (Important when logs are gigabytes; the streaming habit a security engineer needs.)
- How do you parse JSON in Python?
- import json; json.loads(text) parses a string, json.dumps(obj) serializes. json.load/json.dump work on file objects. (Everywhere in API and cloud work; the ConvertFrom-Json / ConvertTo-Json of Python.)
- How do you make an HTTP request in Python?
- Use the requests library: r = requests.get(url); r.json(). (The Invoke-RestMethod of Python; used to hit APIs and threat-intel feeds.)
- What is the datetime module for, and why does it matter in security work?
- Parsing, formatting, and doing math on dates and times: datetime.strptime parses a timestamp string, and you can compare or subtract times. (Essential for log correlation, working out what happened when, and time windows.)
- What is hashlib used for?
- Computing hashes: hashlib.sha256(data).hexdigest(). (Security-critical: file integrity, computing or matching IOCs, checksums. Know SHA-256 is preferred and MD5/SHA-1 are considered weak.)
- What is the base64 module for?
- Encoding and decoding base64: base64.b64decode(s). (Comes up decoding payloads, tokens, and encoded data during investigations.)
- What is the ipaddress module, and give a security use.
- It represents and validates IPs and networks: you can check if an address falls inside a subnet with ip_address(x) in ip_network('10.0.0.0/8'). (Exactly the 'is this IP in our range' question a security engineer asks constantly.)
- What does the os module give you?
- Operating-system access: os.environ for environment variables, os.path for paths, os.listdir, running in a cross-platform way. (os.environ is how you read secrets from the environment instead of hardcoding them.)
- What is the subprocess module, and what is the security gotcha?
- It runs external commands: subprocess.run(['ls','-l']). The gotcha is shell=True with unsanitized input, which is a command-injection vulnerability. Pass a list of args and avoid shell=True. (A classic security review catch.)
- Why use the logging module instead of print?
- logging gives levels (debug/info/warning/error), timestamps, and configurable outputs, so it produces real audit trails. print just writes to stdout. (For any production or security tool, logging is the professional choice; like using Write-Verbose/proper logging over Write-Host.)
- What is pathlib?
- The modern, object-oriented way to handle filesystem paths: Path('dir') / 'file.txt'. Cleaner and cross-platform versus string paths. (Preferred over os.path in new code.)
- How do you work with CSV data?
- import csv; csv.DictReader for reading rows as dicts, csv.writer for writing. Or pandas for heavier work. (Import-Csv / Export-Csv are the PowerShell equivalents.)
- What is pandas and when would you use it?
- A data-analysis library with DataFrames (table-like structures) for cleaning, joining, filtering, and analyzing data. (You would use it to join disparate log sources or enrich alerts; heavier than the csv module.)
- How do you use regular expressions in Python?
- import re; re.search(pattern, text), re.findall(pattern, text), re.sub(pattern, repl, text). (Used constantly to parse logs and pull out IOCs; PowerShell's -match / -replace are the parallels.)
- How should you handle secrets in a Python script?
- Never hardcode them. Read from environment variables (os.environ), a secrets manager, or a vault, and keep them out of source control. (Standard security hygiene; the answer is always 'not in the code.')
- What is the difference between stdout and stderr?
- stdout is normal program output; stderr is for errors and diagnostics, kept separate so you can redirect them independently. (Same split as PowerShell's output vs error streams.)
- How do you write to stderr in Python?
- print('msg', file=sys.stderr), or use the logging module. (Keeps error messages out of the normal output stream so piped data stays clean.)
- How do you redirect output at the shell when running a Python script?
- python script.py > out.txt sends stdout to a file; 2> err.txt sends stderr; 2>&1 merges them. (The same redirection operators PowerShell uses: > and 2>.)
- How do you debug Python code?
- Insert breakpoint() (or import pdb; pdb.set_trace()) to drop into the debugger, step through, and inspect variables. Read the traceback bottom-up. Use logging over scattered prints. (A real method beats scattering prints everywhere.)
- What does the print() function do with multiple arguments?
- print(a, b, c) prints them separated by spaces and adds a newline. You can change sep= and end=. (Write-Host / Write-Output are the loose PowerShell parallels.)
- What is a class and an object?
- A class is a blueprint; an object is an instance made from it. class Dog: defines it; d = Dog() creates one. (PowerShell 5+ also has classes with the same idea.)
- What is self in a method?
- self is the current instance, so methods can read and set that object's attributes. It is the first parameter of instance methods. (Roughly like $this inside a PowerShell class.)
- What is __init__?
- The constructor, called automatically when you create an instance, used to set up initial attributes. (Like a class constructor in PowerShell.)
- What is the difference between a function and a method?
- A method belongs to an object or class and takes self; a function stands alone. (The same distinction exists in PowerShell classes.)
- What is a decorator?
- A function that wraps another to add behavior, applied with @name above the definition. Examples: @staticmethod, @property, timing or logging wrappers. (Think of it as automatically piping a function through a wrapper.)
- What is a generator, and why use one?
- A function that uses yield to produce values one at a time, lazily, instead of building a full list in memory. (Great for streaming large datasets, like reading a huge log without loading it all.)
- What is the difference between yield and return?
- return exits the function with a value. yield produces a value but pauses the function, resuming on the next request, which makes it a generator. (yield is how you make something you can iterate over lazily.)
- What is collections.Counter, and give a security use?
- Counter tallies occurrences: Counter(ip_list) gives each IP's count, and .most_common(5) gives the top 5. (Perfect for counting failed logins per IP; saves writing the tally loop by hand.)
- What is collections.defaultdict?
- A dict that auto-creates a default value for missing keys, so you can do d[key].append(x) without checking if the key exists first. (Removes the 'if key not in dict' boilerplate when grouping data.)
- What is a namedtuple?
- A tuple with named fields, so you access p.x instead of p[0]. (Gives you lightweight, readable records without writing a full class.)
- What do split() and join() do?
- 'a,b,c'.split(',') gives ['a','b','c']; ','.join(list) rebuilds the string. (The core of parsing a log line into fields and reassembling; PowerShell's -split and -join.)
- What does strip() do?
- Removes leading and trailing whitespace (or specified characters): line.strip(). (Cleaning up parsed log fields; .Trim() in PowerShell.)
- How do you check if a string contains a substring?
- 'error' in line returns True/False. For position, use line.find('x') (returns -1 if absent) or line.index('x') (raises if absent). (The 'in' test is the quick membership check.)
- What do startswith() and endswith() do?
- line.startswith('GET') and filename.endswith('.log') test the ends of a string. (Common for filtering log lines or filenames.)
- How do you change string case?
- s.lower(), s.upper(), s.title(). (Useful for case-insensitive matching, like normalizing usernames or domains.)
- How do you replace text in a string?
- s.replace('old', 'new'). For pattern-based replacement, use re.sub. (Direct analog of PowerShell's -replace, minus the regex unless you use re.)
- What is the difference between dict['key'] and dict.get('key')?
- Bracket access raises KeyError if the key is missing; .get('key') returns None (or a default you pass) instead. (Use .get() when a missing key is normal and should not crash you.)
- How do you iterate a dictionary's keys and values together?
- for key, value in my_dict.items(). Use .keys() or .values() for just one. (In PowerShell you would use .GetEnumerator(); .items() is the Python way.)
- How do you safely group items into a dict of lists?
- Use defaultdict(list) and d[key].append(item), or dict.setdefault(key, []).append(item). (Avoids checking whether the key exists first; common when bucketing events by host or IP.)
- How do you merge two dictionaries?
- Python 3.9+: d1 | d2. Older: {**d1, **d2} or d1.update(d2). (Later keys win on conflicts.)
- What is an f-string, and why is it preferred?
- String interpolation with an f prefix: f'{user} logged in at {ts}'. Expressions inside braces are evaluated inline. It is the modern, readable standard. (Cleaner than concatenation; the PowerShell equivalent is "$user logged in".)
- What are format specifiers inside f-strings?
- Formatting controls after a colon: f'{value:.2f}' for two decimals, f'{n:,}' for thousands separators, f'{x:>10}' to right-align in 10 spaces. (Used for clean, aligned report or table output.)
- What are the older string formatting styles you might see?
- Percent style: '%s' % name. And .format(): '{}'.format(name). (You will meet both in legacy code and some logging calls; f-strings are what you write today.)
- What is the tabs-vs-spaces gotcha in Python?
- Python uses indentation for blocks, and mixing tabs and spaces raises IndentationError or TabError. Standard is 4 spaces. (A real trap since whitespace is syntax, unlike PowerShell's braces.)
- Why does 0.1 + 0.2 not equal 0.3 in Python?
- Floating-point numbers cannot represent some decimals exactly, so 0.1 + 0.2 is 0.30000000000000004. Never compare floats with ==; use a tolerance (math.isclose). (A classic 'predict the output' trap.)
- What is the difference between / and // in Python 3?
- / is true division and always returns a float (4/2 is 2.0). // is floor division (7//2 is 3). (In Python 3 even 4/2 gives 2.0, which surprises people; PowerShell's / behaves more like true division too.)
- What happens if you modify a list while iterating over it?
- You get skipped elements or errors because the indices shift under you. Iterate over a copy (for x in list[:]) or build a new list instead. (A subtle bug worth knowing on sight.)
- What do 'and' and 'or' actually return in Python?
- They return one of the operands, not a strict boolean: 0 or 'hi' returns 'hi'; 'a' and 'b' returns 'b'. (Useful for defaults like name = given or 'anonymous'.)
- What does negative indexing do?
- list[-1] is the last element, list[-2] the second-to-last. (Cleaner than len(list)-1; a small fluency tell.)
- What does slicing an out-of-range range return?
- It does not error: [1,2,3][5:10] returns []. Slicing is forgiving, unlike indexing a single out-of-range element, which raises IndexError. (Good to know so you do not over-guard slices.)
- What does list slicing syntax mean, e.g. my_list[1:4] and [::-1]?
- my_list[1:4] takes items from index 1 up to but not including 4. [::-1] reverses the sequence. Works on lists, strings, tuples. (Slicing is one of Python's most-used features.)
- What is the walrus operator :=?
- Assigns a value inside an expression: if (n := len(data)) > 10:. It lets you assign and test in one line. (Newer syntax; recognizing it signals you keep up with the language.)
- What is a ternary (conditional) expression in Python?
- value = a if condition else b. A one-line if/else that returns a value. (The compact form for simple either-or assignments.)
- How do you swap two variables in Python?
- a, b = b, a. Tuple unpacking does it in one line with no temp variable. (A small idiom that signals real Python familiarity.)
- What is unpacking, including * unpacking?
- a, b, c = [1,2,3] assigns each; first, *rest = [1,2,3,4] puts 1 in first and [2,3,4] in rest. (Handy for pulling apart records or splitting a header from the rest of a list.)
- Why is eval() or exec() on untrusted input dangerous?
- They execute arbitrary code from a string, so untrusted input becomes remote code execution. Avoid them on any external data. (A security engineer is expected to flag this immediately.)
- What is the Global Interpreter Lock (GIL)?
- A lock in CPython that lets only one thread run Python bytecode at a time, which limits CPU-bound threading. (For CPU-heavy work use multiprocessing; for I/O-bound work threads are fine. Python 3.13 and later offer an optional free-threaded build without the GIL, but the default build keeps it.)
- What is the difference between multithreading and multiprocessing in Python?
- Threads share memory but are limited by the GIL, so they suit I/O-bound work (waiting on network/disk). Multiprocessing uses separate processes with their own memory, suiting CPU-bound work. (Pick based on whether you are waiting or computing.)
- What is a virtual environment and why use one?
- An isolated Python environment (python -m venv) with its own packages, so projects do not clash over dependency versions. (Standard practice; keeps each project's libraries separate.)
- What is pip and requirements.txt?
- pip installs packages from PyPI (pip install requests). requirements.txt pins the project's dependencies so others can reproduce the environment. (The Install-Module / package management equivalent.)
- What are type hints, and do they change runtime behavior?
- Annotations like def f(x: int) -> str: that document expected types. They do not enforce anything at runtime; tools like mypy check them. (Signals maintainable, modern Python; increasingly expected in professional code.)