Что такое динамическая типизация питон
Как работать с типизацией в Python
Авторизуйтесь
Как работать с типизацией в Python
Первые упоминания о подсказках типов в языке программирования Python появились в базе Python Enhancement Proposals (PEP-483). Такие подсказки нужны для улучшения статического анализа кода и автодополнения редакторами, что помогает снизить риски появления багов в коде.
В этой статье мы рассмотрим основы типизации кода Python и ее роль в динамически-типизированном языке, эта информация будет наиболее полезна для начинающих Python-разработчиков.
Типизация в Python
Для обозначения базовых типов переменных используются сами типы:
Пример использования базовых типов в python-функции:
Также есть более абстрактные типы, например:
На первом месте стоит массив типов входных параметров, на втором — тип возвращаемого значения.
Про остальные абстрактные типы контейнеров можно прочитать в документации Python.
Также Python позволяет определять свои Generic-типы.
В данном примере TypeVar означает переменную любого типа, которую можно подставить при указании. Например:
На месте KeyType или ValueType могут быть конкретные типы.
Зачем это нужно
Цель — указать разработчику на ожидаемый тип данных при получении или возврате данных из функции или метода. В свою очередь, это позволяет сократить количество багов, ускорить написание кода и улучшить его качество.
Конечно, можно написать и проще:
Однако, в обоих случаях может возникнуть ошибка, если ключ age будет присутствовать и при этом иметь строковый тип. Валидация типов добавляет не очень много строк кода, но при большом количестве моделей может занимать немало места в проекте.
Использование Pydantic помогает корректно валидировать данные, при этом тип автоматически поменяется на требуемый.
Как можно заметить, более строгая типизация кода помогает сделать его проще и безопаснее. Однако, использование некоторых возможностей Pydantic может нежелательно повлиять на код. Так, мутация данных при валидации способна привести к тому, что тип значения модели будет непонятен. Например:
В данном примере созданный User после валидации будет иметь отличный от того, который был указан в модели. Это ведет к возможным крупным багам, которые лучше всегда избегать.
Также сейчас набирает большую популярность фреймворк FastAPI, который, благодаря Pydantic, позволяет быстро писать веб-приложения с автоматической валидацией данных.
В данном примере эндпоинт /item автоматически валидирует входящий json и передает его в функцию как требуемую модель.
Также для уменьшения количества багов используют mypy, который позволяет проводить статический анализ кода на соответствие типов. За счет этого зачастую можно избежать очевидных багов или несоответствий типов в функциях.
И как бонус для тех, кто ленится вручную поддерживать типизацию. MonkeyType дает возможность автоматически проставить типы во всех функциях, хотя после запуска этой программы обычно требуется пройтись по коду и поправить некоторые значения, которые оказались распознаны не так, как предполагалось.
Нововведения Python 3.9.0
Заключение
В этой статье мы рассмотрели некоторые типы в языке Python. В заключение отметим, что типизированный код в Python становится намного более читаемым и очевидным, что помогает проводить ревью в команде и не допускать глупых ошибок. Хорошее описание типов также позволяет разработчикам быстрее влиться в проект, понять, что происходит, и погрузиться в задачи. Также при использовании определенных библиотек удается в несколько раз сократить количество строк кода, которые ранее требовались только для валидации типов и значений.
Проверка типов данных и «утиная» типизация в Python
В этой статье мы вам расскажем о проверке типов, о различных типах данных в разных языках, а также о неявной типизации и подсказках.
В Python проверка типов выполняется в интерпретаторе. Так как Python — язык с динамической типизацией, он не заставляет пользователя принудительно указывать тип объектов. Это потенциально может привести к ошибкам, причем их будет трудно найти. Чтобы избежать этого, Python можно использовать вместе с другими инструментами и реализовывать проверки типов вместе с собственным алгоритмом неявной типизации.
Существует два метода типизации, за каждым из которых стоят определенные языки программирования:
Языки со статической типизацией
Проверка типа переменной выполняется во время компиляции. Кроме того, система типов языка заставляет явно объявлять «тип данных» переменной перед ее использованием.
Вот ряд языков программирования со статической типизацией: Scala, Java, C++ и так далее. Например, объявление переменной строкового типа в языке Scala выглядит следующим образом:
Языки с динамической типизацией
В этих языках проверка типа переменной выполняется во время выполнения. Кроме того, система типизации языка не требует явного объявления типа данных переменной перед ее использованием. К языкам программирования с динамической типизацией относятся Python, JavaScript, Ruby и так далее.
Например, переменная строкового типа в языке Python определяется следующим образом:
Здесь мы видим, что переменную myCar не нужно явно объявлять.
Функции type() и ‘isinstance() в Python
Приведенный выше код выдает в качестве результата ‘int’. Тип данных переменной my_var является целочисленным, и функция type() определяет его именно таким образом.
При помощи функции isinstance(‘ obj ‘,’ class ‘) в языке Python можно определить, является ли данный объект ( ‘obj’ ) экземпляром класса ( ‘class’ ). Возвращается булево значение ( True или False ).
Неявная («утиная») типизация в Python
В Python действует популярный принцип: «Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка». Попросту говоря, тип объекта или класса не имеет значения, но объект должен содержать аналогичные методы и свойства, тогда объект может использоваться для определенной цели.
Давайте разберем это на конкретном примере.
Результат выполнения данного кода будет следующим:
Подсказки типов и модуль mypy
У динамически типизированных языков, таких как Python, есть свои мощные преимущества, но есть и некоторые недостатки. Одним из недостатков является возникновение ошибок выполнения (runtime error) когда Python не производит принудительного преобразования типов. В результате могут возникать баги, которые с увеличением длины кода становится все трудней найти.
Подсказки типов реализованы в Python начиная с версии 3.5. А более старые версии могут не поддерживать данный функционал.
Давайте посмотрим простой пример без подсказок типов и модуля mypy.
Данная функция предназначена для вычитания целочисленных значений. Она принимает на вход два целых числа и возвращает их разность.
mypy — это модуль Python, который помогает в проверке статических типов. Он использует собственную динамическую проверку Python или неявную («утиную») типизацию с подсказкой самого типа.
Для начала вам нужно установить сам модуль mypy:
Далее вам нужно создать файл с именем mypy_example.py на своем локальном компьютере и сохранить туда следующий код:
Это простая программа, которая принимает два целых числа в качестве входных данных в параметре, а после ‘->’ показывает тип возвращаемых данных, который также является целочисленным (‘int’). Но хотя функция должна возвращать целочисленное значение (int), возвращается строка ‘Subtracted two integers’.
Запустите указанный выше код в терминале следующим образом:
После этого будет показана ошибка, указывающая на несоответствие типов (должен быть ‘int», а выдается ‘str’).
Давайте теперь изменим тип возвращаемого значения. Заменим строковое значение на разницу двух целых чисел. Таким образом, будет возвращаться целочисленное значение.
Мы видим, что программа выполняется успешно, никаких проблем не обнаружено.
Поздравляем!
Ликбез по типизации в языках программирования
Эта статья содержит необходимый минимум тех вещей, которые просто необходимо знать о типизации, чтобы не называть динамическую типизацию злом, Lisp — бестиповым языком, а C — языком со строгой типизацией.
В полной версии находится подробное описание всех видов типизации, приправленное примерами кода, ссылками на популярные языки программирования и показательными картинками.
Рекомендую прочитать сначала краткую версию статьи, а затем при наличии желания и полную.
Краткая версия
Языки программирования по типизации принято делить на два больших лагеря — типизированные и нетипизированные (бестиповые). К первому например относятся C, Python, Scala, PHP и Lua, а ко второму — язык ассемблера, Forth и Brainfuck.
Так как «бестиповая типизация» по своей сути — проста как пробка, дальше она ни на какие другие виды не делится. А вот типизированные языки разделяются еще на несколько пересекающихся категорий:
Примеры:
Статическая: C, Java, C#;
Динамическая: Python, JavaScript, Ruby.
Примеры:
Сильная: Java, Python, Haskell, Lisp;
Слабая: C, JavaScript, Visual Basic, PHP.
Примеры:
Явная: C++, D, C#
Неявная: PHP, Lua, JavaScript
Также нужно заметить, что все эти категории пересекаются, например язык C имеет статическую слабую явную типизацию, а язык Python — динамическую сильную неявную.
Тем-не менее не бывает языков со статической и динамической типизаций одновременно. Хотя забегая вперед скажу, что тут я вру — они действительно существуют, но об этом позже.
Подробная версия
Если краткой версии Вам показалось недостаточно, хорошо. Не зря же я писал подробную? Главное, что в краткой версии просто невозможно было уместить всю полезную и интересную информацию, а подробная будет возможно слишком длинной, чтобы каждый смог ее прочесть, не напрягаясь.
Бестиповая типизация
В бестиповых языках программирования — все сущности считаются просто последовательностями бит, различной длины.
Бестиповая типизация обычно присуща низкоуровневым (язык ассемблера, Forth) и эзотерическим (Brainfuck, HQ9, Piet) языкам. Однако и у нее, наряду с недостатками, есть некоторые преимущества.
Преимущества
Недостатки
Сильная безтиповая типизация?
Да, такое существует. Например в языке ассемблера (для архитектуры х86/х86-64, других не знаю) нельзя ассемблировать программу, если вы попытаетесь загрузить в регистр cx (16 бит) данные из регистра rax (64 бита).
mov cx, eax ; ошибка времени ассемблирования
Так получается, что в ассемлере все-таки есть типизация? Я считаю, что этих проверок недостаточно. А Ваше мнение, конечно, зависит только от Вас.
Статическая и динамическая типизации
Главное, что отличает статическую (static) типизацию от динамической (dynamic) то, что все проверки типов выполняются на этапе компиляции, а не этапе выполнения.
Некоторым людям может показаться, что статическая типизация слишком ограничена (на самом деле так и есть, но от этого давно избавились с помощью некоторых методик). Некоторым же, что динамически типизированные языки — это игра с огнем, но какие же черты их выделяют? Неужели оба вида имеют шансы на существование? Если нет, то почему много как статически, так и динамически типизированных языков?
Преимущества статической типизации
Преимущества динамической типизации
Обобщенное программирование
Хорошо, самый важный аргумент за динамическую типизацию — удобство описания обобщенных алгоритмов. Давайте представим себе проблему — нам нужна функция поиска по нескольким массивам (или спискам) — по массиву целых чисел, по массиву вещественных и массиву символов.
Как же мы будем ее решать? Решим ее на 3-ех разных языках: одном с динамической типизацией и двух со статической.
Алгоритм поиска я возьму один из простейших — перебор. Функция будет получать искомый элемент, сам массив (или список) и возвращать индекс элемента, или, если элемент не найден — (-1).
Динамическое решение (Python):
Как видите, все просто и никаких проблем с тем, что список может содержать хоть числа, хоть списки, хоть другие массивы нет. Очень хорошо. Давайте пойдем дальше — решим эту-же задачу на Си!
Статическое решение (Си):
Ну, каждая функция в отдельности похожа на версию из Python, но почему их три? Неужели статическое программирование проиграло?
И да, и нет. Есть несколько методик программирования, одну из которых мы сейчас рассмотрим. Она называется обобщенное программирование и язык C++ ее неплохо поддерживает. Давайте посмотрим на новую версию:
Статическое решение (обобщенное программирование, C++):
Хорошо! Это выглядит не сильно сложнее чем версия на Python и при этом не пришлось много писать. Вдобавок мы получили реализацию для всех массивов, а не только для 3-ех, необходимых для решения задачи!
Эта версия похоже именно то, что нужно — мы получаем одновременно плюсы статической типизации и некоторые плюсы динамической.
Здорово, что это вообще возможно, но может быть еще лучше. Во-первых обобщенное программирование может быть удобнее и красивее (например в языке Haskell). Во-вторых помимо обобщенного программирования также можно применить полиморфизм (результат будет хуже), перегрузку функций (аналогично) или макросы.
Статика в динамике
Также нужно упомянуть, что многие статические языки позволяют использовать динамическую типизацию, например:
Сильная и слабая типизации
Языки с сильной типизацией не позволяют смешивать сущности разных типов в выражениях и не выполняют никаких автоматических преобразований. Также их называют «языки с строгой типизацией». Английский термин для этого — strong typing.
Слабо типизированные языки, наоборот всячески способствуют, чтобы программист смешивал разные типы в одном выражении, причем компилятор сам приведет все к единому типу. Также их называют «языки с нестрогой типизацией». Английский термин для этого — weak typing.
Слабую типизацию часто путают с динамической, что совершенно неверно. Динамически типизированный язык может быть и слабо и сильно типизирован.
Однако мало, кто придает значение строгости типизации. Часто заявляют, что если язык статически типизирован, то Вы сможете отловить множество потенциальных ошибок при компиляции. Они Вам врут!
Язык при этом должен иметь еще и сильную типизацию. И правда, если компилятор вместо сообщения об ошибке будет просто прибавлять строку к числу, или что еще хуже, вычтет из одного массива другой, какой нам толк, что все «проверки» типов будут на этапе компиляции? Правильно — слабая статическая типизация еще хуже, чем сильная динамическая! (Ну, это мое мнение)
Так что-же у слабой типизации вообще нет плюсов? Возможно так выглядит, однако несмотря на то, что я ярый сторонник сильной типизации, должен согласиться, что у слабой тоже есть преимущества.
Хотите узнать какие?
Преимущества сильной типизации
Преимущества слабой типизации
Оказывается есть и даже два.
Неявное приведение типов, в однозначных ситуациях и без потерь данных
Ух… Довольно длинный пункт. Давайте я буду дальше сокращать его до «ограниченное неявное преобразование» Так что же значит однозначная ситуация и потери данных?
Однозначная ситуация, это преобразование или операция в которой сущность сразу понятна. Вот например сложение двух чисел — однозначная ситуация. А преобразование числа в массив — нет (возможно создастся массив из одного элемента, возможно массив, с такой длинной, заполненный элементами по-умолчанию, а возможно число преобразуется в строку, а затем в массив символов).
Потеря данных это еще проще. Если мы преобразуем вещественное число 3.5 в целое — мы потеряем часть данных (на самом деле эта операция еще и неоднозначная — как будет производиться округление? В большую сторону? В меньшую? Отбрасывание дробной части?).
Преобразования в неоднозначных ситуациях и преобразования с потерей данных — это очень, очень плохо. Ничего хуже этого в программировании нет.
Если вы мне не верите, изучите язык PL/I или даже просто поищите его спецификацию. В нем есть правила преобразования между ВСЕМИ типами данных! Это просто ад!
Ладно, давайте вспомним про ограниченное неявное преобразование. Есть ли такие языки? Да, например в Pascal Вы можете преобразовать целое число в вещественное, но не наоборот. Также похожие механизмы есть в C#, Groovy и Common Lisp.
Ладно, я говорил, что есть еще способ получить пару плюсов слабой типизации в сильном языке. И да, он есть и называется полиморфизм конструкторов.
Я поясню его на примере замечательного языка Haskell.
Полиморфные конструкторы появились в результате наблюдения, что чаще всего безопасные неявные преобразования нужны при использовании числовых литералов.
И это сделано в Haskell, благодаря тому, что у литерала 1 нет конкретного типа. Это ни целое, ни вещественное, ни комплексное. Это же просто число!
Конечно спасает этот прием только при использовании смешанных выражений с числовыми литералами, а это лишь верхушка айсберга.
Таким образом можно сказать, что лучшим выходом будет балансирование на грани, между сильной и слабой типизацией. Но пока идеальный баланс не держит ни один язык, поэтому я больше склоняюсь к сильно типизированным языкам (таким как Haskell, Java, C#, Python), а не к слабо типизированным (таким как C, JavaScript, Lua, PHP).
Ладно, пойдем дальше?
Явная и неявная типизации
Язык с явной типизацией предполагает, что программист должен указывать типы всех переменных и функций, которые объявляет. Английский термин для этого — explicit typing.
Язык с неявной типизацией, напротив, предлагает Вам забыть о типах и переложить задачу вывода типов на компилятор или интерпретатор. Английски термин для этого — implicit typing.
По-началу можно решить, что неявная типизация равносильна динамической, а явная — статической, но дальше мы увидим, что это не так.
Есть ли плюсы у каждого вида, и опять же, есть ли их комбинации и есть ли языки с поддержкой обоих методов?
Преимущества явной типизации
Преимущества неявной типизации
Явная типизация по-выбору
Есть языки, с неявной типизацией по-умолчанию и возможностью указать тип значений при необходимости. Настоящий тип выражения транслятор выведет автоматически. Один из таких языков — Haskell, давайте я приведу простой пример, для наглядности:
* Спасибо int_index за нахождение ошибки.
Хм. Как мы видим, это очень красиво и коротко. Запись функции занимает всего 18 символов на одной строчке, включая пробелы!
Однако автоматический вывод типов довольно сложная вещь, и даже в таком крутом языке как Haskell, он иногда не справляется. (как пример можно привести ограничение мономорфизма)
Есть ли языки с явной типизацией по-умолчанию и неявной по-необходимости? Кон
ечно.
Неявная типизация по-выбору
В новом стандарте языка C++, названном C++11 (ранее назывался C++0x), было введено ключевое слово auto, благодаря которому можно заставить компилятор вывести тип, исходя из контекста:
Неплохо. Но запись сократилась не сильно. Давайте посмотрим пример с итераторами (если не понимаете, не бойтесь, главное заметьте, что запись благодаря автоматическому выводу очень сильно сокращается):
Ух ты! Вот это сокращение. Ладно, но можно ли сделать что-нибудь в духе Haskell, где тип возвращаемого значения будет зависеть от типов аргументов?
И опять ответ да, благодаря ключевому слову decltype в комбинации с auto:
Может показаться, что эта форма записи не сильно хороша, но в комбинации с обобщенным программированием (templates / generics) неявная типизация или автоматический вывод типов творят чудеса.
Некоторые языки программирования по данной классификации
Я приведу небольшой список из популярных языков и напишу как они подразделяются по каждой категории “типизаций”.
Возможно я где-то ошибся, особенно с CL, PHP и Obj-C, если по какому-то языку у Вас другое мнение — напишите в комментариях.
Заключение
Окей. Уже скоро будет светло и я чувствую, что про типизацию больше нечего сказать. Ой как? Тема бездонная? Очень много осталось недосказано? Прошу в комментарии, поделитесь полезной информацией.
Ликбез по типизации в языках программирования
В данной статье содержится весь необходимый багаж информации, который нужно знать о типизации, для того чтобы динамическая типизация не показалась вам сущим злом.
В развернутой вариации этой статьи расположено детальное отображение всех форм типизации, дополненное образцами кода, а также ссылками на известные всеми языки программирования и наглядными картинками
Советую изначально ознакомиться с сокращенным вариантом статьи, а после при желании приступить к развёрнутой версии.
Сокращенная вариация
Обычно языки программирования по типизации делят на две крупные стороны: типизированные и нетипизированные (бестиповые). К первой стороне можно отнести C, Python, Scala, PHP и Lua, а ко второй язык ассемблера, Forth и Brainfuck.
Поскольку «бестиповая типизация» в сущности – чрезмерно проста, то в дальнейшем она не разделяется на другие типы. Вместо этого типизированные языки представляют собой ещё несколько видов:
Примеры:
Статическая: C, Java, C#;
Динамическая: Python, JavaScript, Ruby.
Примеры:
Сильная: Java, Python, Haskell, Lisp;
Слабая: C, JavaScript, Visual Basic, PHP.
Явная типизация – языки данной типизации разнятся тем, что тип новых переменных / функций / их доводов необходимо задавать явно.
Неявная типизация – языки этой типизации переносят эту задачу на компилятор / интерпретатор.
Примеры:
Неявная: PHP, Lua, JavaScript
По крайней мере, не может быть языков со статической и динамической типизаций одновременно. Однако опережая события, сообщу, что в этом моменте я вру, на самом деле они есть, но об этом чуть позже.
Развернутая вариация
В том случае если информации в сокращенной вариации Вам оказалось мало, то это хорошо. Так как для этого я и написал развёрнутую версию. Суть в том, что в сокращенную вариацию нереально было вместить весь полезный и занимательный материал, развернутая, скорее всего, получится чересчур длинная, не каждый сможет её осилить не напрягаясь.
Бестиповая типизация
В бестиповых языках программирования – все сущности являются обычными очередностями бит, разной длины.
Такая типизация свойственна низкоуровневым (язык ассемблера, Forth) и эзотерическим (Brainfuck, HQ9, Piet) языкам. Но и у неё наравне с недочётами, присутствует перечень достоинств.
Достоинства
Недостатки
Сильная бестиповая типизация?
Да, такое наблюдается. К примеру, в языке ассемблера (для архитектуры х86/х86-64, других не знаю) не следует ассемблировать программу, в случае если вы попробуете загрузить в регистр cx (16 бит) данные из регистра rax (64 бита).
mov cx, eax ; ошибка времени ассемблирования.
То есть выходит, что в ассемблере все-таки есть типизация? Я думаю, что количество подобных проверок неудовлетворительно. Но Ваше мнение зависит, исключительно, только от Вас.
Статическая и динамическая типизации
Важно, что статическую (static) типизацию отличает от динамической (dynamic) именно то, что все проверки видов происходят на этапе сбора данных, а не на этапе осуществления.
Некоторые люди считают, что статическая типизация излишне ограничена (на самом деле так и есть, но от этого давно избавились при помощи неких методик). Некие же думают, что динамически типизированные языки — это рискованные действия, но какие особенности их отличают? Неужели оба вида имеют шансы на существование? Если нет, то почему много как статически, так и динамически типизированных языков?
Давайте разберемся.
Преимущества статической типизации
Проверки типов осуществляются исключительно один раз — на этапе сбора данных. А это означает, что нам не требуется постоянно определять, не пытаемся ли мы поделить число на строку (и либо выдать ошибку, либо осуществить преобразование).
Скорость проведения. Из предыдущего пункта ясно, что статически типизированные языки чаще всего быстрее динамически типизированных.
При неких дополнительных условиях, предоставляется возможность выявлять потенциальные ошибки уже на этапе компиляции.
Ускорение разработки при поддержке IDE (отсеивание вариантов, заведомо не подходящих по типу).
Преимущества динамической типизации
Обобщенное программирование
Главный аргумент за динамическую типизацию – комфортность изложения обобщенных алгоритмов. Предлагаю представить задачу – нам потребуется функция поиска по некоторым спискам – по массиву целых чисел, по массиву вещественных и массиву символов.
Каким же образом мы решим такую задачу? С помощью 3-ех различных языков: один с динамической типизацией и два других со статической.
Алгоритм поиска будет один из элементарных – перебор. Функция получает искомый элемент, сам массив (или список) и впоследствии будет возвращать индекс элемента, или, если элемент не найден – (-1).
Динамическое решение (Python):
Как вы можете видеть, все элементарно и никаких проблем с тем, что список может содержать как числа, списки, либо другие массивы нет. Отлично. А значит, следуем дальше и решим эту же задачу теперь на Си!
Статическое решение (Си):
Допустим, любая функция индивидуально сходна версии из Python, но отчего же их три? Неужто статическое программирование проиграло?
Нельзя дать однозначный ответ. Исходя из того, что есть множество технологий программирования, одну из них мы сейчас обсудим. Называется она обобщенное программирование, а язык C++ ее неплохо сохраняет. Рассмотрим новую вариацию:
Статическое решение (обобщенное программирование, C++):
Хорошо! Такое решение выглядит не намного сложнее, чем вариант на Python, а писать пришлось гораздо больше. Дополнительно мы получаем реализацию для всех массивов, а не только для 3-ех, которые необходимы для решения задачи!
Данный вариант именно тот, который нужен, помимо того, что мы получаем плюсы статической типизации, дополнительно у нас есть плюсы динамической.
Радует, что такое можно реализовать, но всегда есть возможность сделать ещё лучше. Во-первых, обобщенное программирование можно сделать гораздо удобнее и привлекательней как в языке Haskell. Во-вторых, кроме обобщенного программирования можно использовать полиморфизм (результат будет хуже), перегрузку функций (аналогично) или макросы.
Статика в динамике
Кроме того следует отметить, что большинство статических языков предоставляют возможность применение динамической типизации, к примеру:
C# поддерживает псевдо-тип dynamic.
Haskell — динамическая типизация обеспечивается модулем Data.Dynamic.
Delphi — посредством специального типа Variant.
Кроме того, некие динамически типизированные языки дают возможность использовать преимущества статической типизации:
Common Lisp — декларации типов.
Perl — с версии 5.6, довольно ограниченно.
Итак, идем дальше?
Сильная и слабая типизации
Языки с сильной типизацией не дают сочетать сущности разных типов в выражениях и осуществлять какие-либо автоматические преобразования. Их называют «языки со строгой типизацией», с английского «strong typing».
Слабо типизированные языки, напротив максимально содействуют для того, чтобы программист сочетал различные виды в одном выражении, более того компилятор сам приведет все к единому виду. Их называют «языки с нестрогой типизацией», с английского «weak typing».
Зачастую слабую типизацию могут путать с динамической, что абсолютно неправильно. Динамически типизированный язык может быть и слабо и сильно типизирован.
Впрочем мало, кто обращает внимание на строгость типизации. Неоднократно утверждают о том, что если язык статически типизирован, то Вы можете уловить большое количество потенциальных ошибок при компиляции. Они определённо Вам врут!
Исходя из этого, язык должен иметь сильную типизацию. А если компилятор вместо сообщения об ошибке будет просто прибавлять строку к числу, или что еще хуже, вычтет из одного массива другой, какой нам толк с того, что все «проверки» типов будут на этапе компиляции? Слабая статическая типизация еще хуже, чем сильная динамическая! (Ну, это мое мнение).
Исходя из этого, можно сделать вывод, что у слабой типизации вообще нет плюсов? вероятно так и выглядит, но вопреки тому, что я ярый сторонник сильной типизации, не могу не согласиться, что у слабой тоже есть свои преимущества.
Хотите узнать какие? Тогда продолжим.
Достоинства сильной типизации
Преимущества слабой типизации
Теперь, мы разобрались, что и у слабой типизации тоже есть преимущества! А есть ли методы переместить плюсы слабой типизации в сильную?
Оказывается, есть и даже два.
Неявное приведение типов, в конкретных ситуациях и без потерь данных.
Ух… Довольно длинный пункт. Предлагаю, дальше сокращать его до «ограниченное неявное преобразование». Так что же значит определённая ситуация и потери данных?
Определённая ситуация – это трансформация или операция, в которой сущность сразу понятна. К примеру, сложение двух чисел – определённая ситуация. А изменение числа в массив — нет (может, создастся массив из одного элемента, а возможно число трансформируется в строку, а после в массив символов).
Потеря данных это еще элементарней. Если мы изменим вещественное число 3.5 в целое, мы можем потерять часть данных (по сути, такая операция абсолютно неопределённая – как будет производиться округление? В большую сторону? В меньшую? Отбрасывать дробную часть?).
Трансформация в неопределённых ситуациях и трансформация с потерей данных – это крайне плохо. Ничего хуже этого в программировании нет.
Если вы мне не можете поверить в это то, изучите язык PL/I или просто поищите его спецификацию. В нем есть правила преобразования между ВСЕМИ типами данных!
Предлагаю вспомнить про ограниченное неявное изменение. Есть ли такие языки? Да, к примеру, в Pascal. Вы сможете изменить целое число в вещественное, но не противоположно. Кроме этого подобные механизмы есть в C#, Groovy и Common Lisp.
Я говорил, что есть еще метод получить пару плюсов слабой типизации в сильном языке. Он называется полиморфизм конструкторов.
Я покажу его на примере прекрасного языка Haskell.
Полиморфные конструкторы образовались в итоге наблюдения, что чаще всего безопасные неявные изменения требуются в момент использования числовых литералов.
К примеру, в выражении pi + 1, не хочется писать pi + 1.0 или pi + float(1). Хочется написать просто pi + 1!
И это разработано в Haskell, по причине того, что у литерала 1 нет точного вида. Это ни единое, ни материальное, ни всестороннее. Это же просто число!
Безусловно, такой приём поможет только в момент использования смешанных выражений с числовыми литералами, а это лишь начало.
Исходя из этого, можно сделать вывод о том, что наилучшим исходом будет соблюдение баланса, находясь на грани, между сильной и слабой типизацией. Но на данный момент совершенный баланс не соблюдает ни один язык, поэтому я склоняюсь к сильно типизированным языкам (таким как Haskell, Java, C#, Python), а не к слабо типизированным (таким как C, JavaScript, Lua, PHP).
Ладно, пойдем дальше?
Явная и неявная типизации
Язык с явной типизацией планирует, что программист будет отмечать типы всех переменных и функций, которые заявляет. С английского он означает «explicit typing».
Язык с неявной типизацией действует, наоборот, предлагая Вам не задумываться о типах и перевести подобную функцию на компилятор или интерпретатор. С английского он означает «implicit typing».
Изначально, кажется, что неявная типизация равна динамической, а явная – статической, но в дальнейшем мы поймём, что это неверно.
Хотелось бы знать есть ли плюсы у того и другого вида, а есть ли их комбинации и языки с поддержкой тех и других методов?
Преимущества явной типизации
Существование у той или иной функции сигнатуры (к примеру, int add(int, int)) даёт возможность без проблем выяснить, что осуществляет функция.
Программист в момент делает записи о том, какой тип значения содержится в определённой переменной, для того чтобы не запоминать это.
Преимущества неявной типизации
Уменьшение записи – def add (x, y) определённо короче, чем int add( int x, int y).
Стойкость к переменам. К примеру, если в функции временная переменная была одинакового типа, что и входной аргумент, то в явно типизированном языке при преобразовании типа входного аргумента требуется поменять еще и тип временной переменной.
Отчётливо видно, что оба подхода обладают как плюсами, так и минусами, а теперь предлагаю найти способы комбинирования этих двух подходов!
Явная типизация по выбору
Существуют языки, с неявной типизацией по умолчанию, а также с функцией указания типа значений, когда будет необходимо. Настоящий тип выражения транслятор выведет автоматически. Haskell является таким языком. Для того чтобы было более понятно приведу такой пример:
Как мы можем заметить, это крайне коротко и красиво выглядит. Вся функция занимает всего 18 символов на одной строчке, в том числе и пробелы!
Но автоматический вывод типов достаточно сложная вещь, и даже в таком крутом языке как Haskell, он периодически не справляется. (для наглядности можно показать ограничение мономорфизма)
Есть ли языки с явной типизацией по умолчанию и неявной по необходимости? Конечно.
Неявная типизация по-выбору
В новом нормативе языка C++, названном C++11 (ранее назывался C++0x), было введено ключевое слово auto, с помощью которого есть возможность принудить компилятор вывести тип, исходя из контекста:
Нормально. Однако запись сжалась минимально. Предлагаю рассмотреть пример с итераторами (если не понимаете, не беда, главное обратите внимание, как сократилась запись, благодаря автоматическому выводу):
Ого, вот это сокращение! Хорошо, но есть ли возможность создать что-то подобное в Haskell, где тип возвращаемого значения будет зависеть от типов аргументов?
И в очередной раз я отвечу, да. При помощи ключевого слова decltype в объединении с auto:
Может возникнуть ощущение, что эта форма записи не достаточно хороша, но в комбинации с обобщенным программированием (templates / generics) неявная типизация или автоматический вывод типов творят невообразимое.
Некоторые языки программирования по данной классификации
Я предоставлю вам некоторый перечень популярных языков и распишу их классификацию по каждой группе «типизации».
Примечания к таблице:
C# – сохраняет динамическую типизацию, с помощью особого псевдо-типа dynamic с версии 4.0. А также оказывает содействие неявной типизации посредством dynamic и var.
С++ – после стандарта C++11 обрёл опору неявной типизации посредством ключевых слов auto и decltype. Сохраняет динамическую типизацию, в момент использования библиотеки Boost (boost::any, boost::variant). Содержит в себе черты как сильной, так и слабой типизации.
Common Lisp – стандарт предполагает декларации типов, благодаря которым некие реализации используются также для статической проверки типов.
D – также осуществляет поддержание неявной типизации.
Delphi – осуществляет поддержку динамической типизации, с помощью особого типа Variant.
Есть вероятность, что я где-то мог ошибиться, в особенности с CL, PHP и Obj-C, если по какому-либо языку у Вас есть другое мнение – пишите в комментариях.
Заключение
Совсем скоро будет светать и мне кажется, что про типизацию я больше ничего не скажу. Ой, как? Эта же тема бездонная? Много чего недосказано? Предлагаю всю полезную информацию по этой теме высказать в комментариях.