
Today, I am going to talk about intrinsic characteristics of Python.
Have you ever received a TypeError while running your Python program?
It occurs because Python is a dynamic type language. It means the language verifies variable types during execution (runtime) - not during compilation, even though Python does compile your code into bytecode before running it. The type check only happens the instant an operation is actually executed, not when the code is first parsed and compiled.
Let’s go with an example:
def python_dynamic_language_example(execute_invalid_operation):
print("The function was compiled to bytecode already")
if execute_invalid_operation:
a = 5
b = "5"
return a + b
return "No invalid operation was found"This function can run successfully even though there is an invalid operation inside the if clause:
python_dynamic_language_example(False)The function was compiled to bytecode already
'No invalid operation was found'
Moreover, since this is a Quarto document, the mere fact that the whole .qmd file compiled successfully already shows the function was compiled to bytecode without error.
However, when we try to access the code portion that does the calculation, we receive the TypeError:
python_dynamic_language_example(True)The function was compiled to bytecode already
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[3], line 1 ----> 1 python_dynamic_language_example(True) Cell In[1], line 7, in python_dynamic_language_example(execute_invalid_operation) 3 4 if execute_invalid_operation: 5 a = 5 6 b = "5" ----> 7 return a + b 8 return "No invalid operation was found" TypeError: unsupported operand type(s) for +: 'int' and 'str'
which means that Python did not perform the operation because it was operated between two invalid types, even though it was compiled, as can be seen by dis:
import dis
dis.dis(python_dynamic_language_example) 1 0 RESUME 0
2 2 LOAD_GLOBAL 1 (NULL + print)
12 LOAD_CONST 1 ('The function was compiled to bytecode already')
14 CALL 1
22 POP_TOP
4 24 LOAD_FAST 0 (execute_invalid_operation)
26 POP_JUMP_IF_FALSE 9 (to 46)
5 28 LOAD_CONST 2 (5)
30 STORE_FAST 1 (a)
6 32 LOAD_CONST 3 ('5')
34 STORE_FAST 2 (b)
7 36 LOAD_FAST 1 (a)
38 LOAD_FAST 2 (b)
40 BINARY_OP 0 (+)
44 RETURN_VALUE
8 >> 46 RETURN_CONST 4 ('No invalid operation was found')
The fact that Python compiled this code, it means that it is dynamically type, instead of statically typed:
Languages that verify the type of the variable before
runtime, such asJavaandC++, are known asstatic type language. The advantage of it is that in production you have more guarantees that your code will not break due to an accidental variable type change or operation. So, you know in advance that a mistake can occur.
The fact that Python did not allow that operation occur in runtime means that it is strongly typed, instead of weakly typed:
Weak typed languages, such as
JavaScriptandPHPwould allow this operation to occur. It would result in"55"(string) inJavaScriptand10(integer) in PHP.
Having these concepts in mind can help you to think on which language to use, or anticipate errors.
Flexibility in Python
Even though this is an odd behavior in Python, it allows you the change the variable type while programming:
a = 5
print(type(a))
print(id(a))
a = "5"
print(type(a))
print(id(a))<class 'int'>
103300731305384
<class 'str'>
103300731366408
It occurs because the value has a type (dynamic type language), not the variable. It also allows you to write less boilerplate code, and make your code into production faster.
What if you want to put more restrictions in Python ?
In recent Python versions (3.x), what you can do is add type annotation to variables and output of functions. However, it will still allow the function to be compiled:
def python_dynamic_language_example_typed(
execute_invalid_operation: bool,
) -> str | TypeError:
print("The function was compiled to bytecode already")
if execute_invalid_operation:
a: int = 5
b: str = "5"
return a + b
return "No invalid"
dis.dis(python_dynamic_language_example_typed) 1 0 RESUME 0
4 2 LOAD_GLOBAL 1 (NULL + print)
12 LOAD_CONST 1 ('The function was compiled to bytecode already')
14 CALL 1
22 POP_TOP
6 24 LOAD_FAST 0 (execute_invalid_operation)
26 POP_JUMP_IF_FALSE 9 (to 46)
7 28 LOAD_CONST 2 (5)
30 STORE_FAST 1 (a)
8 32 LOAD_CONST 3 ('5')
34 STORE_FAST 2 (b)
9 36 LOAD_FAST 1 (a)
38 LOAD_FAST 2 (b)
40 BINARY_OP 0 (+)
44 RETURN_VALUE
10 >> 46 RETURN_CONST 4 ('No invalid')
However, if you are coding in an editor and uses a tool that does live checking, such as Pylance in Vs Code in standard mode, it would warn you about the operation:

and you could fix it right away.
These are concepts that you may not even realize that are important unless you know it.
Happy coding!