Expressions and Operators
An expression computes a value. Operators combine values; indexing, calls, and comprehensions produce new ones. This page covers their syntax and precedence.
Arithmetic
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | Addition; concatenation for strings, bytes, lists, and tuples | 1 + 2 | 3 |
- | Subtraction | 5 - 7 | -2 |
* | Multiplication; repetition for strings, lists, and tuples | "ab" * 2 | "abab" |
/ | True division, always a float | 7 / 2 | 3.5 |
// | Floor division | -7 // 2 | -4 |
% | Remainder; string formatting when the left side is a string | -7 % 3 | 2 |
-x, +x | Negation and identity | -(2 + 3) | -5 |
Floor division rounds toward negative infinity, and the remainder takes the
sign of the right operand, so a == (a // b) * b + a % b always holds:
print(7 // 2, -7 // 2, 7 // -2) # 3 -4 -4
print(7 % 3, -7 % 3, 7 % -3) # 1 2 -2
print(7.5 // 2, 7.5 % 2) # 3.0 1.5
print(7 / 2, 6 / 3) # 3.5 2.0
Mixing an integer and a float produces a float. Division or remainder by zero
is an error. Both operands must have compatible types: "a" + 1 and
[1] + (2,) are errors, so convert first with str() or list().
The Atmos Automation Language has no exponent operator. For a nonnegative integer exponent, multiply in a loop:
def power(base, exponent):
result = 1
for _ in range(exponent):
result = result * base
return result
print(power(2, 10)) # 1024
print(sum([1, 2, 3])) # 6
Bitwise operators
Integers support & (and), | (or), ^ (xor), ~ (complement), <<, and >>.
A negative shift count is an error, and a left shift count must be below 512.
print(5 & 3, 5 | 3, 5 ^ 3) # 1 7 6
print(~5) # -6
print(1 << 10, 1024 >> 3) # 1024 128
Applied to dictionaries and sets, | also merges and unions; see
Data Types.
Comparison
The operators ==, !=, <, >, <=, and >= return True or False. Equality compares
by value across lists, tuples, dictionaries, and sets, and integers equal
floats with the same value (1 == 1.0). Ordering is defined for numbers,
strings, bytes, and sequences of comparable elements, and comparing values of
unrelated types, such as 1 < "a", is an error.
Comparisons do not chain. Python's 0 < x < 10 is a syntax error here
(< does not associate with <); combine two comparisons with and, or add
parentheses:
x = 5
print(0 < x and x < 10) # True
print((1 < 2) == True) # True
Membership
The operators in and not in test whether a value occurs in a collection:
| Collection | x in collection checks |
|---|---|
| string | x is a substring (the left side must be a string) |
| list, tuple, range, set | x equals an element |
| dictionary | x is a key |
| bytes | x is a subsequence of bytes, or an integer byte value |
print("an" in "banana") # True
print(3 in [1, 2, 3]) # True
print("region" in {"region": 1}) # True
print(1 in {"a": 1}) # False
print("x" not in ["a", "b"]) # True
Boolean operators
The and, or, and not operators accept any value, using the
truthiness rules. The binary
operators short-circuit and return one of their operands rather than
a plain boolean:
- The expression
a or breturnsaifais true, otherwiseb. - The expression
a and breturnsaifais false, otherwiseb. - The expression
not aalways returnsTrueorFalse.
print(0 or "default") # default
print("set" or "default") # set
print([] and "never") # []
print(1 and 2 and 3) # 3
print(not []) # True
Use or to supply a fallback: name = ctx.flags.get("name") or "world". The
right operand is evaluated only when needed, so d and d["key"] is safe when
d may be empty.
Conditional expressions
A conditional expression, written value_if_true if condition else value_if_false, selects between two values.
Only the chosen branch is evaluated. Because top-level names can be assigned
once, a conditional expression is the usual way to choose a value for a
top-level assignment.
replicas = 5
size = "large" if replicas > 3 else "small"
print(size) # large
grade = "high" if replicas > 10 else "mid" if replicas > 3 else "low"
print(grade) # mid
String formatting
Percent operator
The percent operator, written format_string % values, fills conversions that begin with %. A single value
supplies one conversion, and a tuple supplies several. A dictionary supplies
named conversions such as %(name)s.
| Conversion | Result |
|---|---|
%s | str() of the value |
%r | repr() of the value |
%d, %i | Integer; a float is truncated, and a string or boolean is an error |
%o, %x, %X | Integer in octal or lowercase or uppercase hexadecimal |
%e, %E | Float in exponent notation, such as 1.234500e+03 |
%f | Float with six decimal places |
%g, %G | Float in the shortest form |
%c | A character from an integer code point, or a one-character string |
%% | A literal percent sign |
print("%s has %d replicas" % ("api", 3)) # api has 3 replicas
print("%r" % "quoted") # "quoted"
print("%x and %o" % (255, 8)) # ff and 10
print("%f" % 1.5) # 1.500000
print("%(svc)s in %(env)s" % {"svc": "api", "env": "prod"}) # api in prod
print("%d%%" % 50) # 50%
print("%s" % ([1, 2],)) # [1, 2]
Flags, field widths, and precision (%5d, %-8s, %.2f, %05d) are not
supported and produce an error such as unknown conversion %5. To pad a string,
build the padding explicitly:
label = "api"
print(label + " " * (8 - len(label)) + "|") # api |
A tuple is always read as the list of values, so wrap a tuple you want to print in a one-element tuple, as in the last example above. The number of values must match the number of conversions.
The format method
The method call "template".format(...) replaces {} placeholders. Placeholders can be
automatic ({}), numbered ({0}), or named ({name}), and !r or !s
selects repr() or str(). Write {{ and }} for literal braces.
print("{} is {}".format("api", "up")) # api is up
print("{0}-{1}-{0}".format("a", "b")) # a-b-a
print("{svc}:{port}".format(svc="api", port=8080)) # api:8080
print("{!r}".format("x")) # "x"
print("{{literal}} {}".format(1)) # {literal} 1
Format specifications such as {:>8} or {:.2f}, attribute access such as
{x.name}, and indexing such as {items[0]} are not supported. Do the work
before formatting, for example "{}".format(x.name). Mixing automatic and
numbered placeholders in one template is an error, and a missing argument is an
error. Extra positional arguments are ignored.
Indexing and slicing
The expression sequence[i] reads one element of a string, bytes, list, tuple, or range. Indexes
start at 0, and a negative index counts from the end. An index outside the
sequence is an error. Likewise, dictionary[key] reads a value and is an error for a
missing key.
items = ["a", "b", "c", "d"]
print(items[0], items[-1]) # a d
print({"k": 1}["k"]) # 1
A slice sequence[start:stop:step] returns a new value of the same type.
Each part is optional. Slice bounds are clamped, so an out-of-range slice
produces a shorter or empty result instead of an error. A step of zero is an
error, and a negative step walks backward.
items = ["a", "b", "c", "d", "e"]
print(items[1:3]) # ["b", "c"]
print(items[:2]) # ["a", "b"]
print(items[-2:]) # ["d", "e"]
print(items[::2]) # ["a", "c", "e"]
print(items[::-1]) # ["e", "d", "c", "b", "a"]
print(items[2:100]) # ["c", "d", "e"]
print("hello"[1:4]) # ell
Assignment through an index, items[i] = value or d[key] = value, works on
lists and dictionaries. Slice assignment is not available; build a new list
with concatenation instead. See Statements.
Attribute access and calls
The expression value.name reads an attribute or a method. A method is looked up on the value's
type, so "abc".upper is a function bound to the string, and calling it with
parentheses runs it. Reading an attribute that does not exist is an error.
Call a function with positional arguments, keyword arguments, or both. A *
before an iterable expands it into positional arguments, and ** before a
dictionary expands it into keyword arguments:
def deploy(name, region, replicas = 1):
return "{} in {} x{}".format(name, region, replicas)
args = ["api", "us-east-1"]
options = {"replicas": 3}
print(deploy("api", "us-east-1")) # api in us-east-1 x1
print(deploy(name = "api", region = "eu-west-1")) # api in eu-west-1 x1
print(deploy(*args, **options)) # api in us-east-1 x3
In a call, write positional arguments first, then keyword arguments, then
* and ** expansions. A positional argument after *args is a syntax error.
Supplying the same parameter twice, an unknown keyword, or too few or too many
arguments is an error. See Statements
for how parameters are declared.
Comprehensions
A comprehension builds a list or dictionary from an iterable in one
expression. It reads [expression for variable in iterable], with optional
if filters and additional for clauses.
numbers = [1, 2, 3, 4, 5, 6]
print([n * n for n in numbers if n % 2 == 0]) # [4, 16, 36]
print({n: n * n for n in numbers if n < 4}) # {1: 1, 2: 4, 3: 9}
print([(x, y) for x in [1, 2] for y in ["a", "b"]])
# [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
The loop variables can unpack pairs, which is the usual way to transform a dictionary:
services = {"api": 3, "worker": 5}
print([name for name, count in services.items() if count > 3]) # ["worker"]
print({name: count * 2 for name, count in services.items()}) # {"api": 6, "worker": 10}
The variables of a comprehension exist only inside it; a name with the same
spelling outside is untouched. Python's set comprehensions and generator
expressions have no equivalent here. Wrap a list comprehension in set(...)
to build a set, and pass a list comprehension to any() or all().
print(set([n % 3 for n in range(10)])) # set([0, 1, 2])
Lambda expressions
A lambda expression, written lambda parameters: expression, creates an anonymous function whose body is a
single expression. Parameters follow the same rules as def, including defaults,
*args, and **kwargs.
double = lambda x: x * 2
pick = lambda item, key = "name": item[key]
print(double(21)) # 42
print(sorted(["bb", "a", "ccc"], key = lambda s: -len(s))) # ["ccc", "bb", "a"]
print(pick({"name": "api"})) # api
Use def for anything that needs more than one expression, statements, or a
name in tracebacks.
Operator precedence
Operators are listed from the loosest binding to the tightest. Operators in the same row group from left to right, except where noted.
| Level | Operators |
|---|---|
| 1 | lambda |
| 2 | x if cond else y |
| 3 | or |
| 4 | and |
| 5 | not |
| 6 | == != < > <= >= in not in (cannot be chained) |
| 7 | | |
| 8 | ^ |
| 9 | & |
| 10 | << >> |
| 11 | + - |
| 12 | * / // % |
| 13 | unary + - ~ |
| 14 | x[i] x[a:b] x.name f(args) |
Two consequences differ from some languages:
- The bitwise operators bind more tightly than comparisons, so
5 & 3 == 1is(5 & 3) == 1. - The
notoperator binds more loosely than comparison, sonot 1 in [1]isnot (1 in [1]).
print(1 + 2 * 3) # 7
print((1 + 2) * 3) # 9
print(-3 % 5) # 2
print(1 << 2 + 1) # 8
print(not 1 == 2) # True
print(1 or 0 and 2) # 1
When in doubt, add parentheses.
Next steps
- Data Types: the values operators act on.
- Statements: assignment, functions, and control flow.
- Built-in Functions:
len,sorted,any,all, and more. - Methods: string, list, and dictionary methods.