Dlaczego Python nie potrzebuje kompilatora?

29

Zastanawiam się (teraz, kiedy zacząłem od C ++, który potrzebuje kompilatora), dlaczego Python nie potrzebuje kompilatora?

Po prostu wpisuję kod, zapisuję go jako exec i uruchamiam. W C ++ muszę tworzyć kompilacje i wszystkie inne fajne rzeczy.

Billjk
źródło
4
Python to tylko język z wieloma implementacjami. Iron Python jest kompilowany w taki sam sposób jak C # i C ++, i mogą istnieć inne podobne implementacje.
Job
1
C # i C ++ nie są kompilowane w ten sam sposób - chociaż można argumentować, że oba kończą jako instrukcje maszynowe, ale jeśli tak, to możesz powiedzieć, że BASIC jest kompilowany w ten sam sposób.
gbjbaanb
7
@ gbjbaanb, ale z drugiej strony angielski nie jest kompilowany, a analiza semantyczna jednego zdania może dać dwa równie poprawne wyniki, a powyższe można odczytać jako „skompilowane python żelaza tak samo jak C # i C ++”
Rune FS
Jakiej platformy / oprogramowania używasz do pisania kodu w języku Python? Jeśli napiszesz plik .py, nie jest to plik wykonywalny. Jest to nadal plik kodu źródłowego. Z linii poleceń używasz pythonpolecenia do interpretacji pliku .py lub jeśli używasz IDLE lub Eclipse, IDE robi to za Ciebie.
Rick Henderson

Odpowiedzi:

68

Python ma kompilator! Po prostu tego nie zauważasz, ponieważ działa automatycznie. Możesz jednak powiedzieć, że tam jest: spójrz na .pyc(lub .pyojeśli masz włączony optymalizator) pliki, które są generowane dla modułów, które ty import.

Ponadto nie kompiluje się do kodu natywnej maszyny. Zamiast tego kompiluje się do kodu bajtowego używanego przez maszynę wirtualną. Maszyna wirtualna sama jest skompilowanym programem. Jest to bardzo podobne do działania Java; tak podobny, że istnieje wariant Pythona ( Jython ), który zamiast tego kompiluje się do kodu bajtowego wirtualnej maszyny Java! Istnieje również IronPython , który kompiluje się do CLR Microsoftu (używanego przez .NET). (Zwykły kompilator bajtowy kodu w języku Python jest czasem nazywany CPython, aby odróżnić go od tych alternatyw).

C ++ musi ujawnić proces kompilacji, ponieważ sam język jest niekompletny; nie określa wszystkiego, co linker powinien wiedzieć, aby zbudować program, ani nie może określać opcji kompilacji przenośnie (niektóre kompilatory pozwalają #pragma, ale nie jest to standard). Więc resztę pracy musisz wykonać przy użyciu plików makefile i ewentualnie automatycznego piekła (autoconf / automake / libtool). To jest naprawdę tylko relacja po tym, jak to zrobił C. C zrobił to w ten sposób, ponieważ uczynił kompilator prostym, co jest jednym z głównych powodów, dla których jest tak popularny (każdy mógł wypróbować prosty kompilator C w latach 80-tych).


Niektóre rzeczy, które mogą wpłynąć na działanie kompilatora lub linkera, ale nie są określone w składni C lub C ++:

  • rozwiązywanie zależności
  • wymagania dotyczące biblioteki zewnętrznej (w tym kolejność zależności)
  • poziom optymalizatora
  • ustawienia ostrzeżeń
  • wersja specyfikacji języka
  • mapowania linkerów (która sekcja idzie gdzie w ostatecznym programie)
  • architektura docelowa

Niektóre z nich można wykryć, ale nie można ich określić; np. mogę wykryć, z którym C ++ jest używany __cplusplus, ale nie mogę określić, że C ++ 98 jest tym, który jest używany dla mojego kodu w samym kodzie; Muszę przekazać go jako flagę do kompilatora w pliku Makefile lub wprowadzić ustawienia w oknie dialogowym.

Chociaż może się wydawać, że w kompilatorze istnieje system „rozwiązywania zależności”, który automatycznie generuje rekordy zależności, te rekordy mówią tylko, jakie pliki nagłówkowe wykorzystuje dany plik źródłowy. Nie mogą wskazać, jakie dodatkowe moduły kodu źródłowego są wymagane do połączenia się z programem wykonywalnym, ponieważ w C lub C ++ nie ma standardowego sposobu wskazania, że ​​dany plik nagłówkowy jest definicją interfejsu dla innego modułu kodu źródłowego, a nie tylko wiersze, które chcesz wyświetlać w wielu miejscach, aby się nie powtarzać. Istnieją konwencje nazewnictwa plików, ale nie są one znane ani egzekwowane przez kompilator i linker.

Kilka z nich można ustawić za pomocą #pragma, ale jest to niestandardowe i mówiłem o standardzie. Wszystkie te rzeczy mogą być określone przez standard, ale nie były w interesie kompatybilności wstecznej. Panuje przekonanie, że pliki makefile i IDE nie są zepsute, więc ich nie naprawiaj.

Python obsługuje to wszystko w języku. Na przykład importokreśla jawną zależność modułu, implikuje drzewo zależności, a moduły nie są dzielone na pliki nagłówkowe i źródłowe (tj. Interfejs i implementacja).

Mike DeSimone
źródło
3
Implementacja C Pythona to CPython , Cython to coś innego.
Greg Hewgill
4
Innymi powodami, dla których C skompilował się do kodu maszynowego, było to, że miał on być czymś więcej niż gloryfikowanym asemblerem, ponieważ interpretery kodu bajtowego były technicznie niewykonalne na posiadanym sprzęcie i ponieważ jednym z najważniejszych zadań było napisanie jądra systemu operacyjnego.
tdammers
2
@BillyONeal z jednym dużym wyjątkiem, że w c / c ++ jako programista musisz robić rzeczy w określony sposób (albo makefile, albo zrzucić wszystkie rzeczy do tego samego obiektu blob) w pythonie po prostu wykonujesz swoją pracę i kompilator razem z maszyną wirtualną zajmuje się resztą
Rune FS
3
„C ++ musi ujawnić proces kompilacji, ponieważ sam język jest niekompletny” Er, co ??
Lekkość ściga się z Moniką
3
Przeczytasz tę część zaraz , prawda? „nie określa wszystkiego, co linker powinien wiedzieć, aby zbudować program, ani nie może określać opcji kompilacji przenośnie”. Nie można po prostu zbudować żadnego pliku C ++, przekazując go do kompilatora; często musisz podać metadane, takie jak flagi kompilacji, ścieżki itp. Metadane nie są określone przez standard i nie są przenośne, dlatego musimy przeciągać inne rzeczy, takie jak make, cmake, Visual Studio lub cokolwiek innego, aby skończ prace. Tak więc standard musi wywoływać pewne rzeczy, jak w jednostce kompilacji, a inne w całym programie.
Mike DeSimone
7

Python jest językiem interpretowanym. Oznacza to, że na twoim komputerze znajduje się oprogramowanie, które odczytuje kod Pythona i wysyła „instrukcje” do komputera. Artykuł w Wikipedii na temat tłumaczonych języków może być interesujący.

Gdy kompilowany jest język taki jak C ++ (język skompilowany), oznacza to, że jest on konwertowany na kod maszynowy, który po uruchomieniu jest odczytywany bezpośrednio przez sprzęt. Artykuł na Wikipedii skompilowanych języków może stanowić interesujący kontrast.

Zenon
źródło
21
Nie ma czegoś takiego jak język interpretowany lub skompilowany. Język to abstrakcyjny zestaw reguł matematycznych. Język nie jest kompilowany ani interpretowany. Po prostu jest język . Kompilacja i interpretacja to cechy kompilatora lub tłumacza (duh!), A nie język. Każdy język można zaimplementować za pomocą kompilatora, a każdy język można zaimplementować za pomocą tłumacza. Większość języków ma zarówno kompilowane, jak i interpretowane implementacje. Istnieją interpretery dla C ++ i istnieją kompilatory dla Pythona. (W rzeczywistości, wszystkie obecnie istniejące implementacje Python mają kompilatory.)
Jörg W Mittag
4
Większość nowoczesnych wysokowydajnych implementacji językowych łączy interpreter i kompilator (lub nawet kilka kompilatorów) w celu uzyskania maksymalnej wydajności. Właściwie, to jest niemożliwe , aby uruchomić dowolny program, bez tłumacza. W końcu kompilator to tylko program, który tłumaczy program z jednego języka na inny język. Ale w pewnym momencie musisz faktycznie uruchomić program, co robi interpreter (który może, ale nie musi być zaimplementowany w krzemie).
Jörg W Mittag
10
@ JörgWMittag: Masz techniczną rację. Jednak większość języków została zaprojektowana dla kontekstu interpretowanego lub do pełnej kompilacji. Pisanie interpretera dla GW BASIC lub Common Lisp jest znacznie łatwiejsze niż pisanie dla, powiedzmy, C ++ lub C #; Python traci wiele punktów sprzedaży bez interaktywnego środowiska; pisanie kompilatora dla PHP jest cholernie trudne i prawdopodobnie okropnie nieefektywne, ponieważ skompilowany plik wykonywalny musiałby zawierać cały interpreter PHP z powodu eval () i podobnych konstrukcji - można argumentować, że taki kompilator oszukuje.
tdammers
2
@tdammers, tak. Możemy rozsądnie używać „języka kompilowanego”, aby oznaczać „język zwykle kompilowany”. Ale to pomija punkt, w którym PHP, Java, Python, Lua i C # są zaimplementowane jako kompilatory kodu bajtowego. We wszystkich tych językach zaimplementowano także JIT. Naprawdę nie można tak naprawdę nazwać niektórych z tych języków skompilowanymi, a niektóre zinterpretowanymi, ponieważ mają one tę samą strategię implementacji.
Winston Ewert
2
@BillyONeal, nieprawda przynajmniej dla pytona. Możesz dystrybuować kod bajtowy Pythona i uruchamiać go bez źródła. Ale prawdą jest, że nie można rozpowszechniać Pythona bez kompilatora.
Winston Ewert
5

Nie wszystkie skompilowane języki mają wbudowany cykl edycji-kompilacji-uruchomienia-łącza.

To, na co się natkniesz, to funkcja / ograniczenie C ++ (lub przynajmniej implementacje C ++).

Aby cokolwiek zrobić, musisz zapisać swój kod w plikach i zbudować obraz monolityczny za pomocą procesu zwanego łączeniem.

W szczególności ten monolityczny proces łączenia jest mylony z rozróżnieniem między kompilacją a interpretacją.

Niektóre języki robią to wszystko znacznie bardziej dynamicznie, eliminując nieporadny monolityczny etap łączenia, a nie eliminując kompilację do kodu maszynowego. Źródło jest nadal kompilowane do plików obiektowych, ale są one ładowane do obrazu w czasie wykonywania, a nie łączone w monolityczny plik wykonywalny.

Mówisz „przeładuj ten moduł”, a on ładuje źródło i interpretuje je lub kompiluje, w zależności od przełącznika trybu.

Programowanie jądra Linuksa ma trochę tego smaku, nawet jeśli pracujesz w C. Możesz ponownie skompilować moduł i załadować go i rozładować. Oczywiście nadal masz świadomość, że produkujesz jakieś pliki wykonywalne i zarządza nimi złożony system kompilacji, z kilkoma ręcznymi krokami. Ale faktem jest, że ostatecznie możesz rozładować i ponownie załadować tylko ten mały moduł i nie musisz restartować całego jądra.

Niektóre języki mają jeszcze bardziej drobnoziarnistą modularyzację niż ta, a budowanie i ładowanie odbywa się w czasie ich działania, więc jest bardziej płynne.

Kaz
źródło
2

co za odwrócenie uwagi od pytania początkowego ... Nie wspomniałem o tym, że źródłem programu python jest to, czego używasz i rozpowszechniasz, z perspektywy użytkownika to program. Mamy tendencję do upraszczania rzeczy na kategorie, które nie są dobrze zdefiniowane.

Skompilowane programy są zwykle uważane za samodzielne pliki kodu maszynowego. (co prawda często zawiera linki do bibliotek linków dynamicznych powiązanych z konkretnymi systemami operacyjnymi). To powiedziawszy ... istnieją odmiany większości języków programowania, które można opisać jako skompilowane lub interpretowane.

Python nie potrzebuje kompilatora, ponieważ opiera się na aplikacji (zwanej interpreterem), która kompiluje i uruchamia kod bez zapisywania tworzonego kodu maszynowego w formie, do której można łatwo uzyskać dostęp lub rozpowszechniać.

Stephen Robinson
źródło
1

Wszystkie języki programowania wymagają tłumaczenia ludzkich koncepcji na docelowy kod maszynowy. Nawet język asemblera musi zostać przetłumaczony na kod maszynowy. Tłumaczenie to zwykle odbywa się w następujących fazach:

Faza 1: Analiza i tłumaczenie (parsowanie) na kod pośredni. Faza 2: Tłumaczenie kodu pośredniego na docelowy kod maszynowy z elementami zastępczymi dla odniesień zewnętrznych. Faza 3: Rozdzielenie odniesień zewnętrznych i spakowanie w program wykonywalny maszyny.

To tłumaczenie jest często określane jako kompilacja wstępna i „Just in time” (JIT) lub kompilacja w czasie wykonywania.

Języki takie jak C, C ++, COBOL, Fortran, Pascal (nie wszystkie) i asembler są wstępnie skompilowanymi językami, które mogą być wykonywane bezpośrednio przez system operacyjny bez potrzeby tłumacza.

Języki takie jak Java, BASIC, C # i Python są interpretowane. Wszyscy używają kodu pośredniego utworzonego w fazie 1, ale czasami różnią się sposobem tłumaczenia go na kod maszynowy. Najprostsze formularze używają tego kodu pośredniego do wykonywania procedur kodu maszynowego, które wykonują oczekiwaną pracę. Inni kompilują kod pośredni w dół do kodu maszynowego i wykonują zewnętrzne ustalanie zależności podczas działania. Po skompilowaniu można go natychmiast wykonać. Kod maszynowy jest również przechowywany w pamięci podręcznej skompilowanego wcześniej kodu maszynowego wielokrotnego użytku, który można później ponownie wykorzystać, jeśli funkcja będzie potrzebna później. Jeśli funkcja została już buforowana, interpreter nie musi jej ponownie kompilować.

Większość współczesnych języków wysokiego poziomu należy do kategorii tłumaczonych (z JIT). Przeważnie prekompilowane są głównie starsze języki, takie jak C & C ++.

Mark Baker
źródło