Типизация в Python
Очень часто языки программирования сравнивают по их типизации. Иначе говоря — по тому, как устроена их система типов. Давайте разберемся, какая типизация у Python, и что это означает.
Типизация в Python
Для начала мы можем смело сказать, что типизация в Python существует, а следовательно, он относится к типизированным языкам. У многих низкоуровневых языков (вроде ассемблера) вообще нет типизации — любые структуры в них — не более чем набор битов. Типизация позволяет упростить процесс обработки информации. Если данные имеют тип, то машина будет взаимодействовать с ними по правилам, установленным для этого типа.
Неявная типизация
Неявная типизация подразумевает возможность создавать объекты, не указывая их тип.
a = 1 # int b = 1.1 # float c = 'a' # str
Если бы в Python была явная типизация, приходилось бы каждый раз указывать тип любой переменной. Но и у нее есть свои плюсы. Например, иногда полезно указывать, данные каких типов принимает функция, метод или аргумент. Впрочем, Python позволяет и такое:
def func(a: int, b: str) -> float: return round(float(a / len(b)), 2) var: float = func(3, [1, 1, 1]) # 1.0 # Expected type 'str', got 'list[int]' instead
Это называется аннотацией типов. Ее возможности сильно расширяет модуль typing , активно развивающийся с версии Python 3.5. Аннотации никак не влияют на выполнение программы, но IDE может считывать их и предупреждать, если вы использовали не тот тип.
Сильная типизация
Python — язык с сильной типизацией. Это означает, что различные типы нельзя смешивать в одних выражениях.
2 + '2' # Traceback (most recent call last): # File "C:\main.py", line 1, in # 2 + '2' # ~~^~~~~ # TypeError: unsupported operand type(s) for +: 'int' and 'str'
Если бы Python был языком со слабой типизацией, результатом выполнения такого кода стало бы 22 . В ситуации, когда смешиваются разные типы, слабо типизированные языки могут неявно приводить значения к одному из них. Иногда это вызывает непредсказуемые последствия, например, если по неосторожности использовать строку с цифрами вместо числа. Однако Python допускает подобное в некоторых случаях:
2 + 2.2 # 4.2
Типы int и float могут свободно взаимодействовать. Это продиктовано удобством и естественностью таких преобразований.
Сами понятия «сильной» и «слабой» типизации довольно размыты и зависят от множества конкретных решений при разработке языка. Правильнее будет говорить, что какие-то языки более сильные, чем другие.
Динамическая типизация
Python — язык с динамической типизацией. Это означает, что с определенным типом связывается не переменная, а ее значение. Если бы Python был языком со статической типизацией, мы бы не смогли сделать так:
a = 1 a = 'a' a = SomeClass()
Динамическая типизация — одна из причин популярности Python. Для начала, это просто удобно. Программа может менять типы переменных на лету, пользуясь их особенностями.
a = (1, 1, 1, 3, 1, 1) a = list(set(a)) # [1, 3]
Не менее важно то, что динамическая типизация позволяет максимально естественно абстрагироваться от типов и заниматься обобщенным программированием.
def second(a): try: return a[1] except TypeError: return None second([1, 2]) # 2 second('abcd') # b second(1) # None
Однако за такое удобство приходится платить. Динамическая типизация, вместе с интерпретацией кода, стала причиной главнейшего проклятия Python — низкой скорости работы. К тому же, в языках со статической типизацией есть свои механизмы обобщенного программирования, использующие шаблоны или дженерики.
Утиная типизация
«If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.»
В Python применяется утиная типизация. Это означает, что тип данных не имеет значения — важно лишь то, какие методы и свойства они поддерживают. Например, чтобы узнать, длину объекта, мы можем использовать функцию len() . Она не проверяет, к какому типу относится объект, а всего лишь обращается к магическому методу __len__() . Можно узнать длину любого объекта, у которого он прописан (не важно, как именно). И наоборот, объект с очевидной длиной, но без метода __len__() нельзя обработать этой функцией.
class SomeClass: length = 12 def __len__(self): return self.length len('123') # 3 len([1, 2]) # 2 len(SomeClass()) # 12 len(123) # Traceback (most recent call last): # File "C:\main.py", line 13, in # len(123) # TypeError: object of type 'int' has no len()
Заключение
Каждый из вариантов типизации имеет свои преимущества и недостатки. Создатели языков программирования выбирали их комбинации, исходя из своих целей. Гвидо Ван Россум хотел сделать Python максимально удобным и понятным. Благодаря неявной, динамической и утиной типизации, программы на Python выходят лаконичными и простыми для понимания. В то же время, Python имеет строгую типизацию, почти не допускающую неявных преобразований.
![]()
Практический Python для начинающих
Станьте junior Python программистом за 7 месяцев
Python: Сильная (или Строгая) типизация
Python — один из языков, который строго относится к типам данных. Поэтому на любую несовместимость типов он ответит ошибкой. Все дело в сильной типизации.
Нам известно про два разных типа данных: числа и строки. Например, мы могли складывать числа, потому что операция сложения — это операция для типа «числа». А что, если применить эту операцию не к двум числам, а к числу и строке?
print(1 + '7') # TypeError: unsupported operand type(s).
Python не разрешит сложить число 1 и строку ‘7’ , потому что это значения разных типов. Нужно сначала либо сделать строку числом, либо число строкой. Как это сделать, мы поговорим позже.
Такое педантичное отношение к совместимости типов называется строгой типизацией или сильной типизацией. Python — язык со строгой типизацией.
Не все языки так делают. Например, PHP — это язык со слабой типизацией. Он знает о существовании разных типов, но относится к их использованию не очень строго. PHP пытается преобразовывать информацию, когда это кажется разумным. То же самое относится к JavaScript:
// Как тебе такое, Илон Маск? // Число 1 + Строка 7 = Строка 17 1 + '7'; // '17'
С одной стороны, автоматическое неявное преобразование типов и правда кажется удобным. Но на практике это свойство языка создает множество ошибок и проблем, которые трудно найти. Код может иногда работать, а иногда не работать — в зависимости от того, «повезло» ли с автоматическим преобразованием. Программист это заметит не сразу и потратит много времени на отладку.
Задание
Выведите на экран результат выражения: 7 — (-8 — -2) . Попробуйте сделать число 7 не числом, а строкой. Сработает ли такой код? Поэкспериментируйте с другими числами тоже.
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Типы данных в Python для начинающих: какие бывают и как с ними работать
Готовимся к собеседованию на должность Python-разработчика. Знакомимся с системой типов в Python, встроенными типами данных и правилами работы с ними.


Иллюстрация: Оля Ежак для Skillbox Media

Антон Сёмин
Пишет об истории IT, разработке и советской кибернетике. Знает Python, JavaScript и немного C++, но предпочитает писать на русском.
Python — объектно-ориентированный язык программирования, его основу составляют объекты и классы. Объект — это область памяти компьютера, которая описывается типом (он же класс) и значением. При этом от типа зависит область значений объекта, операции и методы, которые к нему можно применять.
Python предоставляет богатый набор встроенных типов данных. Поэтому при решении стандартных задач питонист реже пишет собственные классы, чем, например, разработчик на Java.
Из этой статьи вы узнаете:
- что такое строгая динамическая типизация в Python;
- что такое изменяемые и неизменяемые типы данных;
- какие встроенные типы данных есть в Python;
- что почитать про типы данных в Python.
Что такое строгая динамическая типизация
Python — язык программирования со строгой динамической типизацией.
«Строгая» означает, что язык не производит неявные преобразования типов и не создаёт сюрпризов при их случайном смешении.
Чтобы понять, о чём идёт речь, запустите этот код на Python и JavaScript, а затем сравните результаты:

Изменяемые и неизменяемые типы данных
Типы данных в Python можно разделить на изменяемые и неизменяемые.
Когда мы присваиваем новое значение неизменяемому объекту, Python не перезаписывает его, а создаёт новый объект с тем же именем. Чтобы в этом убедиться, достаточно проверить id — уникальный номер, который присваивается каждому объекту в Python:
Строки
Строки (string) — это последовательности символов, поэтому к ним применимы многие методы других последовательностей: списков и кортежей. Например, обращение к элементу по индексу, вычисление количества символов, конкатенация и получение среза.
Рассмотрим основные операции со строками в Python:
Списки
Список (list) — это упорядоченная коллекция объектов. Списки могут иметь сколько угодно уровней вложенности и хранить неограниченное количество объектов. Кроме того, в одном списке могут одновременно храниться объекты разных типов.
Над списками можно производить те же операции, что и над строками:
Кортежи
Кортежи (tuple) — это те же списки, только неизменяемые. Над ними можно производить те же операции, что и над списками, — кроме тех, которые изменяют кортеж:

Что почитать про типы данных в Python
Теперь вы знаете о типах в Python достаточно, чтобы решать простейшие задачи и даже ответить на вопросы на собеседовании. Если хотите узнать о системе типов ещё больше — почитайте классические источники:
- «Изучаем Python 3», Часть II. Типы и операции, М. Лутц;
- «Программирование на Python 3», Глава 2. Типы данных, М. Саммерфилд;
- документацию Python, раздел «Built-in Types».
Больше интересного про код в нашем телеграм-канале. Подписывайтесь!
Читайте также:
- Где учить Python: 7 курсов, чтобы освоить язык с нуля и устроиться на работу
- Создаём первую игру на Python и Pygame
- Как работает Docker: подробный гайд от техлида
Числа, состоящие из действительной и мнимой части. Применяются в теории колебаний, квантовой механике и при обработке сигналов.
Курс
Python просто выучить, даже если вы никогда не программировали. Во время обучения вам будет помогать эксперт-куратор. Вы разработаете 3 проекта для портфолио, а Центр карьеры поможет найти работу Python-разработчиком.
Узнать про курс

Профессии с трудоустройством
- Графический дизайнер
- Python-программист
- Инженер по тестированию
- Бизнес-аналитик
- Интернет-маркетолог 2023
Как работать с типизацией в Python
Разбор основ типизации кода в Python и её роли в динамически-типизированном языке, который будет наиболее полезен новичкам в Python.
Первые упоминания о подсказках типов в языке программирования Python появились в базе Python Enhancement Proposals (PEP-483). Такие подсказки нужны для улучшения статического анализа кода и автодополнения редакторами, что помогает снизить риски появления багов в коде.
В этой статье мы рассмотрим основы типизации кода Python и ее роль в динамически-типизированном языке, эта информация будет наиболее полезна для начинающих Python-разработчиков.
Типизация в Python
Для обозначения базовых типов переменных используются сами типы:
Пример использования базовых типов в python-функции:
def func(a: int, b: float) -> str: a: str = f", " return a
Помимо этого, можно параметризировать более сложные типы, например, List . Такие типы могут принимать значения параметров, которые помогают более точно описать тип функции. Так, например, List[int] указывает на то, что список состоит только из целочисленных значений.
from typing import List def func(n: int) -> List[int]: return list(range(n))
Кроме List , существуют и другие типы из модуля typing, которые можно параметризировать. Такие типы называются Generic-типами. Такого рода типа определены для многих встроенных в Python структур данных:
- Set[x]
- FrozenSet[x]
- ByteString[x]
- Dict[x, y]
- DefaultDict[x, y]
- OrderedDict[x, y]
- ChainMap[x,y]
- Counter[x, int]
- Deque[x]
- и т.д.
Как можно заметить, некоторые типы имеют несколько параметров, которые можно описать. Например, Dict[x, y] означает, что это будет словарь, где ключи будут иметь тип x , а значения – тип y .
Также есть более абстрактные типы, например:
- Mapping[x, y] – объект имеет реализации метода __getitem__ ;
- Iterable[x] – объект имеет реализацию метода __iter__ .
При этом функции тоже имеют свои типы. Например, для описания функции можно использовать тип Callable , где указываются типы входных параметров и возвращаемых значений. Пример использования:
from typing import Callable def func(f: Callable[[int, int], bool]) -> bool: return f(1,2) func(lambda x, y: x == y) >>> False
- говорит о том, что у объекта реализован метод __call__ ;
- описывает типы параметров к этому методу.
На первом месте стоит массив типов входных параметров, на втором — тип возвращаемого значения.
Про остальные абстрактные типы контейнеров можно прочитать в документации Python.
Также есть более конкретные типы, например Literal[x] , где x указывает не тип, а конкретное значение. Например Literal[3] означает цифру 3. Используют такой тип крайне редко.
Также Python позволяет определять свои Generic-типы.
from typing import TypeVar, Generic T = TypeVar('T') class Stack(Generic[T]): def __init__(self) -> None: # Create an empty list with items of type T self.items: List[T] = [] def push(self, item: T) -> None: self.items.append(item) def pop(self) -> T: return self.items.pop() def empty(self) -> bool: return not self.items
В данном примере TypeVar означает переменную любого типа, которую можно подставить при указании. Например:
def func(stack: Stack[int]) -> None: stack.push(11) stack.push(-2) s = Stack[int]() func(s) s.empty() >>> False s.items >>> [11, -2]
Для определения собственных типов наследование возможно не только от Generic , но и от других абстрактных типов, например, таких, как Mapping , Iterable .
from typing import Generic, TypeVar, Mapping, Iterator, Dict KeyType = TypeVar('KeyType') ValueType = TypeVar('ValueType') class MyMap(Mapping[KeyType, ValueType]): # This is a generic subclass of Mapping def __getitem__(self, k: KeyType) -> ValueType: . # Implementations omitted def __iter__(self) -> Iterator[KeyType]: . def __len__(self) -> int: .
На месте KeyType или ValueType могут быть конкретные типы.
Также есть специальные конструкции, которые позволяют комбинировать типы. Например, Union[x, y, . ] — один из типов. Если переменной может быть как int , так и float , то как тип следует указать Union[int, float] . Если переменной может быть как int , так и None , то в качестве типа можно указать Union[int,None] или, что предпочтительно, Optional[int] .
Зачем это нужно
Цель — указать разработчику на ожидаемый тип данных при получении или возврате данных из функции или метода. В свою очередь, это позволяет сократить количество багов, ускорить написание кода и улучшить его качество.
Основные принципы программирования: статическая и динамическая типизация
Допустим, у вас есть класс юзера и функция, которая преобразует json в User .
from typing import Dict, Union, Optional from dataclasses import dataclass @dataclass class User: name: str surname: str age: int def get_user_from_json(json_dict: Dict[str, Optional[Union[int, str]]]) -> User: name = json_dict.get("name") surname = json_dict.get("surname") age = json_dict.get("age") if (age is None or name is None or surname is None): raise ValueError("Not enough information") return User(age=age, name=name, surname=surname)
Конечно, можно написать и проще:
def get_user_from_json(json_dict: Dict[str, Optional[Union[int, str]]]) -> User: return User(age=json_dict["age"], name=json_dict["name"], surname=json_dict["surname"])
Однако, в обоих случаях может возникнуть ошибка, если ключ age будет присутствовать и при этом иметь строковый тип. Валидация типов добавляет не очень много строк кода, но при большом количестве моделей может занимать немало места в проекте.
Использование Pydantic помогает корректно валидировать данные, при этом тип автоматически поменяется на требуемый.
from pydantic import BaseModel class User(BaseModel): name: str surname: str age: int def get_user_from_json(json_dict: Dict[str, Optional[Union[int, str]]]) -> User: return User(**json_dict) get_user_from_json(< "name": "ssa", "surname": "ddd", "age": 10 >) >>> User(name='ssa', surname='ddd', age=10) get_user_from_json(< "name": "ssa", "surname": "ddd", "age": "10" >) >>> User(name='ssa', surname='ddd', age=10) get_user_from_json(< "name": "ssa", "surname": "ddd", "age": "d" >) -------------------------------------- ValidationError: 1 validation error for User age value is not a valid integer (type=type_error.integer)
Как можно заметить, более строгая типизация кода помогает сделать его проще и безопаснее. Однако, использование некоторых возможностей Pydantic может нежелательно повлиять на код. Так, мутация данных при валидации способна привести к тому, что тип значения модели будет непонятен. Например:
from pydantic import BaseModel, validator class User(BaseModel): name: str age: int @validator('age') def validate_age(cls, value): if int(value) < 10: raise ValueError("too low") return str(value) User(name='Brian', age=33) >>> User(name='Brian', age='33')
В данном примере созданный User после валидации будет иметь отличный от того, который был указан в модели. Это ведет к возможным крупным багам, которые лучше всегда избегать.
Также сейчас набирает большую популярность фреймворк FastAPI, который, благодаря Pydantic, позволяет быстро писать веб-приложения с автоматической валидацией данных.
from fastapi import FastAPI from typing import Optional from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str price: float is_offer: Optional[bool] = None @app.put("/item") async def put_item(item: Item): return
В данном примере эндпоинт /item автоматически валидирует входящий json и передает его в функцию как требуемую модель.
Также для уменьшения количества багов используют mypy, который позволяет проводить статический анализ кода на соответствие типов. За счет этого зачастую можно избежать очевидных багов или несоответствий типов в функциях.
И как бонус для тех, кто ленится вручную поддерживать типизацию. MonkeyType дает возможность автоматически проставить типы во всех функциях, хотя после запуска этой программы обычно требуется пройтись по коду и поправить некоторые значения, которые оказались распознаны не так, как предполагалось.
Нововведения Python 3.9.0
Начиная с недавно вышедшей версии Python 3.9, у разработчиков больше нет необходимости импортировать абстрактные коллекции для описания типов. Теперь вместо typing.Dict[x, y] можно использовать dict[x,y] , то же самое происходит с Deque , List , Counter и т.д. Полное описание этого нововведения можно прочитать тут: PEP-585.
Также добавили аннотации типов, которые в дальнейшем могут быть использованы инструментами статического анализа. variable: Annotated[T, x] где T — тип переменной variable , а x — некоторые метаданные для переменной. По оценкам некоторых авторов, эти метаданные могут быть использованы также и во время выполнения (подробности смотрите в PEP-593).
Заключение
В этой статье мы рассмотрели некоторые типы в языке Python. В заключение отметим, что типизированный код в Python становится намного более читаемым и очевидным, что помогает проводить ревью в команде и не допускать глупых ошибок. Хорошее описание типов также позволяет разработчикам быстрее влиться в проект, понять, что происходит, и погрузиться в задачи. Также при использовании определенных библиотек удается в несколько раз сократить количество строк кода, которые ранее требовались только для валидации типов и значений.