Hubbry Logo
Python syntax and semanticsPython syntax and semanticsMain
Open search
Python syntax and semantics
Community hub
Python syntax and semantics
logo
8 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Contribute something
Python syntax and semantics
Python syntax and semantics
from Wikipedia
A snippet of Python code demonstrating binary search

The syntax of the Python programming language is the set of rules that defines how a Python program will be written and interpreted (by both the runtime system and by human readers). The Python language has many similarities to Perl, C, and Java. However, there are some definite differences between the languages. It supports multiple programming paradigms, including structured, object-oriented programming, and functional programming, and boasts a dynamic type system and automatic memory management.

Python's syntax is simple and consistent, adhering to the principle that "There should be one—and preferably only one—obvious way to do it."[citation needed] The language incorporates built-in data types and structures, control flow mechanisms, first-class functions, and modules for better code reusability and organization. Python also uses English keywords where other languages use punctuation, contributing to its uncluttered visual layout.

The language provides robust error handling through exceptions, and includes a debugger in the standard library for efficient problem-solving. Python's syntax, designed for readability and ease of use, makes it a popular choice among beginners and professionals alike.

Design philosophy

[edit]

Python was designed to be a highly readable language.[1] It has a relatively uncluttered visual layout and uses English keywords frequently where other languages use punctuation. Python aims to be simple and consistent in the design of its syntax, encapsulated in the mantra "There should be one— and preferably only one —obvious way to do it", from the Zen of Python.[2]

This mantra is deliberately opposed to the Perl and Ruby mantra, "there's more than one way to do it".

Keywords

[edit]

Python 3 has 35 keywords or reserved words; they cannot be used as identifiers.[3][4]

  • and
  • as
  • assert
  • async[note 1]
  • await[note 1]
  • break
  • class
  • continue
  • def
  • del
  • elif
  • else
  • except
  • False[note 2]
  • finally
  • for
  • from
  • global
  • if
  • import
  • in
  • is
  • lambda
  • None
  • nonlocal[note 3]
  • not
  • or
  • pass
  • raise
  • return
  • True[note 2]
  • try
  • while
  • with
  • yield

In addition, Python 3 also has 4 soft keywords, including type added in Python 3.12. Unlike regular hard keywords, soft keywords are reserved words only in the limited contexts where interpreting them as keywords would make syntactic sense. These words can be used as identifiers elsewhere, in other words, match and case are valid names for functions and variables.[6][7]

Notes
  1. ^ a b async and await were introduced in Python 3.5.[5]
  2. ^ a b True and False became keywords in Python 3.0. Previously they were global variables.
  3. ^ nonlocal was introduced in Python 3.0.
  4. ^ a b c match, case and _ were introduced as keywords in Python 3.10.

Function annotations

[edit]

Function annotations (type hints) are defined in PEP 3107.[8] They allow attaching data to the arguments and return of a function. The behaviour of annotations is not defined by the language, and is left to third party frameworks. For example, a library could be written to handle static typing:[8]

def haul(item: Haulable, *vargs: PackAnimal) -> Distance:
    # implementation here

While annotations are optional in Python, the rest of this article will use annotations to provide clarity.

Modules and import statements

[edit]

In Python, code is organized into files called modules, and namespaces are defined by the individual modules. Since modules can be contained in hierarchical packages, then namespaces are hierarchical too.[9][10] In general when a module is imported then the names defined in the module are defined via that module's namespace, and are accessed in from the calling modules by using the fully qualified name.

# assume ModuleA defines two functions : func1() and func2() and one class : Class1
import ModuleA

ModuleA.func1()
ModuleA.func2()
a: ModuleA.Class1 = Modulea.Class1()

The from ... import ... statement can be used to insert the relevant names directly into the calling module's namespace, and those names can be accessed from the calling module without the qualified name:

# assume ModuleA defines two functions : func1() and func2() and one class : Class1
from ModuleA import func1

func1()
func2() # this will fail as an undefined name, as will the full name ModuleA.func2()
a: Class1 = Class1() # this will fail as an undefined name, as will the full name ModuleA.Class1()

Since this directly imports names (without qualification) it can overwrite existing names with no warnings.

A special form of the statement is from ... import * which imports all names defined in the named package directly in the calling module's namespace. Use of this form of import, although supported within the language, is generally discouraged as it pollutes the namespace of the calling module and will cause already defined names to be overwritten in the case of name clashes[11]. However, this page will present code as if the line "from typing import *" were included, for referring to collection types.

The different import statements are demonstrated here:

# imports the argument parsing module
import argparse
# imports the Pattern class from the regular expressions module
from re import Pattern
# imports all symbols inside the typing module
from typing import *

Using from import statements in Python can simplify verbose namespaces, such as nested namespaces.

from selenium.webdriver import Firefox
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.webelement import WebElement

if __name__ == "__main__":
    driver: Firefox = Firefox()
    element: WebElement = driver.find_element(By.ID, "myInputField")
    element.send_keys(f"Hello World{Keys.ENTER}")
    action: ActionChains = ActionChains(driver)
    action.key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL).perform()

Python also supports import x as y as a way of providing an alias or alternative name for use by the calling module:

import numpy as np
from numpy.typing import NDArray, float32

a: NDArray[float32] = np.arange(1000)

When a module is imported, the Python interpreter first checks if it exists in the sys.modules cache, and reuses it if it had been imported previously, otherwise it loads it. When loading, it searches it in sys.path, and compiles it to bytecode or interprets its contents. All code in the global scope of the module is executed. However, this can be mitigated using an explicit main function, which behaves similarly to an entry point in most compiled languages, using the entry point idiom described as follows.

Entry point

[edit]

A pseudo-entry point can be created by the following idiom, which relies on the internal variable __name__ being set to __main__ when a program is executed, but not when it is imported as a module (in which case it is instead set to the module name); there are many variants of this structure:[12][13][14]

import sys

def main(argv: list[str]) -> int:
    argc: int = len(argv)  # get length of argv
    n: int = int(argv[1])
    print(n + 1)
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))

In this idiom, the call to the named entry point main is explicit, and the interaction with the operating system (receiving the arguments, calling system exit) are done explicitly by library calls, which are ultimately handled by the Python runtime. This contrasts with C, where these are done implicitly by the runtime, based on convention.

Indentation

[edit]

Python uses whitespace to delimit control flow blocks (following the off-side rule). Python borrows this feature from its predecessor ABC: instead of punctuation or keywords, it uses indentation to indicate the run of a block.

In so-called "free-format" languages – that use the block structure derived from ALGOL – blocks of code are set off with braces ({ }) or keywords. In most coding conventions for these languages, programmers conventionally indent the code within a block, to visually set it apart from the surrounding code.

A recursive function named foo, which is passed a single parameter, x, and if the parameter is 0 will call a different function named bar and otherwise will call baz, passing x, and also call itself recursively, passing x-1 as the parameter, could be implemented like this in Python:

def foo(x: int) -> None:
    if x == 0:
        bar()
    else:
        baz(x)
        foo(x - 1)

and could be written like this in C:

void foo(int x) {
    if (x == 0) {
        bar();
    } else {
        baz(x);
        foo(x - 1);
    }
}

Incorrectly indented code could be misread by a human reader differently than it would be interpreted by a compiler or interpreter. For example, if the function call foo(x - 1) on the last line in the example above was erroneously indented to be outside the if/else block:

def foo(x: int) -> None:
    if x == 0:
        bar()
    else:
        baz(x)
    foo(x - 1)

it would cause the last line to always be executed, even when x is 0, resulting in an endless recursion.

While both space and tab characters are accepted as forms of indentation and any multiple of spaces can be used, spaces are recommended[15] and four spaces (as in the above examples) are recommended and are by far the most commonly used.[16][17][unreliable source?] Mixing spaces and tabs on consecutive lines is not allowed starting with Python 3[18] because that can create bugs which are difficult to see, since many text editors do not visually distinguish spaces and tabs.

Data structures

[edit]

Since Python is a dynamically-typed language, Python values, not variables, carry type information. All variables in Python hold references to objects, and these references are passed to functions. Some people (including Python creator Guido van Rossum himself) have called this parameter-passing scheme "call by object reference". An object reference means a name, and the passed reference is an "alias", i.e. a copy of the reference to the same object, just as in C/C++. The object's value may be changed in the called function with the "alias", for example:

my_list: list[str] = ["a", "b", "c"]
def my_func(l: list[str]) -> None:
    l.append("x")
    print(l)

print(my_func(my_list))
# prints ['a', 'b', 'c', 'x']
print(my_list)
# prints ['a', 'b', 'c', 'x']

Function my_func changes the value of my_list with the formal argument l, which is an alias of my_list. However, any attempt to operate (assign a new object reference to) on the alias itself will have no effect on the original object.[clarification needed]

my_list: list[str] = ["a", "b", "c"]

def my_func(l: list[str]) -> None:
    # l.append("x")
    l = l + ["x"]  # a new list created and assigned to l means l is no more alias for my_list
    print(l)

print(my_func(my_list))
# prints ['a', 'b', 'c', 'x']
print(my_list)
# prints ['a', 'b', 'c']

In Python, non-innermost-local and not-declared-global accessible names are all aliases.

Among dynamically-typed languages, Python is moderately type-checked. Implicit conversion is defined for numeric types (as well as booleans), so one may validly multiply a complex number by an integer (for instance) without explicit casting. However, there is no implicit conversion between, for example, numbers and strings; a string is an invalid argument to a mathematical function expecting a number.

Base types

[edit]

Python has a broad range of basic data types. Alongside conventional integer and floating-point arithmetic, it transparently supports arbitrary-precision arithmetic, complex numbers, and decimal numbers.

Python supports a wide variety of string operations. Strings in Python are immutable, meaning that string operations, such as replacement of characters, return a new string; in other programming languages the string might be altered in place. Performance considerations sometimes push for using special techniques in programs that modify strings intensively, such as joining character arrays into strings only as needed.

Collection types

[edit]

One of the very useful aspects of Python is the concept of collection (or container) types. In general a collection is an object that contains other objects in a way that is easily referenced or indexed. Collections come in two basic forms: sequences and mappings.

The ordered sequential types are lists (dynamic arrays), tuples, and strings. All sequences are indexed positionally (0 through length - 1) and all but strings can contain any type of object, including multiple types in the same sequence. Both strings and tuples are immutable, making them perfect candidates for dictionary keys (see below). Lists, on the other hand, are mutable; elements can be inserted, deleted, modified, appended, or sorted in-place.

Mappings, on the other hand, are (often unordered) types implemented in the form of dictionaries which "map" a set of immutable keys to corresponding elements (much like a mathematical function). For example, one could define a dictionary having a string "toast" mapped to the integer 42 or vice versa. The keys in a dictionary must be of an immutable Python type, such as an integer or a string, because they are implemented via a hash function. This makes for much faster lookup times, but requires keys to remain unchanged.

Dictionaries are central to the internals of Python as they reside at the core of all objects and classes: the mappings between variable names (strings) and the values which the names reference are stored as dictionaries (see Object system). Since these dictionaries are directly accessible (via an object's __dict__ attribute), metaprogramming is a straightforward and natural process in Python.

A set collection type is an unindexed, unordered collection that contains no duplicates, and implements set theoretic operations such as union, intersection, difference, symmetric difference, and subset testing. There are two types of sets: set and frozenset, the only difference being that set is mutable and frozenset is immutable. Elements in a set must be hashable. Thus, for example, a frozenset can be an element of a regular set whereas the opposite is not true.

Python also provides extensive collection manipulating abilities such as built in containment checking and a generic iteration protocol.

Object system

[edit]

In Python, everything is an object, even classes. Classes, as objects, have a class, which is known as their metaclass. Python also supports multiple inheritance and mixins.

The language supports extensive introspection of types and classes. Types can be read and compared: Types are instances of the object type. The attributes of an object can be extracted as a dictionary.

Operators can be overloaded in Python by defining special member functions – for instance, defining a method named __add__ on a class permits one to use the + operator on objects of that class.

Literals

[edit]

Strings

[edit]

Python has various kinds of string literals.

Normal string literals

[edit]

Either single or double quotes can be used to quote strings. Unlike in Unix shell languages, Perl or Perl-influenced languages such as Ruby or Groovy, single quotes and double quotes function identically, i.e. there is no string interpolation of $foo expressions. However, interpolation can be done in various ways: with "f-strings" (since Python 3.6[19]), using the format method or the old % string-format operator.

For instance, all of these Python statements:

print(f"I just printed {num} pages to the printer {printer}")

print("I just printed {} pages to the printer {}".format(num, printer))
print("I just printed {0} pages to the printer {1}".format(num, printer))
print("I just printed {a} pages to the printer {b}".format(a=num, b=printer))

print("I just printed %s pages to the printer %s" % (num, printer))
print("I just printed %(a)s pages to the printer %(b)s" % {"a": num, "b": printer})

are equivalent to the Perl statement:

print "I just printed $num pages to the printer $printer\n"

They build a string using the variables num and printer.

Multi-line string literals

[edit]

There are also multi-line strings, which begin and end with a series of three single or double quotes and function like here documents in Perl and Ruby.

A simple example with variable interpolation (using the format method) is:

print('''Dear {recipient},

I wish you to leave Sunnydale and never return.

Not Quite Love,
{sender}
'''.format(sender="Buffy the Vampire Slayer", recipient="Spike"))

Raw strings

[edit]

Finally, all of the previously mentioned string types come in "raw" varieties (denoted by placing a literal r before the opening quote), which do no backslash-interpolation and hence are very useful for regular expressions; compare "@-quoting" in C#. Raw strings were originally included specifically for regular expressions. Due to limitations of the tokenizer, raw strings may not have a trailing backslash.[20] Creating a raw string holding a Windows path ending with a backslash requires some variety of workaround (commonly, using forward slashes instead of backslashes, since Windows accepts both).

Examples include:

# A Windows path, even raw strings cannot end in a backslash
win_path: str = r"C:\Foo\Bar\Baz\"

# Error:
#  File "<stdin>", line 1
#    win_path: str = r"C:\Foo\Bar\Baz\"
#                                     ^
# SyntaxError: EOL while scanning string literal

dos_path: str = r"C:\Foo\Bar\Baz\ "  # avoids the error by adding
print(dos_path.rstrip()) # and removing trailing space
# prints('C:\\Foo\\Bar\\Baz\\')

quoted_dos_path: str = r'"{}"'.format(dos_path)
print(quoted_dos_path)
# prints '"C:\\Foo\\Bar\\Baz\\ "'

# A regular expression matching a quoted string with possible backslash quoting
print(re.match(r'"(([^"\\]|\\.)*)"', quoted_dos_path).group(1).rstrip())
# prints 'C:\\Foo\\Bar\\Baz\\'

code: str = 'foo(2, bar)'
# Reverse the arguments in a two-arg function call
print(re.sub(r'\(([^,]*?),([^ ,]*?)\)', r'(\2, \1)', code))
# prints 'foo(2, bar)'
# Note that this won't work if either argument has parens or commas in it.

Concatenation of adjacent string literals

[edit]

String literals appearing contiguously and only separated by whitespace (including new lines using backslashes), are allowed and are aggregated into a single longer string.[21] Thus

title: str = "One Good Turn: " \
             'A Natural History of the Screwdriver and the Screw'

is equivalent to

title: str = "One Good Turn: A Natural History of the Screwdriver and the Screw"

Unicode

[edit]

Since Python 3.0, the default character set is UTF-8 both for source code and the interpreter. In UTF-8, unicode strings are handled like traditional byte strings. This example will work:

s: str = "Γειά"  # Hello in Greek
print(s)

Numbers

[edit]

Numeric literals in Python are of the normal sort, e.g. 0, -1, 3.4, 3.5e-8.

Python has arbitrary-length integers and automatically increases their storage size as necessary. Prior to Python 3, there were two kinds of integral numbers: traditional fixed size integers and "long" integers of arbitrary size. The conversion to "long" integers was performed automatically when required, and thus the programmer usually did not have to be aware of the two integral types. In newer language versions the distinction is completely gone and all integers behave like arbitrary-length integers.

Python supports normal floating point numbers, which are created when a dot is used in a literal (e.g. 1.1), when an integer and a floating point number are used in an expression, or as a result of some mathematical operations ("true division" via the / operator, or exponentiation with a negative exponent).

Python also supports complex numbers natively. The imaginary component of a complex number is indicated with the J or j suffix, e.g. 3 + 4j.

Lists, tuples, sets, dictionaries

[edit]

Python has syntactic support for the creation of container types.

Lists (class list) are mutable sequences of items of arbitrary types, and can be created either with the special syntax

my_list: list[int | str] = [1, 2, 3, "a dog"]

or using normal object creation

my_second_list: list[int] = []
my_second_list.append(4)
my_second_list.append(5)

Tuples (class tuple) are immutable sequences of items of arbitrary types. There is also a special syntax to create tuples

my_tuple: tuple[int | str] = 1, 2, 3, "four"
my_tuple: tuple[int | str] = (1, 2, 3, "four")

Although tuples are created by separating items with commas, the whole construct is usually wrapped in parentheses to increase readability. An empty tuple is denoted by (), while a tuple with a single value can be created with (1,).

Sets (class set) are mutable containers of hashable items[22] of arbitrary types, with no duplicates. The items are not ordered, but sets support iteration over the items. The syntax for set creation uses curly brackets

my_set: set[Any] = {0, (), False}

Python sets are very much like mathematical sets, and support operations like set intersection and union. Python also features a frozenset class for immutable sets, see Collection types.

Dictionaries (class dict) are mutable mappings tying keys and corresponding values. Python has special syntax to create dictionaries ({key: value})

my_dictionary: dict[Any, Any] = {"key 1": "value 1", 2: 3, 4: []}

The dictionary syntax is similar to the set syntax; the difference is the presence of colons. The empty literal {} results in an empty dictionary rather than an empty set, which is instead created using the non-literal constructor: set().

Operators

[edit]

Arithmetic

[edit]

Python includes the +, -, *, / ("true division"), // (floor division), % (modulus), and ** (exponentiation) operators, with their usual mathematical precedence.

In Python 3, x / y performs "true division", meaning that it always returns a float, even if both x and y are integers that divide evenly.

print(4 / 2)
# prints 2.0

and // performs integer division or floor division, returning the floor of the quotient as an integer.

In Python 2 (and most other programming languages), unless explicitly requested, x / y performed integer division, returning a float only if either input was a float. However, because Python is a dynamically-typed language, it was not always possible to tell which operation was being performed, which often led to subtle bugs, thus prompting the introduction of the // operator and the change in semantics of the / operator in Python 3.

Comparison operators

[edit]

The comparison operators, i.e. ==, !=, <, >, <=, >=, is, is not, in and not in[23] are used on all manner of values. Numbers, strings, sequences, and mappings can all be compared. In Python 3, disparate types (such as a str and an int) do not have a consistent relative ordering, and attempts to compare these types raises a TypeError exception. While it was possible to compare disparate types in Python 2 (for example, whether a string was greater-than or less-than an integer), the ordering was undefined; this was considered a historical design quirk and was ultimately removed in Python 3.

Chained comparison expressions such as a < b < c have roughly the meaning that they have in mathematics, rather than the unusual meaning found in C and similar languages. The terms are evaluated and compared in order. The operation has short-circuit semantics, meaning that evaluation is guaranteed to stop as soon as a verdict is clear: if a < b is false, c is never evaluated as the expression cannot possibly be true anymore.

For expressions without side effects, a < b < c is equivalent to a < b and b < c. However, there is a substantial difference when the expressions have side effects. a < f(x) < b will evaluate f(x) exactly once, whereas a < f(x) and f(x) < b will evaluate it twice if the value of a is less than f(x) and once otherwise.

Logical operators

[edit]

In all versions of Python, boolean operators treat zero values or empty values such as "", 0, None, 0.0, [], and {} as false, while in general treating non-empty, non-zero values as true. The boolean values True and False were added to the language in Python 2.2.1 as constants (subclassed from 1 and 0) and were changed to be full blown keywords in Python 3. The binary comparison operators such as == and > return either True or False.

The boolean operators and and or use minimal evaluation. For example, y == 0 or x/y > 100 will never raise a divide-by-zero exception. These operators return the value of the last operand evaluated, rather than True or False. Thus the expression (4 and 5) evaluates to 5, and (4 or 5) evaluates to 4.

Functional programming

[edit]

A strength of Python is the availability of a functional programming style, which makes working with lists and other collections much more straightforward.

Comprehensions

[edit]

One such construction is the list comprehension, which can be expressed with the following format:

l: list[Any] = [mapping_expression for element in source_list if filter_expression]

Using list comprehension to calculate the first five powers of two:

powers_of_two: list[int] = [2 ** n for n in range(1, 6)]

The Quicksort algorithm can be expressed elegantly (albeit inefficiently) using list comprehensions:

T: TypeVar = TypeVar("T")

def qsort(l: list[T]) -> list[T]:
    if l == []:
        return []
    pivot: T = l[0]
    return (qsort([x for x in l[1:] if x < pivot]) +
            [pivot] +
            qsort([x for x in l[1:] if x >= pivot]))

Python 2.7+[24] also supports set comprehensions[25] and dictionary comprehensions.[26]

First-class functions

[edit]

In Python, functions are first-class objects that can be created and passed around dynamically.

Python's limited support for anonymous functions is the lambda construct. An example is the anonymous function which squares its input, called with the argument of 5:

f: Callable[[int], int] = lambda x: x**2
f(5)

Lambdas are limited to containing an expression rather than statements, although control flow can still be implemented less elegantly within lambda by using short-circuiting,[27] and more idiomatically with conditional expressions.[28]

Closures

[edit]

Python has had support for lexical closures since version 2.2. Here's an example function that returns a function that approximates the derivative of the given function:

def derivative(f: Callable[[float], float], dx: float):
    """Return a function that approximates the derivative of f
    using an interval of dx, which should be appropriately small.
    """
    def function(x: float) -> float:
        return (f(x + dx) - f(x)) / dx
    return function

Python's syntax, though, sometimes leads programmers of other languages to think that closures are not supported. Variable scope in Python is implicitly determined by the scope in which one assigns a value to the variable, unless scope is explicitly declared with global or nonlocal.[29]

Note that the closure's binding of a name to some value is not mutable from within the function. Given:

def foo(a: int, b: int) -> None:
    print(f"a: {a}")
    print(f"b: {b}")
    def bar(c: int) -> None:
        b = c
        print(f"b*: {b}")
    bar(a)
    print(f"b: {b}")

print(foo(1, 2))
# prints:
# a: 1
# b: 2
# b*: 1
# b: 2

and you can see that b, as visible from the closure's scope, retains the value it had; the changed binding of b inside the inner function did not propagate out. The way around this is to use a nonlocal b statement in bar. In Python 2 (which lacks nonlocal), the usual workaround is to use a mutable value and change that value, not the binding. E.g., a list with one element.

Generators

[edit]

Introduced in Python 2.2 as an optional feature and finalized in version 2.3, generators are Python's mechanism for lazy evaluation of a function that would otherwise return a space-prohibitive or computationally intensive list.

This is an example to lazily generate the prime numbers:

import itertools

def generate_primes(stop_at: Optional[int] = None) -> Iterator[int]:
    primes: list[int] = []
    for n in itertools.count(start = 2):
        if stop_at is not None and n > stop_at:
            return # raises the StopIteration exception
        composite: bool = False
        for p in primes:
            if not n % p:
                composite = True
                break
            elif p ** 2 > n:
                break
        if not composite:
            primes.append(n)
            yield n

When calling this function, the returned value can be iterated over much like a list:

for i in generate_primes(100):  # iterate over the primes between 0 and 100
    print(i)

for i in generate_primes():  # iterate over ALL primes indefinitely
    print(i)

The definition of a generator appears identical to that of a function, except the keyword yield is used in place of return. However, a generator is an object with persistent state, which can repeatedly enter and leave the same scope. A generator call can then be used in place of a list, or other structure whose elements will be iterated over. Whenever the for loop in the example requires the next item, the generator is called, and yields the next item.

Generators do not have to be infinite like the prime-number example above. When a generator terminates, an internal exception is raised which indicates to any calling context that there are no more values. A for loop or other iteration will then terminate.

Generator expressions

[edit]

Introduced in Python 2.4, generator expressions are the lazy evaluation equivalent of list comprehensions. Using the prime number generator provided in the above section, we might define a lazy, but not quite infinite collection.

import itertools

primes_under_million: Iterator[int] = (i for i in generate_primes() if i < 1000000)
two_thousandth_prime: Iterator[int] = itertools.islice(primes_under_million, 1999, 2000).next()

Most of the memory and time needed to generate this many primes will not be used until the needed element is actually accessed. Unfortunately, you cannot perform simple indexing and slicing of generators, but must use the itertools module or "roll your own" loops. In contrast, a list comprehension is functionally equivalent, but is greedy in performing all the work:

primes_under_million: list[int] = [i for i in generate_primes(2000000) if i < 1000000]
two_thousandth_prime: int = primes_under_million[1999]

The list comprehension will immediately create a large list (with 78498 items, in the example, but transiently creating a list of primes under two million), even if most elements are never accessed. The generator comprehension is more parsimonious.

Dictionary and set comprehensions

[edit]

While lists and generators had comprehensions/expressions, in Python versions older than 2.7 the other Python built-in collection types (dicts and sets) had to be kludged in using lists or generators:

squares = dict((n, n * n) for n in range(5))
# in Python 3.5 and later the type of squares is dict[int, int]
print(squares)
# prints {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Python 2.7 and 3.0 unified all collection types by introducing dictionary and set comprehensions, similar to list comprehensions:

print([n * n for n in range(5)])  # regular list comprehension
# prints [0, 1, 4, 9, 16]
print({n * n for n in range(5)})  # set comprehension
# prints {0, 1, 4, 9, 16}
print({n: n * n for n in range(5)})  # dict comprehension
# prints {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Objects

[edit]

Python supports most object-oriented programming (OOP) techniques. It allows polymorphism, not only within a class hierarchy but also by duck typing. Any object can be used for any type, and it will work so long as it has the proper methods and attributes. And everything in Python is an object, including classes, functions, numbers and modules. Python also has support for metaclasses, an advanced tool for enhancing classes' functionality. Naturally, inheritance, including multiple inheritance, is supported. Python has very limited support for private variables using name mangling which is rarely used in practice as information hiding is seen by some as unpythonic, in that it suggests that the class in question contains unaesthetic or ill-planned internals. The slogan "we're all responsible users here" is used to describe this attitude.[30]

As is true for modules, classes in Python do not put an absolute barrier between definition and user, but rather rely on the politeness of the user not to "break into the definition."

— 9. Classes, The Python 2.6 Tutorial (2013)

OOP doctrines such as the use of accessor methods to read data members are not enforced in Python. Just as Python offers functional-programming constructs but does not attempt to demand referential transparency, it offers an object system but does not demand OOP behavior. Moreover, it is always possible to redefine the class using properties (see Properties) so that when a certain variable is set or retrieved in calling code, it really invokes a function call, so that spam.eggs = toast might really invoke spam.set_eggs(toast). This nullifies the practical advantage of accessor functions, and it remains OOP because the property eggs becomes a legitimate part of the object's interface: it need not reflect an implementation detail.

In version 2.2 of Python, "new-style" classes were introduced. With new-style classes, objects and types were unified, allowing the subclassing of types. Even entirely new types can be defined, complete with custom behavior for infix operators. This allows for many radical things to be done syntactically within Python. A new method resolution order for multiple inheritance was also adopted with Python 2.3. It is also possible to run custom code while accessing or setting attributes, though the details of those techniques have evolved between Python versions.

With statement

[edit]

The with statement handles resources, and allows users to work with the Context Manager protocol.[31] One function (__enter__()) is called when entering scope and another (__exit__()) when leaving. This prevents forgetting to free the resource and also handles more complicated situations such as freeing the resource when an exception occurs while it is in use. Context Managers are often used with files, database connections, test cases, etc.

Properties

[edit]

Properties allow specially defined methods to be invoked on an object instance by using the same syntax as used for attribute access. An example of a class defining some properties is:

class MyClass:
    def __init__(self):
        self._a: int = 0

    @property
    def a(self) -> int:
        return self._a

    @a.setter  # makes the property writable
    def a(self, value: int) -> None:
        self._a = value

Descriptors

[edit]

A class that defines one or more of the three special methods __get__(self, instance, owner), __set__(self, instance, value), __delete__(self, instance) can be used as a descriptor. Creating an instance of a descriptor as a class member of a second class makes the instance a property of the second class.[32]

Class and static methods

[edit]

Python allows the creation of class methods and static methods via the use of the @classmethod and @staticmethod decorators. The first argument to a class method is the class object instead of the self-reference to the instance. A static method has no special first argument. Neither the instance, nor the class object is passed to a static method.

Exceptions

[edit]

Python supports (and extensively uses) exception handling as a means of testing for error conditions and other "exceptional" events in a program.

Python style calls for the use of exceptions whenever an error condition might arise. Rather than testing for access to a file or resource before actually using it, it is conventional in Python to just go ahead and try to use it, catching the exception if access is rejected.

Exceptions can also be used as a more general means of non-local transfer of control, even when an error is not at issue. For instance, the Mailman mailing list software, written in Python, uses exceptions to jump out of deeply nested message-handling logic when a decision has been made to reject a message or hold it for moderator approval.

Exceptions are often used as an alternative to the if-block, especially in threaded situations. A commonly invoked motto is EAFP, or "It is Easier to Ask for Forgiveness than Permission,"[33] which is attributed to Grace Hopper.[34][35] The alternative, known as LBYL, or "Look Before You Leap", explicitly tests for pre-conditions.[36]

In this first code sample, following the LBYL approach, there is an explicit check for the attribute before access:

if hasattr(spam, "eggs"):
    ham = spam.eggs
else:
    handle_missing_attr()

This second sample follows the EAFP paradigm:

try:
    ham = spam.eggs
except AttributeError:
    handle_missing_attr()

These two code samples have the same effect, although there will be performance differences. When spam has the attribute eggs, the EAFP sample will run faster. When spam does not have the attribute eggs (the "exceptional" case), the EAFP sample will run slower. The Python profiler can be used in specific cases to determine performance characteristics. If exceptional cases are rare, then the EAFP version will have superior average performance than the alternative. In addition, it avoids the whole class of time-of-check to time-of-use (TOCTTOU) vulnerabilities, other race conditions,[35][37] and is compatible with duck typing. A drawback of EAFP is that it can be used only with statements; an exception cannot be caught in a generator expression, list comprehension, or lambda function.

Comments and docstrings

[edit]

There are two ways to annotate Python code. One is by using comments to indicate what some part of the code does. Single-line comments begin with the hash character (#) and continue until the end of the line. Comments spanning more than one line are achieved by inserting a multi-line string (with """ or ''' as the delimiter on each end) that is not used in assignment or otherwise evaluated, but sits in between other statements.

Commenting a piece of code:

import sys

def getline() -> str:
    return sys.stdin.readline()  # Get one line and return it

Commenting a piece of code with multiple lines:

def getline() -> str:
    """This function gets one line and returns it.

    As a demonstration, this is a multiline docstring.

    This full string can be accessed as getline.__doc__.
    """
    return sys.stdin.readline()

Docstrings (documentation strings), that is, strings that are located alone without assignment as the first indented line within a module, class, method or function, automatically set their contents as an attribute named __doc__, which is intended to store a human-readable description of the object's purpose, behavior, and usage. The built-in help function generates its output based on __doc__ attributes. Such strings can be delimited with " or ' for single line strings, or may span multiple lines if delimited with either """ or ''' which is Python's notation for specifying multi-line strings. However, the style guide for the language specifies that triple double quotes (""") are preferred for both single and multi-line docstrings.[38]

Single-line docstring:

def getline() -> str:
    """Get one line from stdin and return it."""
    return sys.stdin.readline()

Multi-line docstring:

def getline() -> str:
    """Get one line
       from stdin
       and return it.
    """
    return sys.stdin.readline()

Docstrings can be as large as the programmer wants and contain line breaks. In contrast with comments, docstrings are themselves Python objects and are part of the interpreted code that Python runs. That means that a running program can retrieve its own docstrings and manipulate that information, but the normal usage is to give other programmers information about how to invoke the object being documented in the docstring.

There are tools available that can extract the docstrings from Python code and generate documentation. Docstring documentation can also be accessed from the interpreter with the help() function, or from the shell with the pydoc command pydoc.

The doctest standard module uses interactions copied from Python shell sessions into docstrings to create tests, whereas the docopt module uses them to define command-line options.

Decorators

[edit]

A decorator is any callable Python object that is used to modify a function, method or class definition. A decorator is passed the original object being defined and returns a modified object, which is then bound to the name in the definition. Python decorators were inspired in part by Java annotations, and have a similar syntax; the decorator syntax is pure syntactic sugar, using @ as the keyword:

@viking_chorus
def menu_item() -> None:
    print("spam")

is equivalent to

def menu_item() -> None:
    print("spam")
menu_item = viking_chorus(menu_item)

Decorators are a form of metaprogramming; they enhance the action of the function or method they decorate. For example, in the sample below, viking_chorus might cause menu_item to be run 8 times (see Spam sketch) for each time it is called:

R: TypeVar = TypeVar("R")

def viking_chorus(myfunc: Callable[..., R]) -> Callable[..., None]:
    def inner_func(*args: tuple[Any, ...], **kwargs: dict[Any, Any]):
        for i in range(8):
            myfunc(*args, **kwargs)
    return inner_func

Canonical uses of function decorators are for creating class methods or static methods, adding function attributes, tracing, setting pre- and postconditions, and synchronization,[39] but can be used for far more, including tail recursion elimination,[40] memoization and even improving the writing of other decorators.[41]

Decorators can be chained by placing several on adjacent lines:

@invincible
@favourite_colour("Blue")
def black_knight() -> None:
    pass

is equivalent to

def black_knight() -> None:
    pass
black_knight = invincible(favourite_colour("Blue")(black_knight))

or, using intermediate variables

def black_knight() -> None:
    pass
blue_decorator = favourite_colour("Blue")
decorated_by_blue = blue_decorator(black_knight)
black_knight = invincible(decorated_by_blue)

In the example above, the favourite_colour decorator factory takes an argument. Decorator factories must return a decorator, which is then called with the object to be decorated as its argument:

def favourite_colour(colour: str) -> Callable[[Callable[[], R]], Callable[[], R]]:
    def decorator(func: Callable[[], R]) -> Callable[[], R]:
        def wrapper() -> R:
            print(colour)
            func()
        return wrapper
    return decorator

This would then decorate the black_knight function such that the colour, "Blue", would be printed prior to the black_knight function running. Closure ensures that the colour argument is accessible to the innermost wrapper function even when it is returned and goes out of scope, which is what allows decorators to work.

Despite the name, Python decorators are not an implementation of the decorator pattern. The decorator pattern is a design pattern used in statically-typed object-oriented programming languages to allow functionality to be added to objects at run time; Python decorators add functionality to functions and methods at definition time, and thus are a higher-level construct than decorator-pattern classes. The decorator pattern itself is trivially implementable in Python, because the language is duck typed, and so is not usually considered as such.[clarification needed]

Easter eggs

[edit]

Users of curly bracket languages, such as C or Java, sometimes expect or wish Python to follow a block-delimiter convention. Brace-delimited block syntax has been repeatedly requested, and consistently rejected by core developers. The Python interpreter contains an easter egg that summarizes its developers' feelings on this issue. The code from __future__ import braces raises the exception SyntaxError: not a chance. The __future__ module is normally used to provide features from future versions of Python.

Another hidden message, the Zen of Python (a summary of Python design philosophy), is displayed when trying to import this.

The message Hello world! is printed when the import statement import __hello__ is used. In Python 2.7, instead of Hello world! it prints Hello world....

Importing the antigravity module opens a web browser to xkcd comic 353 that portrays a humorous fictional use for such a module, intended to demonstrate the ease with which Python modules enable additional functionality.[42] In Python 3, this module also contains an implementation of the "geohash" algorithm, a reference to xkcd comic 426.[43]

References

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
Python syntax and semantics encompass the formal rules for structuring code and the defined meanings of those structures in the Python programming language, emphasizing readability, simplicity, and consistency to facilitate rapid development and maintenance. As detailed in the official language reference, the syntax is governed by a context-free grammar that includes lexical analysis for tokenizing source code, while core semantics cover the data model of objects and types, the execution model involving namespaces and evaluation, and specific behaviors for expressions, statements, and control flow. A hallmark of Python's syntax is its use of indentation—typically four spaces—to delineate code blocks, replacing traditional braces or keywords found in many other languages, which promotes visual clarity and reduces errors from mismatched delimiters. For instance, compound statements like loops and conditionals rely on consistent indentation levels:

python

if condition: # indented block statement else: # another indented block statement

if condition: # indented block statement else: # another indented block statement

Simple statements, such as assignments or function calls, terminate at line ends without requiring semicolons, though multiple can be combined on one line using them if needed. Expressions support operator precedence and associativity, enabling concise arithmetic, logical, and bitwise operations, with dynamic typing allowing variables to hold values of any type without explicit declarations. Semantically, Python is dynamically typed and interpreted, meaning type checking occurs at runtime and code executes line-by-line via the Python Virtual Machine (PVM), supporting interactive development and just-in-time evaluation. The data model treats all values as objects with attributes and methods, enabling uniform handling across built-in types like integers, strings, and lists, while the execution model manages name resolution through lexical scoping and dynamic attribute access. These features, combined with support for modules, classes, and exceptions, make Python's syntax and semantics particularly suited for scripting, , , and more, as they prioritize expressiveness without sacrificing performance in core operations.

Basic Syntax Elements

Keywords

In Python, keywords are reserved words with predefined meanings in the language's syntax, which cannot be used as ordinary such as variable or function names. These keywords form the building blocks for control structures, definitions, and other language constructs. As of Python 3.14, there are exactly 35 such keywords, which are case-sensitive and must be spelled precisely to avoid syntax errors. The following table lists all keywords along with brief descriptions of their primary roles:
KeywordRole
FalseBoolean literal representing falsity.
NoneSingleton representing the absence of a value.
TrueBoolean literal representing truth.
andLogical operator for conjunction in expressions.
asAlias for imports or exception handling.
assertFor debugging assertions.
asyncPrefix for asynchronous functions and comprehensions.
awaitSuspends execution in asynchronous coroutines.
breakExits the nearest enclosing loop.
classDefines a new class.
continueSkips to the next iteration of a loop.
defDefines a function.
delDeletes a reference, slice, or variable.
elifConditional branch in if statements.
elseDefault branch in conditionals or loops.
exceptHandles exceptions in try blocks.
finallyEnsures cleanup code runs after try-except.
forIterates over sequences or iterables.
fromImports specific items from a module.
globalDeclares a variable as global in scope.
ifConditional execution.
importImports modules or packages.
inMembership test operator.
isIdentity comparison operator.
lambdaCreates anonymous functions.
nonlocalRefers to variables in enclosing scopes.
notLogical negation operator.
orLogical operator for disjunction in expressions.
passNo-op placeholder.
raiseRaises an exception.
returnExits a function with a value.
tryStarts exception handling block.
whileRepeats code while a condition holds.
withManages context with resource handling.
yieldYields values from generators.
Keywords like if, def, and for initiate code blocks that rely on indentation for structure, distinguishing Python's whitespace-sensitive syntax from brace-delimited languages. Among these, async and await are specifically used to define and pause asynchronous , enabling concurrent programming without threads; they were introduced in Python 3.5 via PEP 492 to support native coroutine syntax. No new keywords have been added in Python 3.14, maintaining the set unchanged from Python 3.7 onward. Identifiers in Python must follow specific rules: they begin with a letter (A-Z, a-z) or , followed by letters, underscores, or digits, and may include characters normalized per NFKC. Attempting to use a keyword as an identifier triggers a SyntaxError, as the tokenizer recognizes it as reserved. For example, the code def = 5 raises SyntaxError: invalid syntax because def is reserved for function definitions. Similarly, class = "example" would fail with the same error. This reservation ensures keywords retain their syntactic roles across all Python versions.

Indentation and Structure

Python's syntax relies on indentation to delineate code blocks, distinguishing it from languages that use delimiters like braces. Leading whitespace at the start of a logical line determines the indentation level, which is used to group statements into blocks following compound statements such as if, while, for, try, or def. This approach enforces visual structure that mirrors the program's logical hierarchy, promoting readability while embedding scoping semantics directly into the code layout. Consistency in indentation is mandatory within a block; Python permits either spaces or tabs but prohibits mixing them in ways that lead to ambiguous levels, as the interpreter expands tabs to spaces assuming eight-space width. The Python style guide recommends using four spaces per indentation level to maintain uniformity across projects, avoiding tabs entirely to prevent portability issues across editors. Semantically, indentation defines the scope and execution flow of code blocks without explicit markers, creating nested namespaces for functions, loops, and conditionals. For instance, statements at the same indentation level belong to the same block, while increased indentation signals entry into a sub-block, and dedentation indicates its end. This whitespace-sensitive affects control structures by associating statements with their enclosing contexts, ensuring that namespaces are isolated per module, function, or class. Indentation inconsistencies trigger specific exceptions during parsing. An IndentationError is raised for mismatched levels, such as unexpected indents or required blocks missing after a compound statement header. A TabError, subclass of IndentationError, occurs when tabs and spaces are mixed inconsistently. For example, the following code raises IndentationError: expected an indented block after 'if' statement on line 2:

if True: print("This should be indented")

if True: print("This should be indented")

Similarly, mixing tabs and spaces might yield TabError: inconsistent use of tabs and spaces in indentation. Python supports multi-line statements to handle long expressions without horizontal sprawl. Explicit line joining uses a backslash (\) at the end of a physical line to continue it logically, preserving the indentation of the initial line; however, backslashes cannot split indentation itself. Implicit joining occurs within parentheses, square brackets, or curly braces, where physical line breaks are ignored, and indentation need not align strictly as long as the overall structure is valid. For example:

total = (value1 + value2 + value3)

total = (value1 + value2 + value3)

This allows clean formatting of function calls, lists, or arithmetic without backslashes. Since Python 3.0, the core mechanics of indentation and structure have remained stable, with no fundamental syntax changes. However, starting in Python 3.10, error messages for IndentationError were enhanced to pinpoint issues more precisely, such as indicating the expected block after specific statements. Python 3.11 further refined these messages for better diagnostics in cases of mismatched whitespace.

Comments and Documentation

In Python, comments are non-executable annotations that enhance code readability without affecting program execution. Single-line comments begin with the hash symbol (#) and extend to the end of the line, where they are completely ignored by the interpreter. These comments can be placed at the beginning of a line, after initial whitespace, or inline after a statement, but a # within a string literal is treated as a regular character rather than a comment delimiter. For example:

# This is a standalone comment explaining the code below x = 1 # Inline comment describing the assignment

# This is a standalone comment explaining the code below x = 1 # Inline comment describing the assignment

This placement allows developers to document intent, such as variable purposes or algorithmic steps, while maintaining clean code structure. Inline comments should be separated from the preceding code by at least two spaces for clarity, as recommended in style guidelines. Documentation in Python extends beyond simple comments through docstrings, which are specially formatted string literals that provide structured descriptions for modules, classes, functions, and methods. Docstrings are enclosed in triple double quotes (""") and must appear as the first statement in their respective definition, becoming the __doc__ special attribute of the object. Unlike regular comments, docstrings are executable strings but are not evaluated; instead, they enable runtime introspection and automated tools. For instance:

def add(a, b): """Return the sum of two numbers. Args: a (int): The first number. b (int): The second number. Returns: int: The sum of a and b. """ return a + b

def add(a, b): """Return the sum of two numbers. Args: a (int): The first number. b (int): The second number. Returns: int: The sum of a and b. """ return a + b

Accessing add.__doc__ retrieves this string, while the built-in help(add) function displays it in a formatted manner, facilitating interactive exploration in environments like the REPL. This semantic role supports code maintainability by embedding usage details directly in the source, allowing tools like pydoc to generate external documentation. Best practices for docstrings emphasize consistency and completeness, as outlined in PEP 257. They should be placed immediately after the def or class keyword, with a blank line separating class docstrings from subsequent attributes. One-line docstrings suit simple cases, consisting of a concise summary ending in a period, while multi-line versions include a summary line, a blank line, and sections for arguments, return values, and exceptions. All public modules, functions, classes, and methods warrant docstrings to ensure comprehensive coverage. Python 3.14 introduces no syntactic changes to comments or docstrings, preserving their established forms since earlier versions. However, enhancements to the REPL include default , which improves the visual display of docstrings when viewed via help() or similar , using color-coded elements for better readability unless disabled.

Literals and Basic Data Types

Numeric Literals

Numeric literals in Python provide a direct way to express , floating-point, and complex numbers in , serving as the primary means to introduce numeric values without computation. These literals are parsed during and evaluated to create immutable objects of the corresponding built-in types: int, float, or complex. The syntax supports multiple bases for integers and for floats, ensuring flexibility in representation while adhering to standard conventions for precision and overflow handling. Integer literals represent whole numbers and can be specified in , , , or . A uses standard digits without a prefix, such as 123 or 2147483647, and leading zeros are not permitted except for the value zero itself to avoid confusion with notation. integers are prefixed with 0x or 0X followed by hexadecimal digits (0-9, a-f, or A-F), for example, 0x7b which equals 123 in . literals use 0o or 0O as a prefix with digits (0-7), like 0o123 equaling 83 in , while binary literals employ 0b or 0B with binary digits (0-1), such as 0b1110111 for 119. Since Python 3.6, underscores (_) may be inserted between digits for readability in any literal, as in 1_000_000 or 0xFF_FF, but they are ignored during evaluation. Floating-point literals denote approximate real numbers using decimal or hexadecimal notation. The basic form consists of an optional integer part, a decimal point, an optional fractional part, and an optional exponent, such as 3.14 or 3e4 (which equals 30000.0). Leading zeros are allowed, and underscores can separate digits since Python 3.6, for instance, 3.141_59. Python also supports hexadecimal floating-point literals with the prefix 0x or 0X, a hexadecimal fraction, and a power-of-2 exponent prefixed by p or P, like 0x1.0p0 equaling 1.0; this format aids in precise representation of binary floating-point values. Complex literals express numbers with real and imaginary parts, formed by adding an imaginary literal to a numeric literal using the imaginary unit j or J. The imaginary part follows immediately after the real part without spaces, such as 3+4j or 1.5-2.0j, where both parts can be integers or floats. A standalone imaginary literal like 4j is equivalent to 0+4j. The real and imaginary components are stored as float values. Semantically, all numeric literals yield immutable objects, meaning their values cannot be altered after creation. Integers provide exact representation with unlimited precision, limited only by available memory, and since Python 3.0, there is no overflow—arithmetic operations on integers automatically handle arbitrarily large values without , unlike the fixed-size long type in Python 2. Floating-point literals conform to the double-precision standard, offering approximately 15 decimal digits of precision but subject to rounding errors inherent in binary representation. Complex numbers similarly use for both real and imaginary parts, ensuring consistent behavior in computations. Numeric signs (e.g., -123) are treated as unary operators applied to positive literals, not part of the literal itself. The syntax for numeric literals has remained stable through recent versions, with no changes introduced in Python 3.14. However, Python 3.12 enhanced support for float-style formatting in related types like fractions.Fraction, improving interoperability with numeric literals in output scenarios, though this does not alter literal parsing.

String Literals

String literals in Python represent sequences of characters and are defined using single quotes (') or double quotes ("), allowing flexibility in including the opposite quote type without escaping. These literals support escape sequences to represent special characters, such as \n for , \t for tab, \\ for , and \' or \" for quotes. For and Unicode escapes, sequences like \xhh (two hex digits) and \uXXXX (four hex digits) or \UXXXXXXXX (eight hex digits) enable inclusion of non-ASCII characters. Triple-quoted strings, delimited by ''' or """, allow multi-line text while preserving newlines and permitting unescaped quotes of the opposite type. Raw strings, prefixed with r or R, treat backslashes as literal characters, ignoring escape sequences except for the closing quote, which is useful for regular expressions. Bytes literals, prefixed with b or B (e.g., b'ABC'), represent immutable sequences of 8-bit values (0-255) suitable for low-level storage and transmission; they use ASCII characters and similar escape rules, with the full range accessible via hexadecimal escapes like \x00 to \xff. Unicode strings can be converted to bytes using the str.encode() method, which translates Unicode code points to bytes via a specified encoding (default UTF-8). Prefixes can combine for specialized literals, such as u or U for explicit strings (though all strings are Unicode by default since Python 3.0). Formatted string literals, or f-strings, prefixed with f or F (introduced in Python 3.6), embed expressions inside curly braces for runtime evaluation. Combinations like rb or fr are supported since Python 3.3. Template string literals, prefixed with t or T (introduced in Python 3.14 via PEP 750), create a Template object from the string.templatelib module. Unlike f-strings, they do not evaluate expressions immediately but allow access to static parts (strings attribute) and interpolated placeholders (interpolations attribute) for custom processing, such as sanitization in web templating or domain-specific languages. They support similar syntax to f-strings, including nested expressions and quotes, but do not combine with other prefixes like f, r, or b. For example, t"Hello {name}" returns a Template object that can be processed by a custom function, such as html(t"<p>{user_input}</p>") to escape HTML. This feature provides a safer alternative to f-strings for untrusted data. Adjacent string literals are implicitly concatenated, so 'hello' 'world' evaluates to 'helloworld', applicable across quoted, raw, bytes, and f-string types. Semantically, Python strings are immutable sequences of code points, and cannot be modified after creation. Source code containing string literals is encoded in by default since Python 3.0, ensuring Unicode support. In Python 3.14, template string literals were introduced as a new form of string literal syntax. Triple-quoted strings are commonly used for docstrings to provide .

Boolean and None Literals

In Python, the boolean literals True and False are reserved keywords that represent the two possible truth values of the language. These literals are case-sensitive and cannot be reassigned or used as variable names due to their status as keywords. The None literal is similarly a reserved keyword serving as the sole instance of the NoneType class, used to denote the absence of a value or a null reference. Semantically, True and False are instances of the bool type, which is a subclass of the int type, allowing boolean values to interoperate with integers where True evaluates to 1 and False to 0. This inheritance enables operations like arithmetic on booleans, such as True + False yielding 1, though such usage is generally discouraged in favor of explicit integer literals for clarity. The bool type was formally introduced in Python 2.3 via PEP 285 to provide a dedicated boolean type with consistent semantics across operations that return truth values. No changes to these semantics have occurred up to Python 3.14. In boolean contexts, such as conditional statements, Python employs a concept known as "truthiness" to evaluate the falsity or truth of any object. Values considered false include False, None, the integer zero (including 0.0 and 0j), empty sequences (like '', (), []), and empty mappings or sets (like {}, set()). All other objects, including non-empty collections and non-zero numbers, are treated as true. User-defined classes can override this behavior by implementing the __bool__() special method, which should return a boolean value. For None specifically, its role as a singleton ensures that all references to it point to the same object, promoting efficient identity checks via the is operator, as in if value is None:. Equality comparisons with == also work but are less efficient for None due to its unique implementation; the language convention strongly favors is None or is not None for precision and performance. This design avoids the pitfalls of null pointer exceptions found in other languages by making None a first-class object rather than an uninitialized state.

python

# Example of boolean literals in a conditional if True: print("This executes") # Output: This executes # Truthiness with None and empty collection value = None if not value: # Evaluates as true because None is falsy print("None is falsy") empty_list = [] if not empty_list: # Evaluates as true print("Empty list is falsy") # Identity check for None result = some_function() # Assume returns None if result is None: print("No value returned")

# Example of boolean literals in a conditional if True: print("This executes") # Output: This executes # Truthiness with None and empty collection value = None if not value: # Evaluates as true because None is falsy print("None is falsy") empty_list = [] if not empty_list: # Evaluates as true print("Empty list is falsy") # Identity check for None result = some_function() # Assume returns None if result is None: print("No value returned")

These literals form the foundation for conditional logic and null handling, with True and False often resulting from evaluations involving logical operators like and, or, and not.

Collections and Data Structures

List, Tuple, and Sequence Literals

Lists and tuples are fundamental sequence types in Python, created using literals that define ordered collections of objects. A list literal is enclosed in square brackets, such as [1, 2, 3], and represents a mutable sequence that can be modified after creation. The empty list is denoted by []. In contrast, a tuple literal consists of comma-separated values, optionally enclosed in parentheses, such as (1, 2, 3) or simply 1, 2, 3, and is immutable, meaning its contents cannot be altered once defined. The empty tuple is (), while a singleton tuple requires a trailing comma, like (1,), to distinguish it from a parenthesized expression. Both and tuples support indexing to access elements by zero-based position, using square brackets: for example, seq[0] retrieves the first element, and negative indices like seq[-1] access from the end. Slicing extracts subsequences with notation seq[start:end], producing a new or tuple of the specified range; an optional step parameter allows strides, as in seq[::2] for every other element. These operations are defined for all sequence types, with returning mutable slices and tuples returning immutable ones. Sequence unpacking assigns elements to variables, such as a, b = (1, 2), which requires the number of variables to match the sequence length; this feature has been available since Python 3.0. Extended unpacking using the * operator, introduced in Python 3.5 via PEP 448, allows flexible assignments like first, *rest = [1, 2, 3, 4], where rest captures remaining elements as a list. No further syntax changes for these literals or operations have occurred through Python 3.14. List literals can also incorporate comprehensions for concise creation, though detailed syntax for those is covered separately.

Set and Dictionary Literals

Set literals in Python provide a concise syntax for creating unordered collections of unique, hashable elements using curly braces enclosing , such as {1, 2, 3}. This syntax automatically removes duplicates and requires all elements to be hashable, meaning they must be immutable types like integers, strings, or tuples whose hash value remains constant. An cannot be created with {}—which instead denotes an —but must use the set() constructor. For an immutable variant, the frozenset() constructor creates a hashable set that cannot be modified after creation, supporting the same operations as sets but allowing use as keys or set elements. Dictionary literals use curly braces to define mappings of hashable keys to arbitrary values, formatted as comma-separated key-value pairs with colons, for example {'a': 1, 'b': 2}. Keys must be hashable and thus immutable, such as numbers, strings, or frozensets, to ensure consistent hashing for lookup efficiency; attempting to use mutable types like lists as keys raises a TypeError. The empty dictionary is simply {}. Prior to Python 3.7, dictionaries were unordered, with iteration order not guaranteed; since Python 3.7, they preserve insertion order as a language feature, iterating keys in the sequence they were added. Dictionary displays support unpacking with the ** operator since Python 3.5, allowing merges like {**d1, 'new': value, **d2} where later items override earlier duplicates, per PEP 448. Dictionary comprehensions, introduced in Python 2.7 as a backport from Python 3.0 via PEP 274, enable concise creation with syntax like {x: x**2 for x in range(3)}, evaluating keys left-to-right and preserving insertion order in Python 3.7+. No syntax changes for set or dictionary literals have occurred in Python 3.14.

Built-in Type System Overview

Python's built-in type system encompasses a set of core fundamental types that form the foundation of its data model, including numeric types such as int (arbitrary-precision integers), float (double-precision floating-point numbers), and complex; sequence types like str (Unicode strings), list (ordered mutable collections), and tuple (ordered immutable collections); mapping types such as dict (unordered mutable key-value stores); set types including set (unordered mutable collections of unique elements) and frozenset (immutable variant); the boolean type bool (a subclass of int with values True and False); and the singleton type NoneType represented by None. These types are instantiated through literals, such as 42 for int or ["a", "b"] for list. At its core, Python's adheres to the principle that everything in the is an object, meaning all values are instances of classes with associated methods and attributes, enabling a treatment across types. The system employs dynamic typing, where variables do not require explicit type declarations and can reference objects of any compatible type at runtime, promoting flexibility in code. Object identity, which distinguishes distinct instances even if their values are equal, is accessible via the id() function, returning an unique to each object during its lifetime. For instance:

python

x = 42 y = 42 print(id(x) == id(y)) # May be True due to small integer caching, but not guaranteed z = [42] print(id(x) == id(z)) # False, as they are different objects

x = 42 y = 42 print(id(x) == id(y)) # May be True due to small integer caching, but not guaranteed z = [42] print(id(x) == id(z)) # False, as they are different objects

Mutability is a key semantic distinction: immutable types like int, float, str, tuple, bool, and NoneType cannot have their internal state modified after creation, ensuring thread safety and hashability for use in sets and dictionary keys, whereas mutable types such as list, dict, and set allow in-place changes to their contents. This design influences operations; for example, assigning a mutable object to a variable creates a reference, so modifications affect all references, while immutable types create new objects on reassignment. Python embraces as a for type compatibility, prioritizing an object's behavior—specifically, whether it supports the required methods or attributes—over its explicit class or type, avoiding runtime checks like isinstance() in favor of "if it walks like a and quacks like a , then it is a ." This approach, complemented by abstract base classes for optional structural , fosters polymorphic code that works across types without rigid inheritance hierarchies. Type hints, introduced to support optional static analysis, allow annotations for better code readability and tooling but are not enforced at runtime, maintaining the dynamic nature of the system. The has remained stable across versions, with enhancements like native syntax (e.g., int | str) in annotations introduced in Python 3.10 via PEP 604, though details on advanced features are covered elsewhere.

Operators and Expressions

Arithmetic and Bitwise Operators

Python provides a set of arithmetic operators for performing mathematical operations on numeric types such as integers and floating-point numbers, as well as bitwise operators for manipulating the binary representations of integers. These operators follow specific rules for precedence and associativity, ensuring unambiguous evaluation of expressions. The arithmetic operators include (+), (-), (*), true division (/), floor division (//), (%), and (**). Bitwise operators encompass AND (&), OR (|), XOR (^), unary NOT (~), left shift (<<), and right shift (>>). Additionally, since Python 3.5, the matrix operator (@) has been available for compatible types like arrays, though it is not applicable to built-in numeric types. Arithmetic operations are defined for integers, floats, and complex numbers, with results promoting to the wider type as needed (e.g., int + float yields float). In Python 3.14, mixed-mode arithmetic rules for real and complex numbers were updated to conform to the standard. The addition operator (+) computes the sum of two numbers, as in 3 + 5 evaluating to 8. (-) yields the difference, such as 10 - 4 resulting in 6. (*) scales values, for example, 2 * 3 produces 6. True division (/) always returns a float, even for integer operands, like 7 / 2 giving 3.5. Floor division (//) performs division and rounds the quotient down to the nearest integer toward negative infinity, so 7 // 2 is 3 and -7 // 2 is -4. The modulo operator (%) computes the , consistent with the sign of the : 7 % 2 is 1 and -7 % 2 is 1. (**) raises the base to the power of the exponent, such as 2 ** 3 equaling 8; it is right-associative, meaning 2 ** 3 ** 2 evaluates as 2 ** (3 ** 2) or 512. Bitwise operators apply only to integers, treating them as binary numbers with arbitrary precision. The AND operator (&) performs a bitwise conjunction, setting bits in the result where both operands have 1s, e.g., 5 & 3 (binary 101 & 011) yields 1 (binary 001). OR (|) sets bits where at least one operand has a 1, so 5 | 3 results in 7 (binary 111). XOR (^) toggles bits where operands differ, as in 5 ^ 3 giving 6 (binary 110). Unary NOT (~) inverts all bits, equivalent to -x - 1 for x, so ~5 is -6. Left shift (<<) multiplies by powers of 2, with 5 << 1 equaling 10. Right shift (>>) divides by powers of 2, the result, such that 5 >> 1 is 2. These operations preserve sign for negative numbers via arithmetic shifting. Operator precedence determines evaluation order, with (**) having the highest priority among binary operators, followed by unary operators like ~ and unary -, then multiplicative operators (*, @, /, //, %), additive (+, -), and finally bitwise shifts (<<, >>), AND (&), XOR (^), and OR (|). All listed operators associate left-to-right except **, which is right-to-left. Parentheses () can override precedence for clarity, as in (2 + 3) * 4 evaluating to 20. This precedence structure has remained unchanged through Python 3.14.

Comparison, Logical, and Membership Operators

Python's comparison operators evaluate the relationship between two operands and yield a boolean result, True or False. The equality operator == returns True if the operands have the same value, while != returns True if they differ. The ordering operators <, <=, >, and >= assess relative magnitude, with < yielding True if the left operand is less than the right, and similarly for the others. These operators support rich comparisons for custom classes via special methods like __eq__() and __lt__(). A distinctive feature is the ability to chain comparison operators, such as x < y <= z, which is semantically equivalent to x < y and y <= z but evaluates the intermediate operand y only once, promoting efficiency and readability. Chaining extends left-to-right with equal precedence among comparisons, membership tests, and identity tests, all below arithmetic operators. This chaining mechanism has been part of Python since its early versions and remains unchanged in Python 3.14. For example:

python

a, b, c = 1, 2, 3 result = a < b <= c # Evaluates to True, with b computed once

a, b, c = 1, 2, 3 result = a < b <= c # Evaluates to True, with b computed once

If any pairwise comparison in a chain yields False, the entire expression short-circuits to False without further evaluations. Logical operators combine boolean values or evaluate truthiness in expressions. The and operator returns its first argument if it is falsy, otherwise the second; or returns its first argument if truthy, otherwise the second. Both employ short-circuit evaluation, skipping the second operand when the outcome is determined by the first. The unary not operator negates the truth value of its operand. Precedence orders them as not highest, then and, then or, all with left-to-right associativity.

python

x = 0 y = True print(x and y) # Outputs 0 (falsy first operand, short-circuits) print(y or x) # Outputs True (truthy first operand, short-circuits) print(not x) # Outputs True

x = 0 y = True print(x and y) # Outputs 0 (falsy first operand, short-circuits) print(y or x) # Outputs True (truthy first operand, short-circuits) print(not x) # Outputs True

These operators do not always return booleans; they return the actual operand values, which can be used for more concise conditional assignments. Membership operators test containment within collections. The in operator returns True if the left operand is a member of the right (a sequence, set, or iterable like dictionary keys), while not in returns the inverse. For sequences like lists or tuples, membership checks iterate until a match or exhaustion; for sets and dictionary keys, average O(1) time is typical due to hashing. These operators have the same precedence as comparisons and support chaining.

python

fruits = ['apple', 'banana'] print('apple' in fruits) # True print('orange' not in fruits) # True

fruits = ['apple', 'banana'] print('apple' in fruits) # True print('orange' not in fruits) # True

The semantics of these operators rely on Python's concept of truthiness for non-boolean objects in logical and conditional contexts. Values considered falsy include False, None, numeric zeros (e.g., 0, 0.0), empty sequences (e.g., [], ''), and empty mappings or sets; all other values are truthy. Custom types can define __bool__() to override this behavior. Logical operators and membership tests use this truthiness rather than strict boolean conversion. Additionally, identity operators is and is not test whether two variables reference the same object in memory, using id() equality, distinct from value comparison via ==. For example, 5 is 5 is True due to integer caching, but separate list instances compare False with is despite equal contents. These have the same precedence as comparisons and are useful for checking singletons like None. Such operators are fundamental in conditional statements, where their boolean results determine execution paths.

Assignment Operators and Expressions

In Python, assignment statements bind names to objects in the current scope, creating or updating references without copying the object itself. The basic syntax is target = expression, where the expression is evaluated to produce an object, and the target—typically an identifier—becomes a reference to that object in the local namespace, unless modified by global or nonlocal declarations. For mutable objects like lists, this allows modifications to affect all references, but assignment to a new name does not create aliases automatically, distinguishing it from languages like where multiple assignment might imply different semantics. Multiple targets can be assigned simultaneously, such as a = b = 1, which evaluates the expression once and binds it to each target from left to right. Unpacking extends this to assign values from an iterable to multiple targets, treating the right-hand side as a sequence and matching elements positionally. For example, a, b = 1, 2 assigns 1 to a and 2 to b, equivalent to tuple packing and unpacking. Extended unpacking with a starred expression, introduced in Python 3.0 via PEP 3132, captures remaining elements into a list, as in head, *tail = [1, 2, 3, 4], where head is 1 and tail is [2, 3, 4]. This requires the iterable to have at least as many elements as non-starred targets, or it raises a ValueError. Targets can also be tuples, lists, or attribute references, but nested structures must match the unpacked sequence's shape. Augmented assignment operators combine an operation with assignment, such as +=, -=, *=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=, and @=, allowing in-place modification where supported by the object's type. The syntax is target op= expression, where the target is evaluated first to obtain the current value, the expression is then computed, and the operation applied if an in-place method like __iadd__ exists; otherwise, it falls back to the binary operator and a standard assignment. For instance, counter += 1 is equivalent to counter = counter + 1 but more efficient for mutable types supporting in-place operations, such as lists, as it can modify the original object in place when possible. These operators were introduced in Python 2.0 to reduce redundancy in common update patterns. Assignment expressions, or the operator" :=, introduced in Python 3.8 via PEP 572, allow assignment within larger expressions, evaluating the right-hand side and binding the result to a name while yielding the value for further use. The syntax is name := expression, often parenthesized for clarity, as in if (n := len(s)) > 10: print(n). Semantically, it binds the name in the current scope like a statement but returns the value, enabling reuse without recomputation, such as in loops or comprehensions: print(sum(line for line in file if (n := len(line)) > 80). This feature addresses code duplication in conditional checks and iterators, motivated by analyses showing frequent redundant computations in real Python codebases. No semantic changes to assignment expressions have occurred through Python 3.14.

Control Flow Constructs

Conditional Statements

Conditional statements in Python enable selective execution of code blocks based on the evaluation of expressions, forming a fundamental aspect of the language's mechanisms. These constructs allow programs to make decisions, branching execution paths depending on conditions such as user input, computational results, or environmental states. The primary conditional statement is the if construct, which can be extended with elif and else clauses for handling multiple alternatives, ensuring efficient and readable code organization. The syntax for a basic if statement begins with the keyword if followed by a condition expression, a colon, and an indented block of statements to execute if the condition evaluates to true. For example:

python

if x > 0: print("Positive")

if x > 0: print("Positive")

Here, the condition x > 0 is evaluated, and if true, the subsequent indented statements form the block executed. Indentation, typically four spaces, delineates the block's scope; consistent indentation is mandatory, as Python uses it to define compound statement bodies rather than braces or keywords. To handle multiple conditions, the elif (else if) clause follows the if block, allowing chained evaluations without nested structures. An optional else clause captures all remaining cases. The full syntax is:

python

if condition1: # statements for condition1 elif condition2: # statements for condition2 else: # statements for all other cases

if condition1: # statements for condition1 elif condition2: # statements for condition2 else: # statements for all other cases

Execution proceeds by evaluating conditions sequentially from top to bottom; the first true condition triggers its block, skipping subsequent ones, while else executes only if none match. This promotes linear readability over deep nesting. Conditions are expressions that yield a boolean value through truth value testing: objects are considered true unless they are None, False, zero (for numerics), empty sequences, or empty mappings. For concise conditional expressions, Python provides a ternary operator, also known as a conditional expression, which evaluates to a value based on a condition without defining blocks. The is value_if_true if condition else value_if_false, with evaluation short-circuiting: the condition is checked first, then only the appropriate value is computed. This is useful for simple assignments or returns, such as status = "even" if num % 2 == 0 else "odd". It has the lowest operator precedence, ensuring proper grouping in complex expressions. For more advanced conditional logic, Python 3.10 introduced the match statement for matching (detailed in the Advanced Language Features section).

Loop Constructs

Python's loop constructs provide mechanisms for repeated execution of blocks based on conditions or over data structures. The primary loops are the , which iterates over iterables, and the , which continues as long as a condition evaluates to true. These constructs support an optional else clause that executes only if the loop completes normally without interruption by a break statement. within loops is managed using break, continue, and pass statements to exit, skip, or noop iterations as needed. The syntax and semantics of these features have remained unchanged since Python 3.0 and through version 3.14. The syntax is for target_list in expression_list: suite [else: suite], where target_list receives values from the iterator produced by expression_list. The expression_list is evaluated once to obtain an iterable, from which an iterator is created via the iter() built-in function; this iterator must implement the protocol by providing __iter__() (returning itself) and __next__() methods that yield successive items until raising StopIteration to signal exhaustion. Each call to __next__() assigns the returned value to target_list using standard assignment semantics, after which the loop body (suite) executes. Upon exhaustion of the iterator, the loop terminates, and if no break occurred, the else suite executes; this allows code to run only after full iteration, such as verifying completeness. For example, iterating over a list:

python

for item in [1, 2, 3]: print(item) else: print("Iteration complete")

for item in [1, 2, 3]: print(item) else: print("Iteration complete")

This prints each number followed by "Iteration complete" since no break interrupts the loop. Functions like enumerate() and zip() can generate iterables for indexed or combined iteration in for loops. The while loop syntax is while expression: suite [else: suite], where the expression is evaluated repeatedly at the start of each iteration. If true (nonzero or nonempty), the suite executes, and the loop continues; if false, the loop ends, and the else suite runs unless a break intervened. Unlike for, there is no inherent iterable involvement; the condition drives repetition indefinitely until falsified. For example:

python

count = 0 while count < 3: print(count) count += 1 else: print("Loop ended normally")

count = 0 while count < 3: print(count) count += 1 else: print("Loop ended normally")

This outputs 0, 1, 2, and "Loop ended normally". The break statement, used within a loop's suite, immediately exits the innermost enclosing for or while loop, skipping any remaining iterations and the else clause. It must appear in a loop context and, if nested in a try block, triggers the finally clause before exit. For instance, in a for loop, it prevents full exhaustion of the iterable. The continue statement skips the rest of the current iteration and advances to the next one in the enclosing for or while loop, re-evaluating the condition or fetching the next item. Like break, it respects finally clauses in try blocks but does not affect the else clause, as it allows normal completion. The pass statement performs no operation and serves as a syntactic placeholder within loop bodies where a statement is required but none is desired, such as in empty iterations or during development. It can be used in for or while suites without altering control flow. For example, with control statements:

python

for num in range(5): if num == 2: continue # Skip 2 if num == 4: break # Exit at 4 print(num) else: print("No break occurred")

for num in range(5): if num == 2: continue # Skip 2 if num == 4: break # Exit at 4 print(num) else: print("No break occurred")

This prints 0, 1, 3 but skips the else due to break.

Exception Handling

Exception handling in Python provides a mechanism to detect and respond to runtime errors, allowing programs to recover from exceptional conditions without terminating abruptly. The language uses a try-except-else-finally construct to encapsulate potentially erroneous code and define handlers for specific error types. This approach promotes robust error management by separating normal execution paths from error recovery logic. The core syntax of exception handling revolves around the try statement, which consists of a try clause followed by zero or more except clauses, an optional else clause, and an optional finally clause. The try clause contains the code that may raise an exception. If an exception occurs, execution transfers to the first matching except clause, where the exception type is specified (e.g., except ValueError:); an optional as identifier captures the exception instance for inspection. Multiple except clauses can handle different exception types, evaluated in order, with the first match executing and subsequent clauses skipped. For handling multiple related types in a single clause, a tuple of exception classes can be used, such as except TypeError, ValueError: (parentheses optional since Python 3.14 per PEP 758 when not using as), or except (TypeError, ValueError) as e: (parentheses required with as). The else clause executes only if no exception is raised in the try block, useful for code that should run post-successful execution. The finally clause always executes, regardless of exceptions, ideal for cleanup tasks like resource deallocation. Since Python 3.14, the interpreter issues a SyntaxWarning for control flow statements like return, break, or continue within finally blocks, as they can mask exceptions or alter expected flow (PEP 765). Introduced in Python 3.11 via PEP 654, the except* clause extends handling to exception groups, which are collections of multiple concurrent exceptions represented by the BaseExceptionGroup class. This syntax, such as except* OSError as eg:, recursively matches and handles subgroups of exceptions by type, suppressing matched ones while propagating unmatched subgroups. It addresses scenarios like parallel task failures where multiple unrelated errors occur simultaneously. Exceptions are raised explicitly using the raise statement, which can instantiate a new exception like raise ValueError("Invalid input") or re-raise the current exception with a bare raise. When raised, an exception propagates up the call stack until caught by a matching except or except* clause; if unhandled, it terminates the program with a traceback. Python's exception hierarchy is rooted at BaseException, with Exception as the base for non-terminating errors; common built-in exceptions include ValueError for invalid argument values and TypeError for type mismatches, both subclasses of Exception. Custom exceptions can be defined by subclassing Exception or other appropriate bases.

Functions and Functional Features

Function Definitions and Calls

In Python, functions are defined using the def statement, which creates a callable object and binds it to a name in the current namespace. The basic syntax is def function_name(parameter_list):, followed by an indented suite of statements that form the function body; this body is executed only when the function is called. The return statement within the body optionally specifies an expression whose value is returned to the caller; if omitted, the function implicitly returns None. Function definitions can include a docstring as the first statement in the body, which is accessible via the function's __doc__ attribute for documentation purposes. The parameter list in a function definition specifies how arguments are passed and bound during calls, supporting various forms for flexibility. Parameters are categorized as positional-or-keyword (default), positional-only (marked before a / slash), or keyword-only (marked after an * star or *identifier). Positional-only parameters, introduced in Python 3.8 via PEP 570, must be supplied by position and cannot be passed by keyword, allowing API designers to enforce order without exposing parameter names. For example:

python

def greet(name, /, greeting="Hello"): # name is positional-only return f"{greeting}, {name}!"

def greet(name, /, greeting="Hello"): # name is positional-only return f"{greeting}, {name}!"

Keyword-only parameters require explicit keyword passing and follow the *. Default values can be assigned to parameters (positional-or-keyword or keyword-only) using = expression, evaluated once at definition time from left to right; subsequent parameters without defaults must precede those with them. Mutable default objects, such as lists, are shared across calls due to this evaluation timing, potentially leading to unintended modifications—programmers are advised to use None as a sentinel and initialize inside the function if mutability is needed. Arbitrary positional arguments are captured as a tuple via *args (or any name after *), placed after required parameters and before **; similarly, arbitrary keyword arguments are captured as a dictionary via **kwargs. Function calls invoke the defined function by evaluating arguments and binding them to parameters in a new local namespace. The syntax is function_name(argument_list), where arguments can be positional (matched by order), keyword (matched by name, e.g., func(arg= value)), or a mix, provided keywords follow positionals and no duplicates occur. Unpacking extends this: *iterable supplies positional arguments from an iterable, and **mapping supplies keyword arguments from a mapping, enabling dynamic invocation. For instance:

python

def sum_all(*numbers, scale=1): return sum(numbers) * scale values = [1, 2, 3] result = sum_all(*values, scale=2) # Unpacks to positional args, keyword for scale

def sum_all(*numbers, scale=1): return sum(numbers) * scale values = [1, 2, 3] result = sum_all(*values, scale=2) # Unpacks to positional args, keyword for scale

Semantically, upon definition, a function object is created with attributes like __code__ (containing the bytecode) and __globals__ (referencing the global namespace at definition time); the body accesses globals at call time via this reference. Local variables, including parameters, form a new namespace per call, following Python's scoping rules. Recursion is supported but limited by the interpreter's stack depth, defaulting to 1000 frames in CPython; this can be adjusted via sys.setrecursionlimit() but increasing it risks stack overflow.

Lambdas, Closures, and First-Class Functions

In Python, functions are first-class objects, meaning they can be assigned to variables, passed as arguments to other functions, and returned as values from functions, enabling higher-order programming paradigms. This treatment aligns functions with other data types, allowing dynamic composition and manipulation without special syntax. For instance, a function can be assigned to a variable for later invocation: def add(x, y): return x + y; f = add; result = f(3, 4). Similarly, functions can be passed as arguments, as in def apply_operation(func, a, b): return func(a, b); apply_operation(add, 3, 4), or returned from another function: def create_adder(n): def adder(x): return x + n; return adder; increment_by_two = create_adder(2). This first-class status has been a core feature of Python since its inception and remains unchanged in versions up to Python 3.13. Lambda expressions provide a concise way to define anonymous, single-expression functions inline, complementing first-class functions by avoiding the need for named def statements in simple cases. The syntax is lambda parameters: expression, where the expression is evaluated and returned upon invocation, with no support for statements or multiple lines. For example, square = lambda x: x ** 2 creates a function that computes the square of its argument, equivalent to a full def but more succinct for one-off uses. Lambdas can also capture variables from the enclosing scope, behaving like nested functions. They are commonly used with built-ins like map and filter for functional-style operations, such as list(map(lambda x: x * 2, [1, 2, 3])). Introduced in Python 1.0 and refined in subsequent versions, lambda semantics emphasize brevity without altering the underlying function object model. Closures arise when a nested function references variables from its enclosing (outer) scope, retaining access to those variables even after the outer function completes execution, thus "closing over" the environment. These referenced variables, known as free variables, are not local to the inner function nor global but resolved from the nearest enclosing namespace. To modify such variables, the nonlocal statement must be used in the inner function; otherwise, access is read-only. For example:

python

def outer(z): y = 10 def inner(x): nonlocal y return x + y + z return inner f = outer(5) result = f(3) # Returns 18, with y=10 and z=5 captured

def outer(z): y = 10 def inner(x): nonlocal y return x + y + z return inner f = outer(5) result = f(3) # Returns 18, with y=10 and z=5 captured

Here, inner forms a closure over y and z. Semantically, the closure is implemented via the function object's __closure__ attribute, a tuple of cell objects holding bindings for free variables, corresponding to the co_freevars attribute in the function's code object. Each cell contains a reference to the variable's value at closure time, ensuring lexical scoping and preventing garbage collection of the enclosing environment until the closure is discarded. This mechanism, formalized in Python 2.2 with nested scopes and stable through Python 3.13, supports functional patterns like partial application without explicit state management.

Comprehensions and Generator Expressions

Comprehensions in Python provide a concise syntax for creating iterables such as lists, dictionaries, and sets by applying expressions to items from an existing iterable, often with optional filtering conditions. This feature enhances code readability and efficiency compared to equivalent explicit loops. List comprehensions, the original form, generate lists eagerly, while dictionary and set comprehensions extend this to mappings and unordered collections, respectively. Generator expressions, a variant, produce lazy iterables that yield values on demand, conserving memory for large datasets. List comprehensions follow the syntax [expression for item in iterable if condition], where the if clause acts as a filter, including only items that evaluate the condition to true. The iteration proceeds in the order of the underlying iterable, preserving sequence if applicable. Nested comprehensions allow multiple loops, equivalent to nested for statements, by chaining for clauses. For instance, to flatten a list of lists: [[1,2], [3,4]] becomes [item for sublist in nested_list for item in sublist]. List comprehensions were introduced in Python 2.0 to streamline list creation from iterations. Dictionary comprehensions use the form {key: value for item in iterable if condition}, producing a mapping where keys and values are derived from expressions evaluated per item. Set comprehensions employ {expression for item in iterable if condition}, yielding an unordered collection of unique elements. Both iterate over the iterable in its natural order and apply the filter similarly to list comprehensions. These were added in Python 2.7 and 3.0 to parallelize the syntax for non-list collections. Generator expressions, enclosed in parentheses as (expression for item in iterable if condition), create iterator objects that evaluate the expression lazily upon consumption, such as in a for loop or list() call. This defers computation until needed, making them suitable for infinite or memory-intensive sequences. Nesting and filtering work analogously to other comprehensions, with iteration order matching the source iterable. Introduced via PEP 289 in Python 2.4, they generalize list comprehensions for on-the-fly generation. Semantically, all comprehensions bind loop variables in a local scope, preventing leakage to the enclosing namespace, and evaluate if conditions after each iteration but before the output expression. Since Python 3.8, assignment expressions (walrus operator :=) can appear in the output or condition, allowing inline computation like [y for x in data if (y := f(x)) > 0], which assigns y within the filter. This feature, defined in PEP 572, supports more expressive filtering without auxiliary variables. No significant syntactic changes to comprehensions or generator expressions have occurred up to Python 3.14.

Classes and Object-Oriented Features

Class Definitions and Instances

In Python, classes provide a means to define custom object types that encapsulate data and behavior, supporting principles such as encapsulation and . A class is defined using the class keyword followed by the class name and an optional list of base classes in parentheses, with the class body consisting of indented statements that define attributes and methods. For example, a basic class definition appears as class MyClass: pass, creating a new type without , while specifying a base class uses class Derived(Base): pass to indicate that Derived inherits from Base. Multiple is supported by listing multiple base classes, such as class MultiDerived(Base1, Base2): pass, allowing a class to inherit from more than one parent, which introduces complexities in method resolution that are handled by specific algorithms. Instances of a class are created by calling the class as if it were a function, resulting in a new object of that type; for instance, obj = MyClass() produces an instance obj of MyClass. During instantiation, Python automatically invokes the __init__ special method if defined in the class, passing the new instance as the first argument ([self](/page/Self)) along with any provided arguments, enabling initialization of instance-specific attributes. This constructor method allows customization of the object's initial state, such as setting default values for attributes. Without an explicit __init__, instantiation proceeds without additional setup, yielding a blank instance. Semantically, each instance maintains its own namespace for attributes, stored in the instance's __dict__ attribute, which is a mutable dictionary mapping attribute names to values. Class-level attributes, defined directly in the class body, are shared among all instances unless overridden at the instance level. Inheritance affects attribute lookup: when accessing an attribute on an instance, Python searches the instance's __dict__, then the class's __dict__, and proceeds up the inheritance hierarchy. For method resolution in inheritance chains, particularly with multiple bases, Python employs the Method Resolution Order (MRO), a linearization of the class hierarchy computed using the C3 algorithm to ensure a consistent, unambiguous search path. The C3 linearization merges the MROs of base classes while preserving the order specified in the class definition and avoiding duplicates, raising a TypeError if the hierarchy cannot be linearized monotonically. The MRO for a class can be inspected via its mro() method or __mro__ attribute. Classes in Python can contain inner classes, defined within the body of an outer class, which reside in the outer class's namespace and are typically used for helper or related types without affecting the outer class's instances directly. Metaclasses provide a mechanism for customizing class creation, as classes themselves are instances of metaclasses (defaulting to the built-in type); a custom metaclass is specified via the metaclass keyword argument in the class definition, such as class MyClass(metaclass=MyMeta): pass, allowing interception and modification of the class object's construction. Special methods, like __init__, can be defined within classes to hook into Python's object protocol for operations such as initialization. As of Python 3.14, there have been no syntax changes to class definitions or instantiation.

Methods, Attributes, and Special Methods

In Python, methods are functions defined within a class that enable object-oriented behavior by operating on instances or the class itself. Instance methods, the most common type, take the instance as their first argument, conventionally named self, which is automatically bound when the method is called on an instance. This binding allows access to instance-specific data and behavior, as in the syntax def method(self, arg): pass. For example, in a class Point, an instance method might compute the distance from origin using self.x and self.y. Class methods and static methods provide alternatives for operations not tied to a specific instance. Class methods are decorated with @classmethod and receive the class itself as the first argument, typically named cls, enabling factory methods or modifications to class state; they can be called on the class or instances, with cls referring to the calling class in hierarchies. Static methods, marked with @staticmethod, receive no implicit first argument and behave like regular functions within the class namespace, useful for functions related to the class without accessing instance or class data. Both decorators transform the function into a descriptor that handles the appropriate binding during attribute access. Attributes in Python classes are variables that store data, divided into instance attributes (unique to each object, set via self.attr = value in methods like __init__) and class attributes (shared across all instances, defined directly in the class body). Instance attributes are stored in the instance's __dict__ , allowing dynamic addition, while class attributes are looked up in the class namespace first during access. This design supports flexible but can lead to higher memory usage for classes with many instances due to per-instance dictionaries. To optimize memory and restrict attribute creation, Python provides the __slots__ class attribute, which declares a fixed set of allowed instance attributes as a sequence of strings or tuples. When __slots__ = ('x', 'y') is set, instances allocate fixed space for these attributes instead of a __dict__, reducing by up to 50% for simple classes with many instances, though it prevents dynamic attribute addition and requires careful design. This feature, introduced in Python 2.2, remains unchanged in Python 3.14. The semantics of method and attribute access rely on Python's descriptor protocol, where certain class attributes (like functions for methods) implement __get__ to customize retrieval. For instance methods, this protocol binds self to the instance, returning a callable that passes the instance as the first argument; class and static methods override this for class or no binding, respectively. This protocol ensures transparent integration of methods as bound objects without explicit user code. Special methods, also known as dunder methods (prefixed and suffixed with double underscores), allow classes to define custom behavior for built-in operations and language constructs, enabling operator overloading and integration with Python's core types. Common examples include __init__(self, *args) for instance initialization during creation, __str__(self) for a readable string representation (used by print() and str()), __len__(self) for the length of an object (returning an integer for len()), and __add__(self, other) for the + operator, which can return a new object or raise NotImplemented if unsupported. These methods are invoked implicitly by the interpreter, such as obj1 + obj2 calling obj1.__add__(obj2), promoting polymorphic behavior across types. No new special methods were introduced in Python 3.14.

Properties, Descriptors, and Decorators

Properties in Python provide a way to define methods that act like attributes, allowing for controlled access to instance data through getters, setters, and deleters while maintaining a simple attribute-like interface. This is achieved using the @property decorator for the getter, @attribute.setter for the setter, and @attribute.deleter for the deleter, which wrap methods to compute or validate values dynamically. For example, in a class representing a , the can be managed as a to ensure it remains non-negative:

python

class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError("Radius cannot be negative") self._radius = value @radius.deleter def radius(self): del self._radius

class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value < 0: raise ValueError("Radius cannot be negative") self._radius = value @radius.deleter def radius(self): del self._radius

This syntax enables computed attributes, such as deriving the area from the radius without direct access to the underlying storage, enhancing encapsulation and validation semantics. Properties are particularly useful for attributes whose values are derived or need side effects on access, without altering the class's public interface. Underlying properties is the descriptor protocol, which defines how classes can customize attribute access, assignment, and deletion when used as class attributes. A descriptor is any object implementing one or more of the special methods __get__(self, obj, type=None), __set__(self, obj, value), and __delete__(self, obj), where obj is the instance (or None for class access) and type is the class. These methods are invoked automatically during attribute lookup: __get__ computes or retrieves the value, __set__ stores or validates it, and __delete__ handles removal. Descriptors can also implement __set_name__(self, owner, name) to receive notification of their assigned name in the owner class, aiding in initialization. The property built-in is itself a descriptor factory, returning a descriptor object that delegates to the provided getter, setter, and deleter functions. Descriptor semantics distinguish between data descriptors (those with __set__ or __delete__, which take precedence over instance dictionary entries) and non-data descriptors (only __get__, which yield to instance attributes with the same name). This protocol powers not only properties but also class and static methods, enabling advanced customization of object behavior. For instance, a custom descriptor for a directory size might compute the total file count on each access:

python

class DirectorySize: def __get__(self, obj, objtype=None): return sum(len(os.listdir(d)) for d in obj.dirs)

class DirectorySize: def __get__(self, obj, objtype=None): return sum(len(os.listdir(d)) for d in obj.dirs)

Such descriptors facilitate reusable attribute logic across classes, promoting computed attributes that avoid redundant storage. Decorators extend this customization to functions and methods via the @decorator syntax, placed directly before a def statement, which applies the decorator to the function at definition time. A decorator is a callable that accepts the function as an argument and returns a new callable, effectively wrapping the original to add or modify behavior, such as logging or caching, without altering the function's code. For example:

python

def log(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper @log def add(a, b): return a + b

def log(func): def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper @log def add(a, b): return a + b

This replaces add with log(add), preserving the original invocation interface. Decorators can accept arguments, like @decorator(arg), and stack vertically, applying from bottom to top (innermost first), as in @outer @inner def func(): pass, equivalent to func = outer(inner(func)). To maintain the wrapped function's metadata, such as its name and docstring, decorators commonly use @functools.wraps from the standard library:

python

from functools import wraps def log(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper

from functools import wraps def log(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__}") return func(*args, **kwargs) return wrapper

This ensures introspection tools like help() reflect the original function's details. The decorator syntax, introduced in Python 2.4, streamlines aspect-oriented programming by composing function wrappers declaratively. As of Python 3.14, there have been no changes to the core syntax or semantics of properties, descriptors, or decorators.

Advanced Language Features

Context Managers and With Statement

The with statement in Python provides a convenient way to manage resources by ensuring that setup and teardown actions are performed automatically, even if exceptions occur during the execution of the enclosed code block. Introduced in Python 2.5 via PEP 343, it simplifies common patterns previously handled with try/finally blocks, such as acquiring and releasing locks or opening and closing files. The syntax is with EXPR as VAR: followed by a suite, where EXPR evaluates to a context manager object, and VAR optionally binds to the value returned by the manager's entry method. A context manager is an object that implements the context management protocol, consisting of the __enter__() and __exit__() special methods. Upon entering the with block, Python calls __enter__() on the context manager; this method performs setup and returns a value (often the resource itself) that is assigned to VAR if the as clause is present. The suite then executes, and regardless of completion—normal or exceptional—__exit__(exc_type, exc_value, traceback) is invoked for cleanup, receiving None for all arguments if no exception occurred. If an exception is raised in the suite, it propagates unless __exit__() returns a truthy value, which suppresses the exception. For multiple context managers, the syntax with A() as a, B() as b: is equivalent to nested with statements, processing them from left to right; this feature was added in Python 3.1. Parentheses can group multi-line expressions since Python 3.10. Context managers are commonly used for file handling, where open() returns a manager that acquires a file handle in __enter__() and closes it in __exit__(). The contextlib module offers utilities to create context managers without defining full classes, notably the @contextmanager decorator, which transforms a generator function into a context manager. The generator's code before the yield statement handles setup and yields the resource (or None); the code after yield (typically in a try/finally block) performs teardown. This approach leverages generators for concise resource management, introduced alongside the with statement in Python 2.5. For example:

python

from contextlib import contextmanager @contextmanager def temporary_setting(value): old_value = get_current_setting() set_current_setting(value) try: yield finally: set_current_setting(old_value) with temporary_setting('new_value'): # Code that uses the temporary setting pass

from contextlib import contextmanager @contextmanager def temporary_setting(value): old_value = get_current_setting() set_current_setting(value) try: yield finally: set_current_setting(old_value) with temporary_setting('new_value'): # Code that uses the temporary setting pass

An asynchronous variant, async with, was introduced in Python 3.5 for use in coroutines, invoking __aenter__() (awaited) on entry and __aexit__() (awaited) on exit, following a similar protocol but supporting async cleanup. The contextlib module provides @asynccontextmanager since Python 3.7 for generator-based async managers.

Type Annotations and Deferred Evaluation

Type annotations in Python provide optional metadata about the expected types of function parameters, return values, and variables, enhancing code readability and enabling static analysis tools. The syntax was introduced in Python 3.0 for function annotations via PEP 3107, allowing declarations such as def greet(name: str) -> str: where name is annotated as a string and the return type as a string. For more complex types, the typing module, added in Python 3.5 through PEP 484, supplies constructs like List and Union; for example, from typing import List, Union enables def process_items(items: List[int]) -> Union[str, None]:. Variable annotations, including class variables, were introduced in Python 3.6 via PEP 526, supporting forms like x: int = 1 within a class definition, such as class Point: x: int = 0; y: int = 0. Semantically, type annotations are stored in the __annotations__ attribute of functions, classes, or modules as a mapping names to their annotation expressions, but they are ignored by the Python runtime and do not affect execution. Prior to Python 3.14, annotations were evaluated immediately at definition time, which could lead to errors with forward references to types not yet defined, such as referencing a class before its declaration. To mitigate this, PEP 563 introduced postponed evaluation in Python 3.7 via the from __future__ import annotations directive, converting annotations to strings to defer evaluation; it was planned to become the default behavior in Python 3.10 but was rolled back before the release to avoid compatibility issues. In Python 3.14, deferred evaluation is fully implemented and enabled by default through PEP 649, which uses descriptors to lazily compute annotations only when accessed, eliminating the need for string conversion and resolving prior limitations without runtime overhead. PEP 749 provides the implementation details, including support for metaclasses and the REPL, while deprecating the from __future__ import annotations import, which now triggers a DeprecationWarning and is slated for removal in a future version. The new annotationlib module, also introduced in Python 3.14, offers utilities like get_annotations() for robust of these deferred annotations across functions, classes, and modules. This evolution supports tools like mypy for static type checking without altering runtime semantics.

Pattern Matching

Pattern matching, also known as structural pattern matching, is a feature introduced in Python 3.10 that allows for concise and expressive handling of complex data structures through the match statement. This construct evaluates a subject expression against a series of patterns defined in case clauses, executing the block of the first matching case. Unlike traditional conditional statements, it supports destructuring and binding of variables directly from the subject's structure, making it particularly useful for tasks like commands or validating data shapes. The syntax of the match statement begins with match subject:, followed by one or more case blocks indented under it. Each case clause is written as case pattern [if guard]:, where the pattern describes the structure to match, and the optional guard is a boolean expression (if condition) that must also evaluate to true for the case to succeed. For example:

python

match point: case (0, 0): print("Origin") case (0, y): print(f"Y-axis at {y}") case (x, 0): print(f"X-axis at {x}") case _: print("Elsewhere")

match point: case (0, 0): print("Origin") case (0, y): print(f"Y-axis at {y}") case (x, 0): print(f"X-axis at {x}") case _: print("Elsewhere")

Here, the subject point (assumed to be a tuple) is matched against literal and capture patterns, with the wildcard _ handling any unmatched cases. Guards can refine matches, such as case (x, y) if x == y:, ensuring additional conditions beyond structure. Patterns in Python support a variety of forms to handle different data types. Literal patterns match exact values like None, booleans, numbers, or strings (e.g., case 42: or case "hello":). Capture patterns bind the entire subject to a variable (e.g., case x: sets x to the subject). The wildcard _ matches any value without binding it. Sequence patterns destructure iterables like lists or tuples, using fixed positions (e.g., case [first, second]:), starred expressions for variable-length rest (e.g., case [head, *tail]:), or combinations thereof. Mapping patterns target dictionaries, matching specific keys (e.g., case {"x": x_val, "y": y_val}:) or using **rest for remaining entries (e.g., case {"action": action, **data}:). Class patterns allow matching instances of a class, either positionally (e.g., case Point(x, y):) or by keyword (e.g., case Point(x=x, y=y):), provided the class defines __match_args__ for positional support. Semantically, the match statement evaluates the subject once before attempting patterns in order from top to bottom, binding variables only in the successful case and making those bindings available in the corresponding block and beyond. There is no fallthrough between cases; execution jumps to the matching block and skips the rest, similar to a but with structural depth. If no pattern matches, the statement completes without action, though a final case _: can serve as a catch-all. Matching is exhaustive only if all possible subjects are covered, but Python does not enforce this at runtime or . Variable captures from nested patterns (e.g., in sequences or classes) create new bindings scoped to the block. This feature can serve as a more readable alternative to nested if-elif chains for multi-way decisions on structured data. Introduced in Python 3.10 via PEP 634, pattern matching received refinements in Python 3.11, including enhanced error messages for parsing and matching failures, leveraging the PEG parser for more precise diagnostics. No further syntactic or semantic changes have been made as of Python 3.14.

Template Strings and Recent Innovations

Template strings, introduced in Python 3.14 via PEP 750, provide a new literal type prefixed with 't', such as t"hello {name}", which evaluates to a Template object rather than a formatted string. This object allows for custom processing of the string and its interpolated values before final combination, enabling safer and more flexible interpolation without immediate evaluation. Unlike f-strings, which perform eager evaluation and can pose security risks if used with untrusted input due to potential code execution, template strings defer processing to user-defined methods on the Template object, mitigating eval-like vulnerabilities. For example, the following creates a template:

python

from string.templatelib import Template # Processing module for template strings tmpl = t"Hello, {name}!" result = tmpl.process(name="World") # Custom method call print(result) # Outputs: Hello, World!

from string.templatelib import Template # Processing module for template strings tmpl = t"Hello, {name}!" result = tmpl.process(name="World") # Custom method call print(result) # Outputs: Hello, World!

This design generalizes f-strings for scenarios like database queries or configuration files where placeholders need validation or escaping. Template strings support the same expression syntax as f-strings but return an object that can be concatenated only with other templates, preventing accidental mixing with plain strings. Recent syntax enhancements include improvements to f-strings in Python 3.12 under PEP 701, which formalized their grammar and removed restrictions on nested quotes. Previously, inner quotes in f-string expressions had to differ from the outer string's quotes; now, the same quote type can be used freely, simplifying code like f'{"it's"} a test'. Python 3.14 also implements of annotations by default (as detailed in the Type Annotations section), deferring computation until access via annotations to reduce startup overhead. The walrus operator (:=), added in Python 3.8, saw enhanced integration in Python 3.11 and later for cleaner expressions in comprehensions and conditions without redundant assignments. Additionally, starting in Python 3.11, error messages provide more precise locations and suggestions, such as pointers to syntax issues, improving efficiency. These changes collectively fill gaps in prior versions, like the absence of native template support, enhancing Python's expressiveness and robustness.

References

Add your contribution
Related Hubs
Contribute something
User Avatar
No comments yet.