Typing in Python — Strong, Dynamic, Implicit
Tags: python, typing, programming, computerscience
Published: 2023-11-15 16:00:00

Everyone knows that in Python, data types are divided into mutable and immutable, but how typing works in Python? To answer this question, we need to look at the characteristics of Python's type system.

Typically, when describing the type system of a programming language, three questions are addressed:

1. Does the language perform implicit automatic type conversions?
2. At what stage is the type of a variable determined?
3. Is it necessary to explicitly declare the type of variables?

By answering these questions, one can determine whether a language has strong or weak typing, static or dynamic typing, and explicit or implicit typing.

Python has **strong, dynamic, implicit** typing, and let's take a closer look at why.

## 1. Strong Typing

When we talk about "strong typing" in the context of programming, we're referring to a language's strict approach to handling variables of different types. In the Python programming language, which is characterized by strong typing, **different data types are not automatically mixed**. For example, the expression `"some string" - 3` will cause an error because the language does not allow a string to be implicitly converted to a number to perform a mathematical operation. When encountering type-related errors, Python raises a **TypeError** exception.

Similarly, attempting to add a list `[2, 1, 0]` and a set `set([2, 23, 2])` will also result in an error, as Python will not look for ways to automatically convert one data structure to another to perform the operation. By contrast, JavaScript, for example, allows for adding strings to numbers without issues: `3 + '1'  // Results in the string: '31'`.

Despite its strong typing, Python allows certain operations between different data types, but this is due to explicit implementation rather than automatic conversion:

```python
# Sequence repetition:
# You can "multiply" a string or a list by a number,
# resulting in a repeating sequence.

print("word" * 3)  # wordwordword
print([1, 2] * 3)  # [1, 2, 1, 2, 1, 2]

# float and int
print(5 + 0.1)  # 5.1

# bool and number
print(2.2 + True)  # 3.2

# Note: The bool type inherits from int,
# so here the addition operation is straightforward.

# and others...
```

These operations are possible thanks to the language's internal mechanisms, which provide clear implementation for such cases. For instance, in your program, you can explicitly define the behavior of your type (class) when added to another object using magic methods. However, if you do not do this, you will get an error when attempting to add it to something else.

To illustrate, let's make a string behave like it does in JavaScript: converting the object it's being added to into a string and then concatenate the result.

```python
class JavaScriptStr(str):
    def __add__(self, value):  # Explicit implementation of addition
        return super().__add__(str(value))


number = JavaScriptStr("3")
print(number + 1)  # "31"
```
## 2. Dynamic Typing

Dynamic typing in Python means that the **data types of variables are determined at runtime**, rather than at compile-time as in statically typed languages. This facilitates writing flexible code and allows, for example, the creation of functions that work with various types of data.

Consider the function `find`, which searches for an element `required_element` in a sequence `sequence`. In the language **C**, to implement the same logic, you would have to write several versions of the function for different data types, while in Python, one is sufficient:

```python
def find(required_element, sequence):
    """Performs a search for an element within a sequence."""
    for index, element in enumerate(sequence):
        if element == required_element:
            return index
    return -1

print(find(2, [1, 2, 3]))  # Outputs: 1
print(find("c", ("a", "b", "c", "d")))  # Outputs: 2

print(find(1, 1))  # Raises a TypeError exception
```
The downside of dynamic typing is that it can lead to unexpected runtime errors. For example, if we pass a non-iterable object to `sequence` (which cannot be traversed using **for**), we will get an error. But we will know about it when the program execution reaches the specific line with the **for** loop over this object.

Dynamic typing also allows you to overwrite a single variable with data of different types.

```python
number = 1  # int
number = "One"  # str
```
## 3. Implicit Typing

In languages with implicit typing, i**t isn't necessary to explicitly declare the type of a variable** — the interpreter determines the type based on the assigned value. This simplifies the code and makes it more readable:

```python
var = 5  # int
var = "some string"  # now var is a str
```

### Type Annotations in Python

Since version 3.5, Python introduced support for type annotations, allowing programmers to explicitly indicate the types of variables and the values returned by functions. **This doesn't affect the interpreter's operation** but aids in programming and code maintenance:

```python
def plus(x: int, y: int) -> int:
    return x + y

print(plus(5, 10))  # Outputs: 15

# The function will perform string concatenation.
# This shows that annotation is not equal to explicit typing.
print(plus("Hello ", "world!"))  # Outputs: Hello world!
```

Specifying types through annotations makes the code more understandable for other developers and also allows for the detection of errors before the program is run, using additional static analysis tools. However, the language still retains the flexibility inherent in dynamic typing.

---

## Conclusion

Typing in Python combines rigor and flexibility: on one hand, strong typing protects against type mismatch errors, on the other hand, dynamic typing allows for writing more versatile and easily readable code. Type annotations bring clarity to the program's structure, making it more predictable and safe.