Expressions
2026-08-01
Almost every statement contains an expression, which we indicate in syntax as expr, and sometimes refer to as expression in paragraph text. Expressions can be simple (single value), or complex (contain operators). The operands of operators are expressions, which means that an expression, can contain sub-expressions.
Expression Features
An expression is a programming construct that ‘has a value’. To be more exact, an expression results in one, and only one, value. As we have seen, any value has a type, so it is reasonable to say: an expression has a value and a type:
expr ⇒ value + type
If an expression contain operators, those operators will require operands. Some operators require one operand (unary operators), and some require two (binary operators). Not all operators work with all types of values.
When an operator can work with multiple types of operands, we say it is overloaded. For example, the addition operator (+), is overloaded for str types, which will perform concatenation when its operands are str types, instead of addition.
The order in which expressions are evaluated, depends on the precedence of the operators. When two operators have the same level of precedence, Python chooses one based on their associativity with their operands. Most operators associate from left-to-right (L→R), while the exponentiation operator associate from right-to-left (R→L).
Operator Precedence
Operators are listed below from highest to lowest precedence. Note that some operators are not symbols, but keywords. The expr parts below must be an appropriate type for the relevant operators. It can be a simple object.
| operators | description |
|---|---|
| (expr…), [expr…], {key:val}, {expr…} | Force precedence; list, dictionary, set literals |
| expr[index], expr[index:index], expr(arg,…), expr.attr | Subcript, slice, function call, attribute reference |
| await expr | Await asynchronous expression. |
| num**exp | Exponentiation: raise num to exp. |
| +expr, -expr, ~expr | Unary plus, minus, bitwise NOT |
| expr*expr, expr**@**expr, expr/expr, expr//expr, expr%expr | Multiplication, matrix multiplication, division, integer division, remainder (modulus) |
| expr+expr, expr-expr | Addition, subtraction (difference) |
| int<<int, int>>int | Bitwise left shift, right shift |
| int&int | Bitwise AND |
| int^int | Bitwise XOR |
| int|int | Bitwise OR |
| in, not in, is, is not, <, <=, >, >=, !=, == | Membership (in), identity (is), comparisons, all returning bool |
| not expr | Logical (boolean) NOT |
| and | Logical (boolean) AND |
| or | Logical (boolean) OR |
| if-else | Conditional expression |
| lambda | Lambda expression |
| := | Assignment expression |
Note that normal assignment is a statement, where the right hand expression is evaluated first. This must not be confused with :=, a newer assignment expression.
As a matter of good convention, we tend to put spaces around binary operators, except the attribute reference (.) and exponentiation (**) operators. No spaces after unary operators, or before the subscript operator.
Operator Implementation
Most operators are really just ‘disguised’ function calls, with a convenient and compact syntax. Each operator correlates to a special method, and a custom class can provide an implementation for each, to achieve operator overloading. Here is a table:
| Operators | Special Methods |
|---|---|
** |
__pow__ |
+ |
__add__ |
- |
__sub__ |
* |
__mul__ |
/ |
__truediv__ |
// |
__floordiv__ |
% |
__mod__ |
@ |
__matmul__ |
<< |
__lshift__ |
>> |
__rshift__ |
& |
__and__ |
^ |
__xor__ |
\| |
__or__ |
== |
__eq__ |
!= |
__ne__ |
< |
__lt__ |
<= |
__le__ |
> |
__gt__ |
>= |
__ge__ |
+ (unary) |
__pos__ |
- (unary) |
__neg__ |
~ (unary) |
__invert__ |
[] |
__getitem__,
__setitem__ |
Please note that the logical operators (and,
or, not) and membership testing operators
(in, not in) are not associated with special
methods, as they rely on the boolean values or other special methods of
the objects they operate on.
The reason for this design choice, is that it allows classes to overload these operators. Although not implemented as a function, we can even overload the __call__ special method to make objects callable.
Operator Module
The standard operator module provide a convenient way to ‘call’ intrinsic (built-in) operators as functions. This is mostly useful if you want to a pass an operator to a function that requires a callable argument.
If this module has been imported, the function call ‘operator.add(a, b)’ is equivalent the operator notation: ‘a + b’.
py — Operator module functions
import operator, functools
result1 = operator.add(12, 34)
result2 = 12 + 34
print(result1, result2) #→ 46 46
total = functools.reduce(
operator.mul, #← pass `*`
[11, 22, 33, 44])
print(total) #→ 351384Arithmetic
Operators that should be familiar to you, are the arithmetic operators: multiplication (*), division (/), addition (+) and subtraction (-). They work on both int and float type operands, i.e., numeric types.
When mixing int and float operands with arithmetic operators, they will first convert the int operand to float, before evaluating the operator. The result will thus be float.
py — Implicit arithmetic conversion
>> print( type(12.34 * 2) ) #→ floatWhat is unusual, compared to some other languages, is that the division operator always returns a floating point result; even if both the operands are of type int.
py — Floating point division
>> print( 2 / 3 ) #→ 0.6666666666666666
>> print( type (2 / 3) ) #→ float For situations where this is undesirable behaviour, you must use the integer division operator: //. It always returns an integer value, though not necessarily of type int… but even if the result is float, the significant digits will be .0.
py — True division vs floor division
>> print ( 12 / 5 ) #→ 2.4 ← float
>> print ( 12 // 5 ) #→ 2 ← int
>> print ( 12.0 / 5 ) #→ 2.4 ← float
>> print ( 12.0 // 5 ) #→ 2.0 ← floatAlso unusual, is the modulus or remainder operator (%). It surprisingly works with float and int values. In other languages, it tends to work only with integer types in those languages. It works like division, but returns the remainder, instead of the division result.
py — Modules/remainder operator
>> print( 22 // 5 ) #→ 4 ← division.
>> print( 22 % 5 ) #→ 2 ← remainder.
>> print( 1.5 / 5 ) #→ 0.3 ← division.
>> print( 1.5 % 5 ) #→ 1.5 ← remainder
>> print( 1.5 % 1.25 ) #→ 0.25 ← remainderPython also has the exponentiation or ‘to the power’ operator: **. It can raise a numeric operand to a power. The power can be a float, and even negative (inverse).
py — Exponentiation operator (to the power)
>> print( 2.5 ** 2 ) #→ 6.25 ← 2.5 × 2.5
>> print( 2.5 ** 3 ) #→ 15.625 ← 2.5 × 2.5 × 2.5
>> print( 2.5 ** -2 ) #→ 0.15 ← 1 ÷ 2.5 ÷ 2.5
>> print( 2.5 ** -3 ) #→ 0.064 ← 1 ÷ 2.5 ÷ 2.5 ÷ 2.5
>> print( 2.5 ** 0.5) #→ 1.581… ← math.sqrt(2.5)Spaces around the exponentiation operator is optional.
Comparisons
We can compare two expressions for equality using: == (equality operator). We can establish inequality with: != (inequality operator). These operators always return a bool (boolean) result: True or **False*.
py — Equality and inequality operators
>> print( "ABC" == "abc" ) #→ False
>> print( "ABC" != "abc" ) #→ True
>> print( "ABC" == "ABC" ) #→ True
>> print( 1.0 == 1 ) #→ True
>> print( 123 != 124 ) #→ TrueWhen ordering becomes important, we can use the less-than (<), less-than or equal (<=), greater-than (>) and greater-than or equal (>=) operators. Like the equality operators, they only return a boolean result.
py — Ordering comparison operators
>> print( "ABC" > "abc" ) #→ False
>> print( "ABC" < "abc" ) #→ True
>> print( "ABC" >= "ABC" ) #→ True
>> print( 123 < 123 ) #→ False
>> print( 123 <= 123 ) #→ TrueThe Unicode values for upper case letters are smaller than those of the lower case letters.
Membership
Membership operators are used to test whether a value is a member of a sequence, such as a string, list, tuple, or set. The two membership operators are in and not in, and both return boolean values True or False.
A common use case, is to check if a substring is present in a larger string, without resorting to regular expressions.
py — Test for substring
text = "Python is FUN!"
print("P" in text) #→ True
if "FUN" in text: print("YES") #→ YES
print("Z" in text) #→ False
if "Python" in text:
print("Indeed") #→ IndeedSince a string is just a special sequence, the in operator will work with other sequences. It can test sequences within sequences as well, but the types must match.
py — Membership test in sequences
primes = { 2, 3, 5, 7 } #← set
print(3 in primes) #→ True
print(4 in primes) #→ False
fruits = [ "apple", "banana", "cherry", "date" ]
print("banana" in fruits) #→ True
print("eggfruit" in fruits) #→ False
pairs = ( ["apple", "red"],
["banana", "yellow"],
["cherry", "red"],
["date", "brown"], )
if ["cherry", "red"] in pairs:
print("Cherries are red") #→ Cherries are red
if ("cherry", "red") in pairs:
print("Cherries are red") #· Not matched.The last examples shows that the tuple
("cherry", "red") will not match the list
["cherry", "red"].
The inverse of in is not in (you cannot use ‘in not’).
Identity
Identity operators are used to compare the memory addresses (i.e., references) of two objects to determine if they refer to the same object. The two identity operators are is and is not. They employ the built-in id function, which returns a unique identification for an object. In the CPython implementation, this will be the address of the object.
Two objects may compare as equal values, but may be different objects in memory. Conversely, if objects have the same identity, they are obviously also equal.
Python will sometimes re-use existing literal objects, though this is not guaranteed, although common with the CPython implementation:
py — Equal identities for simple literals
a = "ABC" ; b = "ABC"
print(a is b) #→ True
a = 123 ; b = 123
print(a is b) #→ TrueIn the example, the id()s
of the objects that a and b reference may be
the same in both cases, because CPython may have decided to reuse the
same string object for both variables. But you should not rely on this
behaviour.
The inverse of the is, is ‘is not’ and not ‘not is’.
py — Identity on non-trivial objects
a = ['A', 'B', 'C']
b = ['A', 'B', 'C']
print(a is b) #→ False
print(a is not b) #→ True
print(a == b) #→ TrueThe objects referenced by a and b are equal
in value, but are not the same objects — they exist at
different addresses in memory. Different names can reference
the same object. This can be a problem only if you think they
are different:
py — Names referencing same object
a = ["A", "B", "C"]
b = a #← `b` is same object.
b[1] = 'X' #← change second item.
print(a) #→ ['A', 'X', 'C']The same effect occurs when you pass an argument to a function… the reference is copied to the parameter. If the reference refers to a mutable object, the function can change the contents of the object (but not the reference).
py — Passing copies of references
def fn (param): param[1] = 'X'
arg = ["A", "B", "C"]
print(arg) #→ ['A', 'B', 'C']
fn(arg) #← or: `fn(param=arg)`
print(arg) #→ ['A', 'X', 'C']If arg and param were visible in the same scope, ‘arg is param’ would return True.
Logic
The prefix unary logical operator not; and the binary logical operators and and or; also return only boolean results. They expect operands of type bool, but will accept other types, which they will automatically convert to bool.
>> print( False or False ) #→ False
>> print( False or True ) #→ True
>> print( True or False ) #→ True
>> print( True or True ) #→ True
>> print( False and False ) #→ False
>> print( False and True ) #→ False
>> print( True and False ) #→ False
>> print( True and True ) #→ True
>> print( not True ) #→ False
>> print( not False ) #→ TruePython does not have an xor operator, but its logical result can be obtained using the other operators with the following formula (expression):
( lhs and not rhs ) or ( not lhs and rhs )
Here lhs and rhs means left hand side expression, and right hand side expression, respectively.
Boolean Algebra
In boolean algebra, and (conjunction) is written as ∧, or (disjunction) as ∨, and not (negation) as ¬. Here are a few common boolean algebra laws:
- De Morgan's law:
- ¬ ( A ∧ B ) ≡ ( ¬A ) ∨ ( ¬B )
- ¬ ( A ∨ B ) ≡ ( ¬A ) ∧ ( ¬B )
- Distributive law:
- A ∧ ( B ∨ C ) ≡ ( A ∧ B ) ∨ ( A ∧ C )
- A ∨ ( B ∧ C ) ≡ ( A ∨ B ) ∧ ( A ∨ C )
- Identity law:
- A ∧ True ≡ A
- A ∨ False ≡ A
- Negation law:
- A ∧ ( ¬A ) ≡ False
- A ∨ ( ¬A ) ≡ True
These laws may help you simplify some complicated logical operations.
Shorthand
Python has a special shorthand syntax for a common scenario: checking if a value is within a certain range. For example, imagine you want to check if some value x, is within the range [A…B] inclusive, here is one formula:
x >= A and x <= B
Which we can rewrite as following, with the same result and meaning:
A <= x and x <= B
For a non-inclusive range: (A…B), the formula would become:
A < x and x < B
Python allows us to shorten the last two examples as:
A <= x
<= B
A
< x <
B
py — Shorthand range check and equivalent
>> x = 5 ; A = 1 ; B = 10
>> print (x >= A and x <= B) #→ True
>> print (A <= x and x <= B) #→ True ← better.
>> print (A <= x <= B) #→ True ← shorthand.Function Call
Arguably the most common and versatile operation, is the function call. However, calls are not performed with a function call operator as in some other languages. Instead, it is a syntactic construct that works like an operator:
callable ( [arg…] )
result = callable ( [arg…] )
- it invokes a callable expression as left hand operand;
- initialises parameters from arguments between the parentheses; and
- finally produces a return result, which may optionally be assigned a name.
This is very similar to the operation of operators; we just use different terminology (arguments instead of operands, for example). In fact, we can even map operators to functions.
The ‘callable
(…)’ syntax
is really shorthand for Python mechanics calling a special __call__
method.
Callables
Any expression that is callable can be invoked with parentheses following the expression. We can test if an expression can be invoked, with the callable(expr) built-in function.
Technically, Python looks for a special __call__ method in the type of the expression, and simply invokes that, passing it the arguments, and collecting its return value.
py — Built-in function call detail
import builtins
print(builtins.str.__call__(123)) #→ 123
print(str(123)) #→ 123For calling methods in custom classes the same syntax is used, but is again a convenient shorthand for a more complex operation, where Python must pass the object on which the method is called, as the first argument.
obj.method(args) ≡ class.method(obj, args)
That leads to a number of ways a method can be called, though in practice one should simply used the most succinct version:
py — Calling object method equivalences
class Demo:
def method(self, param): print(f"method({param})")
obj = Demo()
obj.method("arg") #→ method(arg)
Demo.method(obj, "arg") #→ method(arg)
type(obj).method(obj, "arg") #→ method(arg)
obj.__class__.method(obj, "arg") #→ method(arg)An object can be made callable, if its class implements the __call__ method.
py — Callable objects
class CallMe:
def __call__(self, param): print(f"CallMe {param}")
obj1 = CallMe() ; obj2 = CallMe()
obj1("ABC") #→ CallMe ABC
obj2(12345) #→ CallMe 12345
CallMe.__call__(obj1, "ABC") #→ CallMe ABC
CallMe.__call__(obj2, 12345) #→ CallMe 12345This makes Python very flexible, yet the shorthand syntax forms it provides are concise and obviously preferred. We show the alternatives only as a way to understand the Python function call mechanism.
Conditional Expression
This looks a lot like the if statement, but is an expression, which means it returns a result. Statements do not return results in Python. The if conditional expression also use the if and else keywords, and will result in one of two values: true-expr or false-expr, if the condition is True or False, respectively.
true-expr if condition else false-expr
It is similar in operation to the [conditional operator] as found in C-like languages.
py — Conditional expression
>> height = 5
>> cat = "small" if height < 10 else "large"
>> print(cat)
#→ smallIt is a more compact alternative to the same logic using an if statement:
py — Alternative without conditional expression
>> height = 5
>> if height < 10:
·· cat = "small"
·· else:
·· cat = "large"
>> print(cat) #→ smallIt is similar in operation to the C/C++ conditional operator ?:, though arguably more readable.