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

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

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

bob = 1
bob = "bob"

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

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

Ответ 1

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, где типы полностью не пересекаются, и через классы типов возможна только контролируемая форма перегрузки).

Ответ 2

Есть некоторые важные проблемы, которые, как мне кажется, не пройдены.


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

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

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

intptr_t i = (intptr_t)&sz;

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

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

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

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

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

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

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

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


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

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

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

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


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

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

Итак, в этом смысле система типа Haskell сильнее современной Java, которая сильнее, чем предыдущая Java, которая сильнее C, которая сильнее, чем BCPL.

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Этот вопрос может представлять интерес: языки динамического типа по сравнению с языками статического типа и эту статью в Википедии о Системы типов предоставляет дополнительную информацию

Ответ 5

TL;DR;

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'

Ответ 6

Ответ уже несколько раз, но 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

Ответ 7

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

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

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

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

Ответ 8

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

Следовательно, использование термина зависит от того, с кем вы говорите.

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

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

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

Foo = 1 Foo = "1"

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

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

UINT64 kZarkCount;

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

Ответ 9

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

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

Java:

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

Ответ 10

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

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

Ответ 11

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....

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