Является ли Python строго типизированным?

Я сталкивался с ссылками, в которых говорится, что Python является строго типизированным языком.

Тем не менее, я думал, что в строго типизированных языках вы не можете сделать это:

bob = 1
bob = "bob"

Я думал, что строго типизированный язык не допускает изменения типа во время выполнения. Возможно, у меня неправильное (или слишком упрощенное) определение сильных / слабых типов.

Итак, является ли Python языком со строгой или слабой типизацией?

13 ответов

Решение

Python сильно, динамически типизирован.

  • Строгая типизация означает, что тип значения не меняется внезапно. Строка, содержащая только цифры, волшебным образом не становится числом, как в Perl. Каждое изменение типа требует явного преобразования.
  • Динамическая типизация означает, что объекты (значения) времени выполнения имеют тип, в отличие от статической типизации, где переменные имеют тип.

Что касается вашего примера

bob = 1
bob = "bob"

Это работает, потому что переменная не имеет типа; он может назвать любой объект. После bob=1вы найдете это type(bob) возвращается int, но после bob="bob", это возвращает str, (Обратите внимание, что type является обычной функцией, поэтому она оценивает свой аргумент, а затем возвращает тип значения.)

Сравните это с более старыми диалектами C, которые были слабо, статически типизированы, так что указатели и целые числа были в значительной степени взаимозаменяемы. (Современный ISO C требует преобразования во многих случаях, но мой компилятор по-прежнему снисходительно относится к этому.)

Я должен добавить, что сильная и слабая типизация - это скорее континуум, чем логический выбор. C++ имеет более сильную типизацию, чем C (требуется больше преобразований), но система типов может быть подорвана с помощью приведения указателей.

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

def to_number(x):
    """Try to convert x to a number."""
    if x is None:
        return 0
    # more special cases here
    else:
        return float(x)  # works for numbers and strings

class Foo(object):
    def __add__(self, other):
        other = to_number(other)
        # now do the addition

(Единственный язык, который я знаю, который полностью строго типизирован, или строго типизирован, - это Haskell, где типы полностью не пересекаются, и через классы типов возможна только контролируемая форма перегрузки).

Есть некоторые важные вопросы, которые, я думаю, все существующие ответы упустили.


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

char sz[] = "abcdefg";
int *i = (int *)sz;

На платформе с прямым порядком байтов с 32-разрядными целыми числами это делает i в массив чисел 0x64636261 а также 0x00676665, На самом деле, вы можете даже привести указатели к целым числам (соответствующего размера):

intptr_t i = (intptr_t)&sz;

И, конечно, это означает, что я могу перезаписывать память в любом месте системы.*

char *spam = (char *)0x12345678
spam[0] = 0;

* Конечно, современные ОС используют виртуальную память и защиту страниц, так что я могу перезаписывать только память своего собственного процесса, но нет ничего о самом C, который предлагает такую ​​защиту, как может сказать любой, кто когда-либо программировал на, скажем, Classic Mac OS или Win16.

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

Большинство языков сегодня не так слабы, как C и Lisp, но многие из них все еще несколько негерметичны. Например, любой ОО-язык, который имеет неконтролируемый "downcast",* это утечка типа: вы, по сути, говорите компилятору: "Я знаю, я не дал вам достаточно информации, чтобы знать, что это безопасно, но я уверен, что это так,"когда весь смысл системы типов заключается в том, что компилятор всегда имеет достаточно информации, чтобы знать, что безопасно.

* Проверенное снижение не делает систему типов языка более слабой только потому, что она переводит проверку во время выполнения. Если бы это было так, то полиморфизм подтипов (то есть виртуальные или полностью динамические вызовы функций) был бы таким же нарушением системы типов, и я не думаю, что кто-то хочет это сказать.

Очень немногие "скриптовые" языки в этом смысле слабы. Даже в Perl или Tcl вы не можете взять строку и просто интерпретировать ее байты как целое число.* Но стоит отметить, что в CPython (и аналогично для многих других интерпретаторов для многих языков), если вы действительно постоянны, вы можешь использовать ctypes загрузить libpython бросить объект id к POINTER(Py_Object)и заставить систему типов течь. Делает ли это систему типов слабой или нет, зависит от ваших вариантов использования - если вы пытаетесь внедрить изолированную программную среду с ограниченным исполнением на языке для обеспечения безопасности, вам придется иметь дело с этими видами побегов…

* Вы можете использовать функцию как struct.unpack прочитать байты и создать новый int из "как C будет представлять эти байты", но это явно не утечка; даже Haskell позволяет это.


Между тем, неявное преобразование действительно отличается от слабой или негерметичной системы типов.

Каждый язык, даже Haskell, имеет функции, скажем, для преобразования целого числа в строку или число с плавающей точкой. Но некоторые языки будут выполнять некоторые из этих преобразований автоматически - например, в C, если вы вызываете функцию, которая хочет float, и вы передаете это в int Он превращается для вас. Это может определенно привести к ошибкам, например, с неожиданными переполнениями, но это не те ошибки, которые вы получаете от слабой системы типов. И С на самом деле здесь не слабее; Вы можете добавить int и float в Haskell или даже объединить float в строку, вам просто нужно сделать это более явно.

А с динамическими языками это довольно мутно. В Python или Perl нет такой вещи, как "функция, которая хочет плавать". Но есть перегруженные функции, которые делают разные вещи с разными типами, и есть сильный интуитивный смысл, что, например, добавление строки к чему-то еще является "функцией, которая хочет строку". В этом смысле Perl, Tcl и JavaScript, по-видимому, выполняют много неявных преобразований ("a" + 1 дает тебе "a1"), в то время как Python делает намного меньше ("a" + 1 выдвигает исключение, но 1.0 + 1 дает вам 2.0 *). Просто трудно выразить этот смысл в формальных терминах - почему не должно быть + которая принимает строку и целое число, когда есть, очевидно, другие функции, такие как индексация, которые делают?

* На самом деле, в современном Python это можно объяснить с помощью подтипов ОО, так как isinstance(2, numbers.Real) правда. Я не думаю, что есть какой-то смысл, в котором 2 является экземпляром строкового типа в Perl или JavaScript... хотя в Tcl это действительно так, поскольку все является экземпляром строки.


Наконец, есть другое, совершенно ортогональное, определение "сильная" или "слабая" типизация, где "сильный" означает мощный / гибкий / выразительный.

Например, Haskell позволяет вам определить тип, который является числом, строкой, списком этого типа или картой из строк в этот тип, что является отличным способом для представления всего, что может быть декодировано из JSON. Нет способа определить такой тип в Java. Но по крайней мере в Java есть параметрические (универсальные) типы, так что вы можете написать функцию, которая принимает список T и знает, что элементы имеют тип T; другие языки, такие как ранняя Java, заставляли вас использовать List of Object и downcast. Но, по крайней мере, Java позволяет создавать новые типы с помощью собственных методов; С позволяет только создавать структуры. И у BCPL даже этого не было. И так далее до сборки, где единственные типы имеют разную длину в битах.

Таким образом, в этом смысле система типов Haskell сильнее, чем современная Java, которая сильнее, чем ранняя Java, которая сильнее, чем C, которая сильнее, чем BCPL.

Итак, где Python вписывается в этот спектр? Это немного сложно. Во многих случаях типирование утки позволяет имитировать все, что вы можете делать в Haskell, и даже некоторые вещи, которые вы не можете; Конечно, ошибки отлавливаются во время выполнения, а не во время компиляции, но они все еще отлавливаются Тем не менее, есть случаи, когда утки не достаточно. Например, в Haskell вы можете сказать, что пустой список целых является списком целых, так что вы можете решить, что сокращение + по этому списку должен возвращаться 0*; в Python пустой список является пустым списком; нет информации о типе, чтобы помочь вам решить, что сокращение + над этим надо делать.

* На самом деле, Haskell не позволяет вам сделать это; если вы вызываете функцию Reduce, которая не принимает начальное значение в пустом списке, вы получите ошибку. Но его система типов достаточно мощная, чтобы вы могли сделать это, а Python - нет.

TLDR;

Python печатает динамически, поэтому вы можете заменить переменную int на строку

x = 'somestring'
x = 50

Типизация Python является сильной, поэтому вы не можете объединять типы:

'x' + 3 --> TypeError: cannot concatenate 'str' and 'int' objects

В слабо типизированном Javascript это происходит...

 'x'+3 = 'x3'

Относительно вывода типа

Java заставляет вас явно объявлять ваши типы объектов

int x = 50

Котлин использует умозаключения, чтобы понять, что это int

x = 50

Но поскольку оба языка используют статические типы, x не может быть изменено с int, Ни один язык не позволил бы динамическое изменение как

x = 50
x = 'now a string'

Вы путаете "строго типизированный" с "динамически типизированным".

Я не могу изменить тип 1 добавив строку '12', но я могу выбрать, какие типы я храню в переменной, и изменить их во время выполнения программы.

Противоположностью динамической типизации является статическая типизация; объявление типов переменных не изменяется в течение жизни программы. Противоположностью строгой типизации является слабая типизация; тип значений может меняться в течение срока действия программы.

Согласно этой статье вики о Python, Python динамически и строго типизирован (также дает хорошее объяснение).

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

Этот вопрос SO может представлять интерес: языки с динамическим типом и языки со статическим типом, и эта статья в Википедии о системах типов предоставляет больше информации

На него уже отвечали несколько раз, но Python - это строго типизированный язык:

>>> x = 3
>>> y = '4'
>>> print(x+y)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Следующее в JavaScript:

var x = 3    
var y = '4'
alert(x + y) //Produces "34"

В этом разница между слабой и строгой типизацией. Слабые типы автоматически пытаются конвертировать из одного типа в другой, в зависимости от контекста (например, Perl). Сильные типы никогда не преобразуются неявно.

Ваша путаница заключается в неправильном понимании того, как Python связывает значения с именами (обычно называемыми переменными).

В Python имена не имеют типов, поэтому вы можете делать такие вещи, как:

bob = 1
bob = "bob"
bob = "An Ex-Parrot!"

И имена могут быть связаны с чем угодно:

>>> def spam():
...     print("Spam, spam, spam, spam")
...
>>> spam_on_eggs = spam
>>> spam_on_eggs()
Spam, spam, spam, spam

Для дальнейшего чтения:

https://en.wikipedia.org/wiki/Dynamic_dispatch

и немного связанный, но более продвинутый:

http://effbot.org/zone/call-by-object.htm

Термин "строгая типизация" не имеет определенного определения.

Поэтому использование этого термина зависит от того, с кем вы разговариваете.

Я не рассматриваю какой-либо язык, в котором тип переменной не объявлен явно или статически типизирован для строгой типизации.

Строгая типизация не только исключает преобразование (например, "автоматическое" преобразование из целого числа в строку). Это исключает присваивание (т. Е. Изменение типа переменной).

Если следующий код компилирует (интерпретирует), язык не является строго типизированным:

Foo = 1 Foo = "1"

В строго типизированном языке программист может "рассчитывать" на тип.

Например, если программист видит объявление,

UINT64 kZarkCount;

и он или она знает, что через 20 строк kZarkCount по-прежнему является UINT64 (если это происходит в одном и том же блоке) - без необходимости проверять промежуточный код.

Переменная Python хранит нетипизированную ссылку на целевой объект, который представляет значение.

Любая операция присваивания означает присвоение нетипизированной ссылки назначенному объекту, т. Е. Объект совместно используется посредством исходной и новой (подсчитанной) ссылки.

Тип значения привязан к целевому объекту, а не к контрольному значению. Проверка (строгого) типа выполняется, когда выполняется операция со значением (время выполнения).

Другими словами, переменные (технически) не имеют типа - нет смысла думать с точки зрения типа переменной, если кто-то хочет быть точным. Но ссылки автоматически разыменовываются, и мы на самом деле думаем о типе целевого объекта.

Я только что открыл для себя превосходный способ запомнить его:

Динамический / статический типизированный опыт; строго / слабо типизированное значение.

Я думаю, этот простой пример должен объяснить различия между строгой и динамической типизацией:

>>> tup = ('1', 1, .1)
>>> for item in tup:
...     type(item)
...
<type 'str'>
<type 'int'>
<type 'float'>
>>>

Джава:

public static void main(String[] args) {
        int i = 1;
        i = "1"; //will be error
        i = '0.1'; // will be error
    }

Python строго типизирован, потому что в нем нет непроверенных ошибок динамического типа. Другими словами, вы не можете нарушать его систему типов.

Определения

  • Тип: набор значений. Он частично определяет предполагаемое использование (поведение) своих значений. Он может быть указан в расширении с перечислением или в интенсионале с предикатом.
  • Система типов: система, проверяющая, используются ли значения по назначению, частично избегая неопределенного поведения .Следует избегать неопределенного поведения, поскольку оно может привести к позднему сбою программы или скрытой потере данных и получению неверных результатов.
  • Типизированный язык: язык с системой типов.
  • Ошибка типа: ошибка программы, которую можно проверить системой типов.
  • Выражение: текст программы, обозначающий значение.
  • Статический/динамический тип: тип выражения времени компиляции/времени выполнения. Тип выражения во время выполнения — это тип значения, которое оно обозначает.
  • Статическая/динамическая система типов: система типов, проверяющая статические/динамические типы.
  • Статически/динамически типизированный язык: язык со статической/динамической системой типов.
  • Ошибка статического/динамического типа: программная ошибка, которую можно проверить с помощью системы статического/динамического типа.
  • Слабо/строго типизированный язык: язык с/без непроверенных ошибок динамического типа. Статическая типизация, динамическая типизация или обе подразумевают строгую типизацию.
  • Мономорфное/полиморфное выражение: выражение, имеющее один динамический тип/несколько динамических типов. Мономорфное выражение имеет одно предполагаемое использование, а полиморфное выражение имеет множественное предполагаемое использование.
  • Универсальное/специальное полиморфное выражение: реальное/виртуальное полиморфное выражение. Реальное полиморфное выражение обозначает одно значение, которое имеет несколько типов, а виртуальное полиморфное выражение обозначает несколько значений, которые имеют один тип.
  • Параметрическое полиморфное выражение/включение: универсальное полиморфное выражение, основанное на универсальных типах/подтипах типа (например, выражение C++ &обозначает один T* (T&)значение оператора, где Tявляется универсальным типом/выражение C++ обозначает одно значение класса, где Sявляется общим подтипом std::exception).
  • Полиморфное выражение перегрузки/приведения: специальное полиморфное выражение, основанное на преобразовании выражение/значение (например, выражение C++ +обозначает int (int&, int&)а также float (float&, float&)значения оператора/выражение C++ 3.5обозначает floatа также boolценности).

Ссылка

Карделли (Лука), Вегнер (Питер), «О понимании типов, абстракции данных и полиморфизме», Computing Surveys, том 17, выпуск 4, 1985, с. 471-523, DOI: https://doi.org/10.1145/6041.6042.

Существующие ответы в основном согласны с тем, что Python является строго типизированным языком, поскольку он неявно не преобразует значения из одного типа в другой. В этих ответах упоминается случай добавления строки к целому числу для поддержки этого утверждения; "foo" + 3поднимает TypeErrorв Python, тогда как в Javascript (обычно считающемся языком со слабой типизацией) число 3неявно преобразуется в строку, а затем объединяется, поэтому результатом является строка "foo3".

Но есть и другие ситуации, когда Python выполняет неявное преобразование типов:

      # implicit conversion from int to float
1 + 1.0

# implicit conversion from list to bool
if []: pass

Для сравнения, F# (обычно считающийся строго типизированным языком) не допускает ни того, ни другого:

        1 + 1.0;;
  ----^^^
error FS0001: The type 'float' does not match the type 'int'

  if [] then 1 else 2;;
  ---^^
error FS0001: This expression was expected to have type bool but here has type 'a list  

Так что на самом деле нет строгой дихотомии между языками со строгой и слабой типизацией, скорее мы можем сказать, что Python более строго типизирован, чем Javascript, но не так строго типизирован, как F#.

class testme(object):
    ''' A test object '''
    def __init__(self):
        self.y = 0

def f(aTestMe1, aTestMe2):
    return aTestMe1.y + aTestMe2.y




c = testme            #get a variable to the class
c.x = 10              #add an attribute x inital value 10
c.y = 4               #change the default attribute value of y to 4

t = testme()          # declare t to be an instance object of testme
r = testme()          # declare r to be an instance object of testme

t.y = 6               # set t.y to a number
r.y = 7               # set r.y to a number

print(f(r,t))         # call function designed to operate on testme objects

r.y = "I am r.y"      # redefine r.y to be a string

print(f(r,t))         #POW!!!!  not good....

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

Другие вопросы по тегам