Недавно у меня был запрос от клиента, чтобы я помог ему исправить и отладить его код Python для какого-то личного проекта. Это был ад! — код был беспорядочным и беспорядочным, комментариев не было, и я потратил чертовски много времени, выясняя, что делает каждая существующая функция! Python — это язык с динамической типизацией, что означает, что нам не нужно указывать типы данных при создании переменных и функций. Хотя это уменьшает объем кода, который нам нужно написать, рабочая нагрузка, которую мы сэкономим, в свою очередь, добавляется к следующему разработчику, которому необходимо понять и отладить существующую функцию!

Python как язык с динамической типизацией

Допустим, у нас есть простая функция add, которая складывает 2 числа.

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

Обычно в Python нам не нужно указывать типы данных входных и выходных данных — поэтому Python позволит нам передать в функцию любой тип данных, если они работают с оператором +.

x = add(4, 5) # x will be 9
x = add(4.0, 5.0) # x will be 9.0
x = add("apple", "pie") # x will be "applepie"
x = add(["apple"], ["pie"]) # x will be ["apple", "pie"]

Обратите внимание, что мы можем передавать целые числа, числа с плавающей запятой, строки, списки и т. д. в функцию add, и Python просто позволит нам сделать это, если a и b можно сложить вместе. Однако по мере того, как функции становятся более сложными, читабельность сильно страдает, и нам часто приходится гадать и интерпретировать, к каким типам данных относятся определенные переменные и функции.

Например, допустим, нам поручили исправить эту функцию:

def magic(a, b, c):
    if c:
        return [i for i in a if i in b]
    return list(set(a))

На первый взгляд, мы не смогли бы сразу сказать, каковы входные аргументы или выходные данные функций; в этом случае нам нужно будет исследовать и вывести типы данных из самого определения функции. Таким образом, если вы работаете над полусложным проектом с другими разработчиками, для удобочитаемости я бы рекомендовал явно указывать типы входных данных, выходных данных и переменных.

Указание типов данных в функциях

Допустим, нам нужна функция, которая принимает 2 целых числа и складывает их вместе.

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

Теперь давайте изменим эту функцию, включив в нее типы ввода и вывода.

def add(a:int, b:int) -> int:
    return a + b

Обратите внимание на дополнительные : после входных аргументов a и b, которые определяют целочисленный тип. Это означает, что аргументы a и b, которые входят в функцию, должны быть целочисленного типа.

Также обратите внимание на -> int после входных аргументов. Это означает, что функция должна возвращать целое число.

Указание типов данных при объявлении переменных

Вот как мы обычно инициализируем целое число:

x = 5

Вот как мы инициализируем целое число, явно указывая тип данных:

x:int = 5

Это означает, что переменная x является целым числом.

Указание нескольких типов данных

def add2(a:int) -> int:
    return a + 2

Иногда мы хотим, чтобы наши функции могли принимать более одного типа данных. Например, допустим, у нас есть функция add2, которая принимает число и прибавляет к нему 2. Интуитивно эта функция должна работать как для чисел с плавающей запятой, так и для целых чисел. Вот как мы указываем, что функция может принимать или возвращать несколько типов данных:

from typing import Union
def add2(a:Union[int, float]) -> Union[int, float]:
    return a + 2

Здесь Union[int, float] означает, что переменная a может быть либо целым числом, либо числом с плавающей запятой. Это означает, что функция add2 принимает либо целое число, либо число с плавающей запятой, а также возвращает целое число или число с плавающей запятой.

Указание типов списков и словарей

Допустим, у нас есть функция, которая принимает список строк и возвращает другой список, содержащий версии исходных элементов в верхнем регистре.

def convert_upper(lis):
    return [i.upper() for i in lis]
convert_upper(["apple", "orange"]) # ["APPLE", "ORANGE"]

В этом случае нам нужно будет указать, что входной список должен быть списком строковых значений, а не списком целых чисел, чисел с плавающей запятой, логических значений или чего-то еще. Вот как мы можем это сделать:

from typing import List
def convert_upper(lis: List[str]) -> List[str]:
    return [i.upper() for i in lis]

Здесь List[str] означает, что переменная lis должна быть списком, содержащим строковые значения. В этом случае эта функция принимает список строковых значений, а также возвращает список строковых значений.

from typing import Dict
def print_dict(d:Dict[str,int]):
    for k,v in d.items():
        print(k,v)

В данном случае Dict[str,int] означает, что переменная d должна быть словарем, в котором ключи имеют строковый тип, а значения — целочисленный тип.

Важное примечание. Вы по-прежнему можете передавать любые типы данных.

Важно отметить, что хотя эти спецификации типов означают, что определенные переменные должны быть определенных типов, они не означают, что они должны быть этих определенных типов. Допустим, у нас есть эта функция

def add(a: int, b:int) -> int:
    return a + b

Эта функция должна принимать 2 целочисленные переменные и возвращать целочисленное значение. Однако, если мы скажем передать 2 строки, Python все равно разрешит это!

x = add("apple", "pie")
# x will still be "applepie"

Основная цель указания этих типов данных — сообщить другим разработчикам, какими типами данных должны быть определенные переменные, какие типы данных обычно следует вводить в функцию, а также какие типы данных функция должна возвращать. Однако он не диктует строго эти типы данных.

Заключение

Надеюсь, это сделает Python более простым языком для работы! Если это принесло вам пользу и вы хотите поддержать меня как писателя, подумайте о том, чтобы подписаться на членство в Medium! Это 5 долларов в месяц, и вы получаете неограниченный доступ к историям на Medium. Если вы зарегистрируетесь по моей ссылке, я получу небольшую комиссию.

Вот ссылка для чтения неограниченного доступа к Medium

Больше контента на plainenglish.io