Сильно набрано Python?


234

Я натрапив на посилання, які говорять, що Python є сильно набраною мовою.

Однак я подумав, що сильно набраними мовами ти цього не можеш зробити:

bob = 1
bob = "bob"

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

Отже, чи Python є сильно або слабо набраною мовою?

Відповіді:


358

Python сильно, динамічно набирається.

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

Що стосується вашого прикладу

bob = 1
bob = "bob"

Це працює, тому що змінна не має типу; він може назвати будь-який об’єкт. Після bob=1ви знайдете це type(bob)повернення int, але після bob="bob", воно повернеться str. (Зверніть увагу, що typeце звичайна функція, тому вона оцінює її аргумент, а потім повертає тип значення.)

Порівнюйте це зі старими діалектами С, які були слабко, статично типізовані, так що вказівники та цілі числа були в значній мірі взаємозамінними. (Сучасний ISO C вимагає перетворень у багатьох випадках, але моїй компілятор все ще поблажливий щодо цього.)

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

Міцність системи типів у такій динамічній мові, як Python, дійсно визначається тим, як її примітиви та функції бібліотеки реагують на різні типи. Напр., +Перевантажений таким чином, що він працює на два числа або два рядки, але не рядок і число. Це вибір дизайну, зроблений тоді, коли він +був реалізований, але насправді не є необхідністю, що випливає з мовної семантики. Насправді, перевантажуючи +тип користувача, ви можете змусити його неявно перетворювати що-небудь у число:

def to_number(x):
    """Try to convert function argument to float-type object."""
    try: 
        return float(x) 
    except (TypeError, ValueError): 
        return 0 

class Foo:
    def __init__(self, number): 
        self.number = number

    def __add__(self, other):
        return self.number + to_number(other)

Екземпляр класу Fooможна додати до інших об'єктів:

>>> a = Foo(42)
>>> a + "1"
43.0
>>> a + Foo
42
>>> a + 1
43.0
>>> a + None
42

Зауважимо , що навіть якщо сильно типізованих Python повністю добре з додаванням об'єктів типу intі floatі повертає об'єкт типу float(наприклад, int(42) + float(1)повертається 43.0). З іншого боку, через невідповідність між типами Haskell скаржиться, якщо спробувати наступне (42 :: Integer) + (1 :: Float). Це робить Haskell суворо типізованою мовою, де типи повністю непересічні, і лише керована форма перевантаження можлива через класи типів.


18
Один приклад, який я не бачу дуже часто, але я думаю, що важливо показати, що Python не є повністю сильно набраним, це все те, що оцінюється як булева: docs.python.org/release/2.5.2/lib/truth.html
gsingh2011

25
Не дуже впевнений, що це протилежний приклад: речі можуть оцінюватися як булеві, але вони раптом не стають булевими. Це майже так, як якщо б хтось неявно називав щось на зразок as_boolean (<значення>), що не те саме, що тип самого об'єкта, що змінюється, правда?
jbrendel

15
Бути правдою в булевому контексті не є контрприкладом, тому що насправді нічого не перетворюється на Trueабо False. А як щодо просування номера? 1.0 + 2працює так само добре в Python, як і в Perl або C, хоча "1.0" + 2це не так. Я погоджуюся з @jbrendel, що це насправді не неявна конверсія, це просто перевантаження, але в тому ж сенсі Perl теж не робить неявного перетворення. Якщо функції не мають оголошених типів параметрів, ніде не матиметься неявна конверсія.
abarnert

13
Кращим способом думати про сильне введення тексту є те, що тип має значення під час виконання операцій зі змінною. Якщо тип не такий, як очікувалося, мова, яка скаржиться, сильно набрана (python / java), та мова, яка не є слабко набраною (javascript). Динамічно набрані мови (python) - це ті, які дозволяють зміні типу змінної час виконання, тоді як статично набрані мови (java) не дозволяють цього, коли оголошується змінна.
kashif

2
@ gsingh2011 Істинність корисна і не слабка введення тексту самостійно, але випадкова if isValid(value) - 1може просочитися. Булева примушується до цілого числа, яке потім оцінюється як значення тритипу. False - 1стає неправдоподібним і хибним True - 1, що призводить до неприємно важкої двошарової помилки один за одним налагодження. У цьому сенсі пітон здебільшого сильно набирається; тип коерціонів зазвичай не викликає логічних помилок.
Aaron3468

57

Є кілька важливих питань, на які я думаю, що всі існуючі відповіді пропустили.


Слабке введення означає доступ до базового представлення. У 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, але багато з них все ще дещо пропускні. Наприклад, будь-яка мова OO, яка має неперевірений "downcast", * це тип витоку: ви, по суті, говорите компілятору: "Я знаю, я не дав вам достатньо інформації, щоб знати, що це безпечно, але я впевнений це "коли вся суть системи типу полягає в тому, що у компілятора завжди є достатньо інформації, щоб знати, що безпечно.

* Перевірений понижений режим не робить систему типу мови слабшою лише тому, що вона переміщує чек на час виконання. Якби це було, то підтиповий поліморфізм (він же віртуальний або повністю динамічний виклик функції) був би таким же порушенням типу системи, і я не думаю, що хтось хоче цього сказати.

Дуже мало мов «сценаріїв» слабкі в цьому сенсі. Навіть у Perl або Tcl ви не можете взяти рядок і просто інтерпретувати його байти як ціле число. * Але варто зазначити, що в CPython (і аналогічно для багатьох інших інтерпретаторів для багатьох мов), якщо ви справді стійкі, ви можна використовувати ctypesдля завантаження libpython, відкидання об'єкта idдо а POINTER(Py_Object)та примушування системи типу протікати. Незалежно від того, чи робить це тип типу слабким чи ні, залежить від ваших випадків використання - якщо ви намагаєтеся реалізувати пісочницю з обмеженим виконанням мовою для забезпечення безпеки, вам доведеться мати справу з цими видами втечі ...

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


Тим часом неявна конверсія - це справді інша річ від слабкої або нещільної системи типу.

Кожна мова, навіть Haskell, має функції для, скажімо, перетворення цілого числа в рядок або float. Але деякі мови виконають деякі з цих перетворень автоматично для вас, наприклад, в C, якщо ви викликаєте функцію, яка хоче a float, і ви передаєте її int, вона перетворюється за вас. Це безумовно може призвести до помилок, наприклад, з несподіваними переповненнями, але вони не є тими ж помилками, які ви отримуєте від слабкої системи. І тут насправді C не є слабкішим; ви можете додати int і float в Haskell, або навіть об'єднати 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, змусили вас використовувати Список об'єктів та знищені дані. Але принаймні Java дозволяє створювати нові типи власними методами; C дозволяє лише створювати структури. І у BCPL цього навіть не було. І так далі до монтажу, де єдині типи мають різну довжину бітів.

Отже, в цьому сенсі система типу Haskell сильніша за сучасну Java, яка сильніша за попередню Java, яка сильніша за C, яка сильніша за BCPL.

Отже, де Python вписується в цей спектр? Це трохи хитро. У багатьох випадках введення качок дозволяє моделювати все, що ви можете зробити в Haskell, і навіть деякі речі, які ви не можете; звичайно, помилки виявляються під час виконання замість часу компіляції, але вони все одно потрапляють. Однак бувають випадки, коли типи качок недостатньо. Наприклад, у Haskell ви можете сказати, що порожній список ints є списком ints, тож ви можете вирішити, що зменшення +над цим списком має повертати 0 *; у Python порожній список - порожній список; немає інформації про тип, яка допоможе вам вирішити, що зменшити +над цим.

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


3
Ця відповідь геніальна! Сором, що так довго нудить внизу списку.
Лев

1
Лише невеликий коментар до вашого прикладу C: char sz[]це не вказівник на char, це масив char, а в призначенні він розпадається на покажчик.
majkel.mk

39

Ви плутаєте "сильно набраний" з "динамічно набраним" .

Я не можу змінити тип 1, додавши рядок '12', але я можу вибрати, які типи я зберігатиму в змінній, і змінити їх під час виконання програми.

Протилежністю динамічному набору тексту є статичне введення тексту; декларації типів змінних не змінюється в протягом усього терміну програми. Протилежністю сильному набору тексту є слабке введення тексту; тип значень може змінюватися протягом життя програми.


Опис у посиланні сильно набрано: "Як правило, сильно набрана мова має більш жорсткі правила набору тексту під час компіляції, що означає, що помилки та винятки частіше трапляються під час компіляції." означає, що Python є слабко набраною мовою ...
Дощ

1
@ s̮̦̩e̝͓c̮͔̞ṛ̖̖e̬̣̦t̸͉̥̳̼: це зовсім не мається на увазі. У Python є строгі правила введення тексту під час компіляції, кожен створений об'єкт має лише один тип. І "загалом" нічого не означає, це просто означає, що Python є винятком з цього.
Martijn Pieters

24

Відповідно до цієї статті wiki Python Python одночасно динамічно та сильно набирається (також дає хороше пояснення).

Можливо, ви думаєте про статично набрані мови, де типи не можуть змінюватися під час виконання програми, а перевірка типу відбувається під час компіляції для виявлення можливих помилок.

Це питання ТА може бути цікавим: мови динамічного типу та мови статичного типу, і ця стаття Вікіпедії про тип системи надає додаткову інформацію


18

TLDR;

Типізація Python є динамічною, так що ви можете змінити рядкову змінну на int

x = 'somestring'
x = 50

Введення Python є сильним, тому ви не можете об'єднати типи:

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

У слабко набраному Javascript це відбувається ...

 'foo'+3 = 'foo3'

Щодо виводу типу

Java змушує явно заявляти про типи об'єктів

int x = 50

Котлін використовує умовивід, щоб зрозуміти, що цеint

x = 50

Але оскільки обидві мови використовують статичні типи, xїх не можна змінити з int. Жодна мова не дозволила б мати динамічну зміну на кшталт

x = 50
x = 'now a string'

Я не знаю деталей Javascript, але, 'x' + 3можливо, operator+перевантажує і робить перетворення типу поза сценою?
Дощ

3
У будь-якому випадку, ваша відповідь насправді більш лаконічна і зрозуміла, ніж наведена вище.
Дощ

8

На нього вже відповіли кілька разів, але 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


1
Через кілька років - ще один корисний і релевантний ресурс: youtu.be/_AEJHKGk9ns
Wayne Werner

Сильне та слабке введення тексту не має нічого спільного з результатом виразів типу 3 + '4'. JavaScript настільки ж сильний, як і Python для цього прикладу.
qznc

@qznc наскільки Javasript так само сильний? Я не вірю, що я натякав, що це не має нічого спільного з отриманим типом, я дійсно явно заявляю, що слабкі типи автоматично намагаються перетворити з одного типу в інший .
Вейн Вернер

2
@oneloop, що не обов'язково відповідає дійсності, це просто те, що поведінка для комбінування плавців та ints чітко визначена, і це призводить до поплавця. Ви можете робити і "3"*4в python. Результат, звичайно, є "3333". Ви б не сказали, що це перетворює будь-яку річ. Звичайно, це може бути просто аргументація семантики.
Уейн Вернер

1
@oneloop Не обов'язково правда, що тому, що Python виробляє floatз комбінації, floatі intщо це перетворює тип неявно. Існує природний зв’язок між float та int, і справді, тип герархії вимовляє це. Я припускаю, що ви можете стверджувати, що Javascript вважає '3'+4і 'e'+4обидві чітко визначені операції так само, як і Python вважає 3.0 + 4чітко визначеними, але в цей момент тоді насправді немає такого поняття, як сильні або слабкі типи, просто (не) визначені операції.
Уейн Вернер

6

Змінна Python зберігає нетипізовану посилання на цільовий об'єкт, який представляє значення.

Будь-яка операція присвоєння означає присвоєння нетипового посилання на призначений об'єкт - тобто об'єкт обмінюється за допомогою оригіналу та нових (лічильних) посилань.

Тип значення прив’язаний до цільового об'єкта, а не до еталонного значення. Перевірка типу (сильний) проводиться, коли виконується операція зі значенням (час виконання).

Іншими словами, змінні (в технічному плані) не мають типу - немає сенсу думати з точки зору типу змінної, якщо хочеться бути точним. Але посилання автоматично відкидаються, і ми насправді думаємо з точки зору типу цільового об’єкта.


6

Термін "сильний набір тексту" не має певного визначення.

Тому використання терміна залежить від того, з ким ви говорите.

Я не вважаю жодною мовою, в якій тип змінної не є явно оголошеним, або статично набраний як сильно набраний.

Сильне введення тексту не виключає перетворення (наприклад, "автоматичне" перетворення з цілого числа в рядок). Це виключає призначення (тобто зміну типу змінної).

Якщо наступний код компілюється (інтерпретує), мова не вводиться сильно:

Foo = 1 Foo = "1"

Сильно набраною мовою програміст може "розраховувати" на тип.

Наприклад, якщо програміст бачить декларацію,

UINT64 kZarkCount;

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


1

Щойно я виявив чудовий стислий спосіб запам'ятати його:

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


0

я думаю, цей простий приклад, якби ви пояснили відмінності між сильним та динамічним набором тексту:

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

Ваш код python демонструє динамічне введення тексту, тоді як java демонструє статичне введення тексту. Кращим прикладом може бути $ var = '2' + 1 // результат - 3
erichlf

@ivleph я згоден. також можна написати щось подібне: "a" * 3 == "aaa"
Дмитро Загорулкін

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.