Overview of Parameter Passing



This content originally appeared on Level Up Coding – Medium and was authored by Jack Ka-Chun, Yu

Overview of Parameter Passing: A Comparison Between Python and Other Programming Languages

In data science research and AI modeling, we frequently use Python. During this process, data variation and sharing are common requirements, and the way parameters are passed directly affects model behavior, data processing, and performance optimization. For example, when we need to share and modify large data structures, using pass-by-reference can avoid unnecessary data copies, saving memory and time. However, modifying parameters passed into a function will also impact the original data, which can lead to unexpected errors in certain situations. Understanding parameter passing in Python also helps in mastering the concepts of deep copy and shallow copy. When we want to make independent modifications to data, it is essential to know how to correctly create copies, rather than just passing references, especially during the data preprocessing stage. Therefore, understanding Python’s parameter passing mechanism is crucial in data science and AI modeling.

Significance of Comparing Parameter Passing Mechanisms in Python and Other Languages

The comparison of parameter passing mechanisms between Python and other programming languages is significant because different languages often represent different programming paradigms (e.g., interpreted vs. compiled, dynamically typed vs. statically typed, implicit type conversion vs. explicit type conversion).

First, let’s understand how different programming paradigms impact parameter passing.

Interpreted Language vs. Compiled Language

Interpreted Language

An interpreted language refers to a programming language where code is interpreted and executed line-by-line by an interpreter at runtime. The code does not need to be converted into machine code before execution; instead, the interpreter interprets and executes it line-by-line during runtime.

In interpreted languages, code is interpreted and executed one line at a time during execution, which means parameter passing is also handled during execution. Since interpreted languages do not need to be compiled before execution, they offer greater flexibility in handling data types and parameter passing, allowing functions to accept parameters of different types.

Additionally, in interpreted languages, parameter passing is often done by reference, especially for mutable objects. The interpreter can dynamically decide how to handle parameters based on their types during execution.

# Example of performing operations on mutable objects through pass-by-reference

def modify_list(data):
data.append(10)

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list) # Output: [1, 2, 3, 10]

Examples: Python, JavaScript, Ruby, PHP, Perl, R, Bash (Shell Script), MATLAB, Lua, Tcl, Erlang, Racket

Note: The process of “interpretation” refers to the interpreter translating the high-level syntax of the source code (e.g., print("Hello, World!")) line-by-line into executable instructions, which can be intermediate code, virtual machine instructions, or directly converted to machine code.

Compiled Language

A compiled language is one in which code must be compiled by a compiler before execution, transforming it into machine code or an executable file. This machine code can be directly executed by computer hardware without further interpretation.

In compiled languages, code must be compiled before execution, meaning that parameter passing methods (such as pass-by-value or pass-by-reference) are determined during compilation. The compiler generates corresponding machine code to handle parameter passing based on variable types.

Because compiled languages require static checks before execution, the parameter passing method (value or reference) and type matching must be fully determined at compile time. This ensures both safety and performance during execution.

Examples: C, C++, C#, Java, Go (Golang), Rust, Swift, Objective-C, Kotlin, Haskell, Fortran, Pascal, Ada, Algol

The following diagram compares the general processes of compiled and interpreted languages:

image source: TechyTalks. (2016, August 29). Let’s know what are the difference between Programming Language & Scripting Language ? https://techytalks007.blogspot.com/2016/08/let-us-know-what-are-difference-between.html

**Notes that not all compiled and interpreted languages necessarily follow the processes shown in the diagram. These processes provide a general description of compilation and interpretation, but the actual implementation may vary depending on the language design and execution environment.

Dynamically Typed Language vs. Statically Typed Language

Dynamically Typed Language

A dynamically typed language is one in which variable data types are determined at runtime. In other words, the type of a variable is automatically decided during the execution of the program, and developers do not need to explicitly specify the type when writing the code.

# Developers do not need to specify variable types when declaring variables

x = 10 # x is an integer
x = "Hello" # Now x is a string

In dynamically typed languages, variables do not need to have a specified type at declaration but are determined automatically based on assignment during runtime. This makes parameter passing more flexible. A function can accept parameters of different types during invocation, and parameter passing is usually handled by reference internally to reduce the cost of copying.

Additionally, in dynamically typed languages, whether a parameter is mutable affects parameter passing. For example, for immutable objects, Python typically uses a pass-by-value-like approach (though Python’s parameter passing is not exactly the same as traditional “pass-by-value”). For mutable objects, Python uses pass-by-reference.

Examples: Python, JavaScript, Ruby, PHP, Perl, R, Bash (Shell Script), MATLAB, Lua, Tcl

Statically Typed Language

A statically typed language is one in which variable types are determined at compile time. During code writing and compilation, the types of variables must be explicitly specified and cannot be changed afterwards.

// Developers must explicitly specify variable types when defining variables

#include <iostream>
using namespace std;

int main() {
int x = 10; // x is an integer
x = "Hello"; // Error: Cannot assign a string to an integer variable
}

In statically typed languages, variables must have a specified type at declaration, and this type is checked at compile time. This means the method of parameter passing must be explicitly determined during compilation, which often results in better performance and improved error detection.

Additionally, in statically typed languages, the compiler determines whether to use pass-by-value or pass-by-reference based on the type during compilation. The type of parameters cannot be changed at runtime. For example, in C++, parameters can be passed by pointer, reference, or value, and these choices are fixed at compile time, ensuring execution efficiency.

Examples: C, C++, C#, Java, Go (Golang), Rust, Swift, Objective-C, Kotlin, Haskell, Fortran, Pascal, Ada, Algol, Erlang, Racket

Implicit Type Conversion Language vs. Explicit Type Conversion Language

Implicit Type Conversion Language

A weakly typed language (implicit type conversion language) allows for implicit type conversion. In such languages, the compiler or interpreter automatically converts one data type to another without the developer explicitly performing the type conversion.

In weakly typed languages, variable data types are converted implicitly based on context. When a function receives parameters, a weakly typed language will automatically convert the parameter type to match the function requirements.

# Example of expected implicit type conversion

def add(a, b):
return a + b

# Implicit conversion between integer and float
x = 5
y = 2.5
result = add(x, y) # Here, x is an integer, y is a float
print(result) # Output: 7.5

However, this flexibility may lead to unexpected errors, especially when mixing numbers and strings. For example, if a developer accidentally passes the wrong type, the language may attempt an automatic conversion, potentially resulting in unintended outcomes. Weakly typed languages usually perform type checks and implicit conversions during runtime rather than compile time, meaning type errors may only be discovered during program execution, which increases the risk of errors.

# Example of unexpected implicit type conversion

x = "The result is: "
y = 10
result = x + str(y) # Must explicitly convert integer to string; otherwise, an error will occur
print(result) # Output: "The result is: 10"

Examples: Python, JavaScript, Ruby, PHP, Perl, R, Bash (Shell Script), MATLAB, Lua, Tcl

Explicit Type Conversion Language

An explicit type conversion language (strongly typed language) enforces strict rules for type conversion. In such languages, the developer must explicitly perform type conversion to change one data type to another.

In strongly typed languages, variable data types must be explicitly converted at compile time or runtime. When passing parameters to functions, if the parameter type does not match the function’s expected type, the language requires the developer to perform an explicit type conversion.

// Example of explicit type conversion

#include <iostream>
using namespace std;

void display(int num) {
cout << num << endl;
}

int main() {
double value = 10.5;
display((int)value); // Must explicitly convert double type to int type
}

In strongly typed languages, since all parameter passing and type conversions need to be explicitly handled, it reduces potential errors caused by implicit conversion, especially when performing operations involving different data types, thereby improving the safety and reliability of the program.

Examples: C, C++, C#, Java, Go (Golang), Rust, Swift, Objective-C, Kotlin, Haskell, Fortran, Pascal, Ada, Algol, Erlang, Racket

Now, let us understand the difference of main types of parameter passing.

Pass by Value

img
image source: Gangwani, S. (2022, April 18). Pass-By Value and Pass-By Reference in JavaScript with Examples — Scaler Topics. Scaler Topics. https://www.scaler.com/topics/javascript/pass-by-value-and-pass-by-reference/

C++

Input:

#include <iostream>
using namespace std;

void passByValue(int num2) {
cout << "Inside function before modification:" << endl;
cout << "Value of num2 = " << num2 << endl;
cout << "Address of num2 = " << &num2 << endl << endl;

// Modify the value of num2
num2 = 2002;
cout << "Inside function after modification:" << endl;
cout << "Value of num2 = " << num2 << endl;
cout << "Address of num2 = " << &num2 << endl << endl;
}

int main() {
int num1 = 2001; // Original variable num1
cout << "Before function call:" << endl;
cout << "Value of num1 = " << num1 << endl;
cout << "Address of num1 = " << &num1 << endl << endl;

// Call passByValue function, passing a copy of num1's value to num2
passByValue(num1);

cout << "After function call:" << endl;
cout << "Value of num1 = " << num1 << endl;
cout << "Address of num1 = " << &num1 << endl;
return 0;
}

Output:

Before function call:
Value of num1 = 2001
Address of num1 = 0x6ffe0c

Inside function before modification:
Value of num2 = 2001
Address of num2 = 0x6ffde0

Inside function after modification:
Value of num2 = 2002
Address of num2 = 0x6ffde0

After function call:
Value of num1 = 2001
Address of num1 = 0x6ffe0c

Python

Input:

def pass_by_value(num2):
print("Inside function before modification:")
print("Value of num2 =", num2)
print("Address of num2 =", id(num2), '\n')

# Modify the value of num2
num2 = 2002
print("Inside function after modification:")
print("Value of num2 =", num2)
print("Address of num2 =", id(num2), '\n')

# Main program
num1 = 2001 # Original variable num1
print("Before function call:")
print("Value of num1 =", num1)
print("Address of num1 =", id(num1), '\n')

# Call pass_by_value function, passing a copy of num1's value to num2
pass_by_value(num1)

print("After function call:")
print("Value of num1 =", num1)
print("Address of num1 =", id(num1))

Output:

Before function call:
Value of num1 = 2001
Address of num1 = 2560143910288

Inside function before modification:
Value of num2 = 2001
Address of num2 = 2560143910288

Inside function after modification:
Value of num2 = 2002
Address of num2 = 2560143912624

After function call:
Value of num1 = 2001
Address of num1 = 2560143910288

**Notes that in Python, when passing immutable objects like integers to a function, it is similar to passing by value. In reality, a reference (address) to the object is passed, not the object itself. However, since integers are immutable, modifying the value inside the function creates a new object and redirects the variable to the new object’s address.

Pass by Reference

Notes that this image has been modified from its original version. Original image source: Gangwani, S. (2022, April 18). Pass-By Value and Pass-By Reference in JavaScript with Examples — Scaler Topics. Scaler Topics. https://www.scaler.com/topics/javascript/pass-by-value-and-pass-by-reference/

C++

Input:

#include <iostream>
using namespace std;

void modifyObject(int &object2) {
cout << "Inside function before modification:" << endl;
cout << "Value of object2 = " << object2 << endl;
cout << "Address of object2 = " << &object2 << endl << endl;

// Modify the value of object2
object2 = 3006;
cout << "Inside function after modification:" << endl;
cout << "Value of object2 = " << object2 << endl;
cout << "Address of object2 = " << &object2 << endl << endl;
}

int main() {
int object1 = 3005; // Original variable object1
cout << "Before function call:" << endl;
cout << "Value of object1 = " << object1 << endl;
cout << "Address of object1 = " << &object1 << endl << endl;

// Pass reference of object1 to modifyObject function
modifyObject(object1);

cout << "After function call:" << endl;
cout << "Value of object1 = " << object1 << endl;
cout << "Address of object1 = " << &object1 << endl;
return 0;
}

Output:

Before function call:
Value of object1 = 3005
Address of object1 = 0x6ffe0c

Inside function before modification:
Value of object2 = 3005
Address of object2 = 0x6ffe0c

Inside function after modification:
Value of object2 = 3006
Address of object2 = 0x6ffe0c

After function call:
Value of object1 = 3006
Address of object1 = 0x6ffe0c

Python

Input:

def modify_object(object2):
print("Inside function before modification:")
print("Value of object2 =", object2[0])
print("Address of object2 =", id(object2), '\n')

# Modify the value of object2
object2[0] = 3006
print("Inside function after modification:")
print("Value of object2 =", object2[0])
print("Address of object2 =", id(object2), '\n')

# Main program
object1 = [3005] # Using list to simulate a mutable object
print("Before function call:")
print("Value of object1 =", object1[0])
print("Address of object1 =", id(object1), '\n')

# Pass reference of object1 to modify_object function
modify_object(object1)

print("After function call:")
print("Value of object1 =", object1[0])
print("Address of object1 =", id(object1))

Output:

Before function call:
Value of object1 = 3005
Address of object1 = 1880245478208

Inside function before modification:
Value of object2 = 3005
Address of object2 = 1880245478208

Inside function after modification:
Value of object2 = 3006
Address of object2 = 1880245478208

After function call:
Value of object1 = 3006
Address of object1 = 1880245478208

Pass by Pointer

img
image source: Gaurav, S. (2023, October 5). C Pass Addresses and Pointers — Scaler topics. Scaler Topics. https://www.scaler.com/topics/c-pass/

C++

Input:

#include <iostream>
using namespace std;

int main() {
int Var1 = 100; // Value of the variable
int *ptr = &Var1; // Pointer to the variable
cout << "Value of Var1: " << Var1 << endl; // Variable value
cout << "Address of Var1: " << &Var1 << endl << endl; // Address of the variable, &Var1
cout << "Value inside pointer ptr: " << ptr << endl; // Pointer value (address of Var1)
cout << "Address of pointer ptr: " << &ptr << endl; // Address of the pointer itself
cout << "Value pointed to by ptr: " << *ptr << endl; // Value the pointer points to
}

Output:

Value of Var1: 100
Address of Var1: 0x6ffe0c

Value inside pointer ptr: 0x6ffe0c
Address of pointer ptr: 0x6ffe00
Value pointed to by ptr: 100

Python

Input:

var1 = 100
ptr = var1

print("Value of Var1:", var1)
print("Address of Var1:", id(var1), '\n')

print("Value inside pointer ptr:", ptr)
print("Address of pointer ptr:", id(ptr))
print("Value pointed to by ptr:", ptr)

Output:

Value of Var1: 100
Address of Var1: 140713138691976

Value inside pointer ptr: 100
Address of pointer ptr: 140713138691976
Value pointed to by ptr: 100

**Notes that Python does not support true Pass by Pointer behavior since it lacks explicit pointer syntax (e.g., & and * operators in C++). In Python, variables are references to objects, and parameter passing is handled via Pass by Object Reference. This approach allows references to objects to be passed, but it is not equivalent to memory address pointers as used in C++.

Pass by Value-Result

C++

Input:

#include <iostream>
using namespace std;

void increment(int &a) {
a = a + 10; // Add 10
}

int main() {
int x = 5;

cout << "Before increment: x = " << x << endl;

increment(x); // Increment the value of x using pass by value-result

cout << "After increment: x = " << x << endl;

return 0;
}

Output:

Before increment: x = 5
After increment: x = 15

Python

Input:

def increment(a):
a[0] = a[0] + 10 # Add 10

def main():
x = [5] # Use a list to simulate a mutable parameter, initial value is 5

print("Before increment: x =", x[0])

increment(x) # Pass the list to simulate pass by value-result behavior

print("After increment: x =", x[0])

main()

Output:

Before increment: x = 5
After increment: x = 15

**Notes that in Python, true Pass by Value-Result behavior cannot be directly achieved. In this paradigm, the initial value of the parameter is modified within the function, and the result is copied back to the original variable after the function returns. Since Python lacks explicit syntax for Pass by Value-Result, this behavior cannot be precisely replicated. Instead, mutable types like lists can be used to approximate similar behavior.

Pass by Name

Algol

Input:

procedure squareSum(x, y);
integer x, y;
begin
x := x * x;
y := y * y;
end;

begin
integer a, b;
a := 3;
b := a + 1; // b equals 4

squareSum(a, b); // Call squareSum using pass by name
print(a, b);
end

Tracing:

a := 3;
b := a + 1; b := 4;
squareSum(a, b);
x := a;
y := b; y := x + 1 := a + 1;
x := x * x; x := a * a; x := 3 * 3; x := a := 9;
y := y * y; y := b * b; y := (a + 1) * (a + 1); y := (9 + 1) * (9 + 1); y := 100;

Output:

9 100

**Notes that the type declaration of parameters (e.g., integer) must be explicitly specified at the beginning of the procedure, and cannot be included in the begin block. This is a syntax requirement in Algol 60.

Python

Input:

def square_sum(x, y):
x_val = x() # Get the value of x expression
y_val = y() # Get the value of y expression

x_val = x_val * x_val
y_val = y_val * y_val

return x_val, y_val

# Main program
a = 3
b = lambda: a + 1 # b is a lambda expression, simulating pass by name

print("Before square_sum:")
print("a =", a, "b =", b())

# Call square_sum and use lambda to simulate pass by name
a_squared, b_squared = square_sum(lambda: a, b)

print("After square_sum:")
print("a squared =", a_squared, "b squared =", b_squared)

Tracing:

a = 3
b = lambda: a + 1 b = a + 1 b() = 3 + 1 = 4
x = lambda: a
y = lambda: a + 1
x_val = x() x_val = 3
y_val = y() y_val = 4

Output:

Before square_sum:
a = 3 b = 4

After square_sum:
a squared = 9 b squared = 16

Overview of Parameter Passing was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.


This content originally appeared on Level Up Coding – Medium and was authored by Jack Ka-Chun, Yu