A simple swap function in Python is generally considered a function that takes two initialized variables, a = val_a
and b = val_b
, and returns a result of a = val_b
and b = val_a
. The function accepts variables of arbitrary types, or more precisely, variables that are assigned with objects of arbitrary types.
Although the terms โvalueโ and โobjectโ are used interchangeably, the term โvalueโ carries an abstract, non-technical meaning, while the term โobjectโ implies a specific implementation. To track not just an object’s value, but also its identity (consider it a memory address equivalent), the id()
function is also used in the following examples.
Swapping Two Variables in Python – Swapping by Assignment
By relying on a practical Python feature called multiple assignments, the function results are assigned in just one line of code. An example of a simple swap function definition and its use is shown below:
def simpleSwapFun_v1(a, b): tmp = a a = b b = tmp return a, b a = 1 b = 7 print('Before swapping:') print(f'a = {a} @', id(a)) # a = 1 @ 9788608 print(f'b = {b} @', id(b)) # b = 7 @ 9788800 a, b = simpleSwapFun_v1(a, b) print('After swapping:') print(f'a = {a} @', id(a)) # a = 7 @ 9788800 print(f'b = {b} @', id(b)) # b = 1 @ 9788608
Function simpleSwapFun_v1()
is deliberately written in an expanded form to show the idea behind a basic swap algorithm. Building on that, our function can be further simplified to:
def simpleSwapFun_v2(a, b): return b, a print('Before swapping:') print(f'a = {a} @', id(a)) # a = 1 @ 9788608 print(f'b = {b} @', id(b)) # b = 7 @ 9788800 # The results swap their places and get assigned in the defined order a, b = simpleSwapFun_v2(a, b) print('After swapping:') print(f'a = {a} @', id(a)) # a = 7 @ 9788800 print(f'b = {b} @', id(b)) # b = 1 @ 9788608
Functions simpleSwapFun_v1()
and simpleSwapFun_v2()
are equivalent and both return results which are then assigned to existing variables, effectively swapping their previously held values.ย
This approach universally works well across all variable types and treats a function as a self-contained unit without any interaction with its environment (outer scope). The only exception is input parameters of a function, accessed in a unidirectional, read-only way. The described property is a significant one, and functions characterized by it are described as being without side effects.
In general, a side effect represents a phenomenon that implies the modification of objects from the function’s inner scope in a function’s outer scope. Even though this approach has its practical applications, if not used carefully, it can easily introduce various bugs and errors in programs.
Why is this important? When a function has no side effects, the desired effect of the function execution, in our case, swapping the variable values, is realized exclusively by the assignment of returned values to variables outside the called function. Specifically, the referenced objects remain unmodified, and variables referencing these objects only exchange their references.
String Swapping in Python – Swapping by Variable Modification
An alternative approach to swapping values of Python variables employs the side effect phenomenon by referencing variable modification.
The referencing variable modification is similar to the universal approach implemented via functions simpleSwapFun_v1()
and simpleSwapFun_v2()
in that it preserves the state of referenced objects.
Instead of assigning return values to reference variables, the difference lies in the direct modification of referencing variables (remember: side effect!).
The following snippet almost works, but instead results in an error SyntaxError: name 'a' is parameter and global
(the same error goes for ‘b
‘):
def simpleSwapFun_v3(a, b): global a, b a, b = b, a
The causes of this error are input parameters a
and b
, which are local by definition, and cannot be simultaneously declared as global
. However, global declaration is required to โbreak the wallโ and introduce the side effect phenomenon, i.e., enable access to the outside variables a
and b
. So, by removing the function parameters, the following example works just fine, because our function reads from and writes to outer variables defined as global:
def simpleSwapFun_v4(): global a, b a, b = b, a a = 1 b = 7 print('Before swapping:') print('a @', id(a), a) # a = 1 @ 9788608 print('b @', id(b), b) # b = 7 @ 9788800 # The results swap their places simpleSwapFun_v4() print('After swapping:') print('a @', id(a), a) # a = 7 @ 9788800 print('b @', id(b), b) # b = 1 @ 9788608
Objects Swapping in Python – Swapping by Object Modification
Referenced object modification keeps the variable references intact so that the variables reference the same objects during the entire program execution. To perform the swap, the referenced objects have to be modified by exchanging their contents. In other words, the swap is performed on the object level, contrary to previous examples, where the swap was performed on the reference level.
For instance, if two variables reference one list each, we can effectively swap these variables by exchanging elements between these lists and keep both variables referencing the lists intact. However, this approach can be carried out under two strict limitations:
- the referenced objects have to be mutable, and
- the referenced objects have to be of the same type.
Reminder: An immutable object cannot be modified after it is created, meaning that the only way of changing the value of a variable is by assigning it a different object. The built-in immutable core types in Python are numbers, strings, and tuples.
Reminder: A mutable object can be and in most cases is modified during program execution. The built-in mutable core types are lists, dictionaries, and sets.
The first limitation is based on the simple fact that if the objects (whose contents we’re trying to swap) are not mutable, the exchange of contents is prevented by definition.
The second limitation is a bit less straightforward and deals with the fact that each object is assigned with a memory address, i.e. an object identity, upon its creation. The object identity is not related to the object type. However, to change (or cast) an object from one type to another, a new object has to be created and assigned to the referencing variable (potentially leaving the initial object unreferenced/unused):
a = [1, 2, 3] print(f'a = {a} @ ', id(a)) # a = [1, 2, 3] @ 139893930618624 a = set(a) print(f'a = {a} @ ', id(a)) # a = {1, 2, 3} @ 139894074237888
The example shows how type casting produces a new referenced object, replacing the initial one. Now that the referencing variable is connected to the new object, the initial object is not participating in the swap anymore and the second limitation is broken.
In contrast, when both limitations are complied with, the swap can be performed, leaving the referencing variables intact.
Example – Lists Swapping in Python
The problem of lists swapping in Python can easily be solved by every previously mentioned approach. Yet, the most self-contained and natural approach is the one based on swapping by object modification. Here, we keep the references to the original objects and just exchange their contents by applying list slicing (consider a[:]
and b[:]
on the left side of an expression) and shallow copying (consider a[:]
and b[:]
on the right side of an expression), without introducing a side effect:
def swapTwoLists_v1(a, b): tmp = a[:] a[:] = b[:] b[:] = tmp[:] a = [1, 2, 3] b = [4, 5, 6, 7] print('Before swapping:') print(f'a = {a} @', id(a)) # a = [1, 2, 3] @ 139893930520064 print(f'b = {b} @', id(b)) # b = [4, 5, 6, 7] @ 139894074187968 # The results switch their places and get assigned in that order swapTwoLists_v1(a, b) print('After swapping:') print(f'a = {a} @', id(a)) # a = [4, 5, 6, 7] @ 139893930520064 print(f'b = {b} @', id(b)) # b = [1, 2, 3] @ 139894074187968
A reminder: a shallow copy works by creating a new (list) object that then gets filled with references to individual elements from the source object.
To even further simplify our function, we will use a nice one-liner that replaces all the objects’ elements with shallow copies of the source objects’ contents:
def swapTwoLists_v2(a, b): a[:], b[:] = b[:], a[:] a = [1, 2, 3] b = [4, 5, 6, 7] print('Before swapping:') print(f'a = {a} @', id(a)) # a = [1, 2, 3] @ 139893930517440 print(f'b = {b} @', id(b)) # b = [4, 5, 6, 7] @ 139894074167872 # The results switch their places and get assigned in that order swapTwoLists_v2(a, b) print('After swapping:') print(f'a = {a} @', id(a)) # a = [4, 5, 6, 7] @ 139893930517440 print(f'b = {b} @', id(b)) # b = [1, 2, 3] @ 139894074167872
Both swapTwoLists_v1()
and swapTwoLists_v2()
are equivalent in terms of inputs and outputs, resulting in swapped object contents and preserved variable references.
Summary
In this article, we have learned about the simple swapping function. First, we looked at how to perform swapping by assignment and how it is universally applicable to different object types. Second, we saw what swapping by variable modification is and how we can (carefully!) use the side effect phenomenon to implement it. Third, we analyzed the principle behind swapping by object modification and got familiar with its limitation and advantages, followed by an illustrative example.
Simple swapping is a straightforward idea and, if implemented in-place rather than in a function, it is very compact and safe. However, when a function is needed, several approaches are possible, each carrying its advantages and disadvantages, depending on the specific case.