Czy Python jest mocno napisany?

234

Natknąłem się na linki, które mówią, że Python jest silnie pisanym językiem.

Myślałem jednak, że w silnie pisanych językach nie możesz tego zrobić:

bob = 1
bob = "bob"

Myślałem, że mocno napisany język nie akceptuje zmiany typów w czasie wykonywania. Może mam błędną (lub zbyt uproszczoną) definicję typów silnych / słabych.

Czy więc Python jest silnie lub słabo napisanym językiem?

Pacane
źródło

Odpowiedzi:

358

Python jest silnie, dynamicznie pisany.

  • Silne pisanie oznacza, że ​​typ wartości nie zmienia się w nieoczekiwany sposób. Ciąg zawierający tylko cyfry nie magicznie staje się liczbą, co może się zdarzyć w Perlu. Każda zmiana typu wymaga wyraźnej konwersji.
  • Typowanie dynamiczne oznacza, że ​​obiekty wykonawcze (wartości) mają typ, w przeciwieństwie do typowania statycznego, w którym zmienne mają typ.

Jak na twój przykład

bob = 1
bob = "bob"

Działa to, ponieważ zmienna nie ma typu; może nazwać dowolny obiekt. Po tym bob=1zobaczysz, że type(bob)powraca int, ale potem bob="bob"powraca str. (Zauważ, że typejest to funkcja regularna, więc ocenia swój argument, a następnie zwraca typ wartości).

Porównaj to ze starszymi dialektami języka C, które były słabo wpisane statycznie, dzięki czemu wskaźniki i liczby całkowite były prawie wymienne. (Współczesny ISO C wymaga konwersji w wielu przypadkach, ale mój kompilator nadal domyślnie nie toleruje tego).

Muszę dodać, że pisanie mocne kontra słabe jest raczej kontinuum niż wyborem logicznym. C ++ ma silniejsze pisanie niż C (wymagane jest więcej konwersji), ale system typów można obalić za pomocą rzutowania wskaźnika.

O sile systemu typów w dynamicznym języku, takim jak Python, w rzeczywistości decyduje sposób, w jaki jego prymitywy i funkcje biblioteczne reagują na różne typy. Np. +Jest przeciążony, więc działa na dwóch liczbach lub dwóch ciągach znaków, ale nie na ciągu znaków i liczbie. Jest to wybór projektowy dokonany po +jego wdrożeniu, ale tak naprawdę nie jest to konieczność wynikająca z semantyki języka. W rzeczywistości, gdy przeciążasz +niestandardowy typ, możesz niejawnie przekonwertować wszystko na liczbę:

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)

Instancję klasy Foomożna dodać do innych obiektów:

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

Zauważmy, że chociaż silnie wpisane Python jest całkowicie w porządku z dodawania obiektów typu inti floati zwraca obiekt typu float(np int(42) + float(1)zwrotów 43.0). Z drugiej strony, ze względu na niedopasowanie między typami Haskell będzie skarżyć, jeśli ktoś próbuje co następuje (42 :: Integer) + (1 :: Float). To sprawia, że ​​Haskell jest językiem ściśle typowym, w którym typy są całkowicie rozłączne i możliwe jest jedynie kontrolowane przeładowanie za pomocą klas typów.

Fred Foo
źródło
18
Jednym z przykładów, których nie widzę zbyt często, ale myślę, że ważne jest, aby pokazać, że Python nie jest całkowicie silnie napisany, to wszystkie rzeczy, które oceniają jako logiczne: docs.python.org/release/2.5.2/lib/truth.html
gsingh2011
25
Nie jestem pewien, czy jest to kontrprzykład: rzeczy mogą mieć wartość logiczną, ale nie stają się nagle „logiczną”. To prawie tak, jakby ktoś domyślnie nazwał coś takiego jak as_boolean (<wartość>), co nie jest tym samym co typ samego obiektu, który się zmienia, prawda?
jbrendel
15
Prawdziwość w kontekście logicznym nie jest kontrprzykładem, ponieważ tak naprawdę nic nie jest konwertowane na Truelub False. Ale co z promocją liczb? 1.0 + 2działa równie dobrze w Pythonie, jak w Perlu lub C, nawet jeśli "1.0" + 2nie. Zgadzam się z @jbrendel, że to nie jest tak naprawdę domniemana konwersja, to tylko przeciążenie - ale w tym samym sensie Perl również nie dokonuje żadnej niejawnej konwersji. Jeśli funkcje nie mają zadeklarowanych typów parametrów, nie ma mowy o domniemanej konwersji.
abarnert
13
Lepszym sposobem myślenia o silnym typowaniu jest to, że ten typ ma znaczenie podczas wykonywania operacji na zmiennej. Jeśli typ nie jest zgodny z oczekiwaniami, język, który narzeka, jest silnie typowany (python / java), a ten, który nie jest słabo typowany (javascript) Języki dynamicznie typowane (python) to te, które pozwalają na zmianę typu zmiennej w środowisko uruchomieniowe, podczas gdy języki o typie statycznym (Java) nie pozwalają na to po zadeklarowaniu zmiennej.
kashif
2
@ gsingh2011 Prawda jest przydatna i nie jest słabym samodzielnym pisaniem , ale przypadkowy if isValid(value) - 1wyciek może. Wartość logiczna jest wymuszana na liczbę całkowitą, która jest następnie oceniana jako prawdziwa wartość. False - 1staje się prawdomówny i True - 1staje się fałszem, co prowadzi do krępująco trudnego, dwuwarstwowego błędu debugowania. W tym sensie python jest najczęściej silnie pisany; koercje typu zwykle nie powodują błędów logicznych.
Aaron3468,
57

Są pewne ważne kwestie, które moim zdaniem przeoczyły wszystkie istniejące odpowiedzi.


Słabe pisanie oznacza umożliwienie dostępu do podstawowej reprezentacji. W C mogę utworzyć wskaźnik do znaków, a następnie powiedzieć kompilatorowi, że chcę go użyć jako wskaźnika do liczb całkowitych:

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

Na platformie little-endian z 32-bitowymi liczbami całkowitymi tworzy ito tablicę liczb 0x64636261i 0x00676665. W rzeczywistości możesz nawet rzutować same wskaźniki na liczby całkowite (o odpowiednim rozmiarze):

intptr_t i = (intptr_t)&sz;

I oczywiście oznacza to, że mogę nadpisać pamięć w dowolnym miejscu w systemie. *

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

* Oczywiście współczesny system operacyjny korzysta z pamięci wirtualnej i ochrony strony, więc mogę tylko nadpisać pamięć własnego procesu, ale nie ma nic w samym C, które oferuje taką ochronę, jak każdy, kto kiedykolwiek napisał kod, na przykład Classic Mac OS lub Win16.

Tradycyjne Lisp pozwalało na podobne hakowanie; na niektórych platformach liczby zmiennoprzecinkowe i minusowe komórki były tego samego typu, i można było po prostu przekazać jedną funkcję, oczekując drugiej, i „zadziałałaby”.

Obecnie większość języków nie jest tak słaba, jak C i Lisp, ale wiele z nich wciąż jest nieszczelnych. Na przykład, każdy język OO, który ma odznaczony „downcast”, * to przeciek typu: w zasadzie mówisz kompilatorowi „Wiem, że nie podałem ci wystarczającej ilości informacji, aby wiedzieć, że jest to bezpieczne, ale jestem całkiem pewien tak jest, „gdy cały punkt systemu typów polega na tym, że kompilator zawsze ma wystarczającą ilość informacji, aby wiedzieć, co jest bezpieczne.

* Sprawdzony downcast nie osłabia systemu typów języka tylko dlatego, że przenosi czek do środowiska wykonawczego. Gdyby tak było, wówczas polimorfizm podtypów (inaczej wirtualne lub w pełni dynamiczne wywołania funkcji) stanowiłby to samo naruszenie systemu typów i nie sądzę, aby ktokolwiek chciał to powiedzieć.

Bardzo niewiele języków „skryptowych” jest słabych w tym sensie. Nawet w Perlu lub Tcl nie można pobrać łańcucha i po prostu zinterpretować jego bajty jako liczbę całkowitą. * Warto jednak zauważyć, że w CPython (i podobnie w przypadku wielu innych tłumaczy dla wielu języków), jeśli jesteś naprawdę wytrwały, można użyć ctypeszaładować libpython, rzutować obiektu iddo POINTER(Py_Object)i zmusić system typu przeciekać. To, czy powoduje to osłabienie systemu typów, zależy od przypadków użycia - jeśli próbujesz zaimplementować piaskownicę z ograniczeniami wykonania w języku w celu zapewnienia bezpieczeństwa, musisz poradzić sobie z tego rodzaju ucieczkami…

* Możesz użyć funkcji takiej jak struct.unpackodczyt bajtów i zbudowanie nowej int z „jak C reprezentowałby te bajty”, ale to oczywiście nie jest nieszczelne; nawet Haskell na to pozwala.


Tymczasem niejawna konwersja naprawdę różni się od słabego lub nieszczelnego systemu typu.

Każdy język, nawet Haskell, ma funkcje, powiedzmy, konwersji liczby całkowitej na ciąg lub liczbę zmiennoprzecinkową. Ale niektóre języki wykonują dla Ciebie niektóre z tych konwersji automatycznie - np. W C, jeśli wywołasz funkcję, która chce float, a przekażesz ją int, zostanie ona dla Ciebie przekonwertowana. To z pewnością może prowadzić do błędów, np. Z nieoczekiwanymi przepełnieniami, ale nie są to te same rodzaje błędów, które otrzymujesz z systemu słabego typu. A C tak naprawdę nie jest tutaj wcale słabszy; możesz dodać liczbę całkowitą i liczbę zmiennoprzecinkową w Haskell lub nawet połączyć zmiennoprzecinkową z łańcuchem, po prostu musisz to zrobić wyraźniej.

A w przypadku dynamicznych języków jest to dość mętne. W Pythonie i Perlu nie ma czegoś takiego jak „funkcja, która chce liczby zmiennoprzecinkowej”. Ale są przeciążone funkcje, które robią różne rzeczy z różnymi typami, i istnieje silne intuicyjne poczucie, że np. Dodanie łańcucha do czegoś innego jest „funkcją, która chce łańcucha”. W tym sensie Perl, Tcl i JavaScript wydają się robić wiele niejawnych konwersji ( "a" + 1daje ci "a1"), podczas gdy Python robi o wiele mniej ( "a" + 1podnosi wyjątek, ale 1.0 + 1daje 2.0*). Po prostu trudno jest sformułować ten sens w kategoriach formalnych - dlaczego nie powinien istnieć ciąg, +który bierze ciąg i liczbę całkowitą, skoro oczywiście istnieją inne funkcje, takie jak indeksowanie?

* W rzeczywistości we współczesnym Pythonie można to wytłumaczyć podtypami OO, ponieważ isinstance(2, numbers.Real)jest to prawda. Nie wydaje mi się, żeby istniała jakaś 2instancja typu łańcucha w Perlu lub JavaScript… chociaż tak naprawdę jest w Tcl, ponieważ wszystko jest instancją łańcucha.


Wreszcie istnieje inna, całkowicie ortogonalna, definicja „silnego” vs. „słabego” pisania, gdzie „mocny” oznacza mocny / elastyczny / ekspresyjny.

Na przykład Haskell pozwala zdefiniować typ, który jest liczbą, łańcuchem, listą tego typu lub mapą ciągów znaków na ten typ, co jest doskonałym sposobem na przedstawienie wszystkiego, co można zdekodować z JSON. Nie ma możliwości zdefiniowania takiego typu w Javie. Ale przynajmniej Java ma typy parametryczne (ogólne), więc możesz napisać funkcję, która pobiera Listę T i wie, że elementy są typu T; inne języki, takie jak wczesna Java, zmusiły cię do użycia Listy Obiektów i spuszczenia. Ale przynajmniej Java pozwala tworzyć nowe typy własnymi metodami; C pozwala tylko tworzyć struktury. I BCPL nawet tego nie miał. I tak dalej, aż do montażu, gdzie jedynymi typami są różne długości bitów.

W tym sensie system typów Haskella jest silniejszy niż współczesny Java, który jest silniejszy niż wcześniejszy Java, który jest silniejszy niż C, który jest silniejszy niż BCPL.

Gdzie więc Python pasuje do tego spektrum? To trochę trudne. W wielu przypadkach pisanie kaczek pozwala symulować wszystko, co możesz zrobić w Haskell, a nawet niektóre rzeczy, których nie możesz; Oczywiście błędy są wychwytywane w czasie wykonywania zamiast czasu kompilacji, ale nadal są wychwytywane. Są jednak przypadki, w których pisanie kaczek nie jest wystarczające. Na przykład w Haskell możesz powiedzieć, że pusta lista liczb całkowitych jest listą liczb wewnętrznych, więc możesz zdecydować, że zmniejszenie +tej listy powinno zwrócić 0 *; w Pythonie pusta lista jest pustą listą; nie ma informacji o typie, które pomogłyby ci zdecydować, co +powinno zrobić redukcja .

* W rzeczywistości Haskell nie pozwala ci tego zrobić; jeśli wywołasz funkcję zmniejszania, która nie przyjmuje wartości początkowej na pustej liście, pojawi się błąd. Ale jej system typ jest wystarczająco silny, aby mógł dokonać tej pracy, a Python nie jest.

abarnert
źródło
3
Ta odpowiedź jest genialna! Szkoda, że ​​tak długo marnuje się na dole listy.
LeoR
1
Tylko mały komentarz do twojego przykładu w C: char sz[]nie jest wskaźnikiem do char, jest tablicą char, aw zadaniu rozkłada się we wskaźnik.
majkel.mk
39

Mylisz „silnie pisane” z „dynamicznie pisane” .

Nie mogę zmienić typu 1, dodając ciąg '12', ale mogę wybrać, jakie typy przechowuję w zmiennej i zmienić to w czasie wykonywania programu.

Przeciwieństwem pisania dynamicznego jest pisanie statyczne; deklaracja typów zmiennych nie zmienia się w trakcie trwania programu. Przeciwieństwem silnego pisania jest słabe pisanie; rodzaj wartości może ulec zmianie w trakcie trwania programu.

Martijn Pieters
źródło
Opis w linku jest silnie wpisany: „Zasadniczo język silnie typowany ma bardziej rygorystyczne reguły pisania w czasie kompilacji, co oznacza, że ​​podczas kompilacji częściej występują błędy i wyjątki”. sugeruje, że Python jest słabo typowanym językiem ... czy wiki jest niepoprawne?
Pada deszcz
1
@ s̮̦̩e̝͓c̮͔̞ṛ̖̖e̬̣̦t̸͉̥̳̼: to wcale nie jest dorozumiane. Python ma ścisłe reguły pisania w czasie kompilacji, każdy utworzony obiekt ma tylko jeden typ. I „ogólnie” nic nie implikuje, to po prostu oznacza, że ​​Python jest od tego wyjątkiem.
Martijn Pieters
24

Według tej wiki artykuł w Pythonie Python jest dynamicznie i silnie pisany (zapewnia również dobre wyjaśnienie).

Być może myślisz o statycznie typowanych językach, w których typy nie mogą się zmieniać podczas wykonywania programu, a sprawdzanie typu następuje w czasie kompilacji w celu wykrycia ewentualnych błędów.

To pytanie SO może być interesujące: Języki typu dynamicznego kontra języki typu statycznego i ten artykuł w Wikipedii na temat systemów typu zawiera więcej informacji

Levon
źródło
18

TLDR;

Typowanie w Pythonie jest dynamiczne, więc możesz zmienić zmienną łańcuchową na int

x = 'somestring'
x = 50

Pisanie w Pythonie jest silne, więc nie można scalać typów:

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

W słabo wpisanym Javascriptie dzieje się tak ...

 'foo'+3 = 'foo3'

Odnośnie wnioskowania typu

Java zmusza cię do jawnego zadeklarowania typów obiektów

int x = 50

Kotlin używa wnioskowania, aby zdać sobie sprawę, że to jestint

x = 50

Ponieważ oba języki używają typów statycznych , xnie można ich zmienić z int. Żaden język nie pozwoliłby na taką dynamiczną zmianę

x = 50
x = 'now a string'
Adam Hughes
źródło
Nie znam szczegółów Javascript, ale 'x' + 3może to być operator+przeciążenie i konwersja typu za sceną?
Pada
3
W każdym razie twoja odpowiedź jest w rzeczywistości bardziej zwięzła i łatwiejsza do zrozumienia niż powyższe.
Pada
8

Odpowiedzi na to już kilka razy, ale Python jest silnie napisanym językiem:

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

W JavaScript:

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

To jest różnica między słabym i mocnym pisaniem. Słabe typy automatycznie próbują konwertować z jednego typu na inny, w zależności od kontekstu (np. Perl). Silne typy nigdy nie dokonują konwersji pośrednio.

Twoje zamieszanie polega na niezrozumieniu, w jaki sposób Python wiąże wartości z nazwami (powszechnie nazywanymi zmiennymi).

W Pythonie nazwy nie mają typów, więc możesz robić takie rzeczy jak:

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

I imiona można przypisać do dowolnego:

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

Do dalszego czytania:

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

i nieco powiązane, ale bardziej zaawansowane:

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

Wayne Werner
źródło
1
Kilka lat później - kolejny użyteczny i istotny zasób: youtu.be/_AEJHKGk9ns
Wayne Werner
Silne i słabe pisanie nie ma nic wspólnego z typem wynikowym wyrażeń, takich jak 3 + „4”. JavaScript w tym przykładzie jest tak samo silny jak Python.
qznc
@qznc, w jaki sposób JavaScript jest tak silny? Nie sądzę, że zasugerowałem, że ma to coś wspólnego z typem wynikowym, w rzeczywistości wyraźnie stwierdzam, że słabe typy automatycznie próbują konwertować z jednego typu na inny .
Wayne Werner
2
@oneloop niekoniecznie jest to prawda, po prostu zachowanie łączenia liczb zmiennoprzecinkowych i liczb całkowitych jest dobrze zdefiniowane, a wynikiem jest liczba zmiennoprzecinkowa. Możesz to zrobić również "3"*4w Pythonie. Rezultatem jest oczywiście "3333". Nie powiedziałbyś, że przekształca którąkolwiek z tych rzeczy. Oczywiście może to być tylko argumentacja semantyki.
Wayne Werner
1
@oneloop Niekoniecznie jest prawdą, że ponieważ Python produkuje floatz kombinacji floati intże domyślnie konwertuje typ. Istnieje naturalna zależność między zmiennoprzecinkową i wewnętrzną, a tak naprawdę, typowa heirarchia to określa. Przypuszczam, że można argumentować, że Javascript uważa '3'+4i 'e'+4oba są dobrze zdefiniowanymi operacjami w taki sam sposób, jak Python uważa 3.0 + 4za dobrze zdefiniowane, ale w tym momencie tak naprawdę nie ma czegoś takiego jak silne lub słabe typy, tylko (nie) zdefiniowane operacje.
Wayne Werner
6

Zmienna Python przechowuje nietypowe odwołanie do obiektu docelowego, który reprezentuje wartość.

Każda operacja przypisania oznacza przypisanie nietypowego odwołania do przypisanego obiektu - tj. Obiekt jest współdzielony przez oryginalne i nowe (policzone) odniesienia.

Typ wartości jest powiązany z obiektem docelowym, a nie z wartością odniesienia. (Silne) sprawdzanie typu jest wykonywane, gdy wykonywana jest operacja z wartością (czas działania).

Innymi słowy, zmienne (technicznie) nie mają typu - nie ma sensu myśleć w kategoriach typu zmiennego, jeśli chce się być dokładnym. Ale odniesienia są automatycznie usuwane z odniesień i faktycznie myślimy w kategoriach typu obiektu docelowego.

pepr
źródło
6

Termin „silne pisanie na klawiaturze” nie ma określonej definicji.

Dlatego użycie tego terminu zależy od tego, z kim rozmawiasz.

Nie uważam żadnego języka, w którym typ zmiennej nie jest albo jawnie zadeklarowany, ani statycznie wpisany, aby był silnie typowany.

Silne pisanie nie tylko wyklucza konwersję (na przykład „automatyczne” konwertowanie z liczby całkowitej na ciąg). Wyklucza to przypisanie (tj. Zmianę typu zmiennej).

Jeśli poniższy kod się kompiluje (interpretuje), język nie jest pisany silnym typem:

Foo = 1 Foo = „1”

W silnie napisanym języku programista może „liczyć” na typ.

Na przykład, jeśli programista zobaczy deklarację,

UINT64 kZarkCount;

i on lub ona wie, że 20 linii później, kZarkCount jest nadal UINT64 (o ile występuje w tym samym bloku) - bez konieczności sprawdzania interweniującego kodu.

użytkownik5330045
źródło
1

Właśnie odkryłem świetny zwięzły sposób na zapamiętanie go:

Dynamiczne / statyczne typowanie; mocno / słabo wpisana wartość.

Pada deszcz
źródło
0

Myślę, że ten prosty przykład powinien wyjaśnić różnice między silnym i dynamicznym pisaniem:

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

Jawa:

public static void main(String[] args) {
        int i = 1;
        i = "1"; //will be error
        i = '0.1'; // will be error
    }
Dmitrij Zagorulkin
źródło
Twój kod python demonstruje dynamiczne pisanie, podczas gdy java demonstruje pisanie statyczne. Lepszym przykładem jest $ var = '2' + 1 // wynik to 3
erichlf
@ivleph Zgadzam się. można również napisać coś takiego: „a” * 3 == „aaa”
Dmitry Zagorulkin
-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....

Powyższe spowodowałoby koszmar niemożliwego do utrzymania kodu w dużym systemie przez długi okres czasu. Nazwij to jak chcesz, ale możliwość „dynamicznej” zmiany typu zmiennych to po prostu zły pomysł ...

Ryan Alexander
źródło