Über Probleme mit dem Python-Übersetzer und das Überdenken der Sprache

- Wie viele Architekten benötigen Sie, um eine Programmiersprache zu implementieren?
- Einhundert. Man wird die Implementierung schreiben und 99 werden sagen, was sie besser machen können.


In diesem Artikel möchte ich nicht nur die Sprache selbst ansprechen, sondern auch die Details der Implementierung von CPython und seiner Standardbibliothek, die sicherstellen, dass Sie keine einfachen Möglichkeiten haben, die Python-Anwendung entweder multithreadingfĂ€hig, schnell oder einfach zu unterstĂŒtzen, und warum so viel erstellt wurde alternative Implementierungen (PyPy, Cython, Jython, IronPython, Python fĂŒr .NET, Parakeet, Nuitka, Stackless, Unladen Swallow), von denen die HĂ€lfte bereits verstorben ist; und nur wenige verstanden, warum die Alternativen keine Chance hatten, den Kampf ums Überleben gegen andere Sprachen zu gewinnen. Ja, es gibt GDScript, das zur Lösung von Leistungsproblemen entwickelt wurde. Es gibt Nim, das zur Lösung aller Probleme im Allgemeinen entwickelt wurde, ohne dass der Benutzer zu explizit Typen deklarieren muss. Angesichts der enormen TrĂ€gheit der Branche ist mir jedoch klar, dass neue Sprachen in den nĂ€chsten 10 Jahren definitiv keine nennenswerte Nische einnehmen werden. Ich glaube jedoch, dass Python effektiv gemacht werden kann, indem der Stil des Schreibens von Code geĂ€ndert wird, wobei grĂ¶ĂŸtenteils die ursprĂŒngliche Syntax beibehalten wird und die Möglichkeit der Interaktion zwischen neuem und altem Code uneingeschrĂ€nkt erhalten bleibt. Ich werde mich auf die Probleme von CPython konzentrieren, nicht auf den engsten Konkurrenten PyPy, da PyPy tatsĂ€chlich dieselben Probleme von CPython umgeht.


Ich bin ein Programmierer mit sieben Jahren Erfahrung, der sich hauptsĂ€chlich mit der Entwicklung von Desktop-Anwendungen befasst, mit Schwerpunkt auf Web- und Multithread-Datenbanken. Sie fragen: "Warten Sie eine Minute, aber was hat Python mit der BenutzeroberflĂ€che, den Web-Fronts und dem Multithreading gemeinsam?" Und ich werde antworten "das ist es - nichts." Ich habe fĂŒr meine Aufgaben C, Delphi, JavaScript und SQL verwendet. Ich war mit dieser Situation nicht sehr zufrieden und habe vor einiger Zeit versucht, an Eric Schnees Projekt zur Implementierung der UnterstĂŒtzung mehrerer Interpreten in CPython teilzunehmen:
https://www.python.org/dev/peps/pep-0554/
https://github.com/ericsnowcurrently/multi-core-python


Leider kam schnell die Einsicht, dass:


  • CPython wird fĂŒr ein so beliebtes Projekt eher schlecht unterstĂŒtzt und weist eine Reihe alter Probleme auf, die beim erneuten Zeichnen der Implementierung auftreten. Infolgedessen wĂ€hlt Eric den Dolmetscher seit mehreren Jahren mit variablem Fortschritt aus.
  • Auch nach der erfolgreichen Implementierung mehrerer Interpreter ist nicht klar, wie die parallele AusfĂŒhrung weiter organisiert werden soll. PEP schlĂ€gt vor, einfache KanĂ€le zu verwenden. Dieses Tool wird jedoch gefĂ€hrlich, da die Aufgabe komplexer wird und die Gefahr des Einfrierens und unvorhersehbaren Verhaltens besteht.
  • Die Sprache selbst hat große Probleme, die Dolmetscher daran zu hindern, Daten direkt auszutauschen, und ein gewisses Maß an Garantie fĂŒr vorhersehbares Verhalten zu geben.

Jetzt genauer ĂŒber die Probleme.


Modifizierbare Klassendefinitionen


Ja, ich verstehe, dass eine Klasse in Python zur Laufzeit deklariert wird. Aber verdammt, warum Variablen hineinstecken? Warum alte Objekte mit neuen Methoden versehen? Sie können in Java keine Funktionen und Variablen außerhalb von Klassen deklarieren, aber es gibt keine solche EinschrĂ€nkung in Python (und Python wurde vor Java erstellt). DarĂŒber hinaus bitte ich Sie, darauf zu achten, wie Sie sich mit Krebs bĂŒcken mĂŒssen, um Ă€hnliche Methoden zum Objekt selbst und nicht zur Klasse hinzuzufĂŒgen - dies erfordert types.MethodType, function .__ get__, functools.partial und so weiter.
FĂŒr den Anfang möchte ich eine seltsame Frage stellen: Warum brauchen Sie Methoden in Python? Funktioniert nicht wie in engem JavaScript, sondern mit Klassenmethoden. Einer der Faktoren: Guido hat keine besseren Möglichkeiten gefunden, Kurznamen von Funktionen zu erstellen (so dass es kein gtk_button_set_focus_on_click gibt), da nicht klar ist, wie die erforderlichen Funktionen fĂŒr dieses bestimmte Objekt aus einer Reihe Ă€hnlicher Funktionen mit einem Kurznamen ausgewĂ€hlt werden können. Trotzdem erschienen len, iter, next, isinstance, slice, dict, dir, str, repr, hash, type in Python - dies sind jetzt Wrapper ĂŒber die entsprechenden Klassenmethoden mit Unterstrichen im Namen, und frĂŒher eingebaute einfache Typen nicht Klassen und arbeitete nur durch diese Funktionen. Persönlich sehe ich keinen großen Unterschied zwischen der Aufzeichnung von method (object) und object.method - insbesondere wenn method eine statische Funktion ist, die es im Allgemeinen nicht interessiert, welches erste Argument (self) akzeptiert wird.
Dynamische Klassendefinitionen im allgemeinen Fall:


  • Geben Sie keine modularen Tests . Ein im Test korrekt ausgearbeiteter Code kann einen Fehler auslösen, wenn das gesamte System funktioniert, und Sie sind in CPython nicht davor geschĂŒtzt.
  • große Optimierungsschwierigkeiten schaffen . Eine Klassendeklaration garantiert Ihnen nicht den tatsĂ€chlichen Betrieb der Klasse. Aus diesem Grund verwendet PyPys einziges erfolgreiches Optimierungsprojekt die Ablaufverfolgung, um die tatsĂ€chliche Abfolge der von der PrĂŒfmethode ausgefĂŒhrten Aktionen zu ermitteln.
  • Docken Sie nicht an die parallele CodeausfĂŒhrung an. Dieselbe Mehrfachverarbeitung funktioniert beispielsweise mit Kopien von Klassendefinitionen. Wenn Sie, wie Gott es verbietet, die Beschreibung der Klassen in einer der Kopien Ă€ndern, besteht die Gefahr, dass Ihre Anwendung auseinanderfĂ€llt.

Eine subtilere Version dynamischer Klassen ĂŒberschreibt den Attributzugriff durch __getattribute__, __getattr__ und andere. Oft werden sie als regulĂ€re Getter-Setter verwendet, um Funktionen an ein Feldobjekt zu delegieren und gelegentlich eine DSL zu organisieren. All diese Funktionen können zivilisierter implementiert werden, ohne die Klasse in einen Dump von Beschreibungen zu verwandeln, deren Verhalten manchmal schwer zu garantieren ist. Übrigens gibt es fĂŒr Getter / Setter bereits einen solchen Mechanismus - dies sind Attributdeskriptoren: https://www.python.org/dev/peps/pep-0252/#id5


Das Austauschen von Klassen im laufenden Betrieb ist fĂŒr das Debugging und das alltĂ€gliche Codieren erforderlich. Es sollte jedoch weiterhin ein spezielles Tool fĂŒr den Entwickler sein und kein zur Laufzeit nicht zu eliminierendes Artefakt.


Kompilierte Klassen sind nur ein kleiner Schritt, den Cython und Nuitka bereits unternommen haben. Dieser Schritt allein reicht jedoch ohne weitere Änderungen nicht aus, um selbst in Bezug auf die AusfĂŒhrungsgeschwindigkeit einen signifikanten Effekt zu erzielen, da Python beispielsweise die dynamische Variablenbindung ausgiebig nutzt, die nirgendwo zu finden ist geht in kompiliertem Code nicht weg.


Mehrfachvererbung


Ich denke, das ist der AnfĂŒhrer der Hutparade. Es ist nicht einmal auf der Ebene der C-Funktionen in der Implementierung von Python selbst und seinen Erweiterungen. "Aber was ist mit Schnittstellen?" Sie protestieren. Schnittstellen in C ++ und Java werden zum Deklarieren von Protokollen zum Aufrufen von Methoden eines Objekts zum Zwecke der nachfolgenden statischen ÜberprĂŒfung dieser Protokolle bei der Kompilierung sowie zum Generieren von Methodentabellen benötigt, die zur Laufzeit von anderem Code verwendet werden, der nichts ĂŒber das Quellobjekt weiß. Diese Rollen gehen in Python fast vollstĂ€ndig verloren, daher gibt es keine Rechtfertigung fĂŒr ihre Existenz. Ich mag die Art und Weise, wie BenutzeroberflĂ€chen in Go erstellt werden - es ist sehr Ă€hnlich zu Python ABC: https://www.python.org/dev/peps/pep-3119


Mehrfachvererbung ist kein direktes Problem fĂŒr die Parallelisierung und Optimierung, erschwert jedoch die Lesbarkeit und Wartbarkeit des Codes - dies ist der sogenannte Lasagne-Code (Ă€hnlich dem Spaghetti-Code).


Generatoren


Dies ist ein wirklich vernachlĂ€ssigter Fall von GoTo, bei dem die AusfĂŒhrung nicht nur unkontrolliert ĂŒber den Code springt, sondern auch ĂŒber Stapel. Besonders heftiges Spiel entsteht, wenn sich Generatoren mit Kontextmanagern ĂŒberschneiden (hallo PEP 567). Wenn in Python generell die Tendenz besteht, die Anwendung in eine dichte Kugel verbundener verĂ€nderlicher ZustĂ€nde zu verwickeln, die keinen Raum fĂŒr Test-, Parallelisierungs- und Programmoptimierungsmanöver bieten, dann sind die Generatoren eine Kirsche auf diesem Kuchen.


Was denkst du wird das Ergebnis des Programms sein:


import contextlib
@contextlib.contextmanager
def context_manager():
    try:
        print('')
        yield
    finally:
        print('')

def gen_in_manager():
    m = context_manager()
    with m:
        for i in range(5):
            yield i

g1 = gen_in_manager()
next(g1)
print('')


, :


import contextlib
@contextlib.contextmanager
def context_manager():
    try:
        print('')
        yield
    finally:
        print('')

def gen_in_manager():
    m = context_manager()
    with m:
        for i in range(5):
            yield i

def test():
    g1 = gen_in_manager()
    next(g1)

test()
print('')




.
, , : async/await, .


: RPython -. , . , , .



https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines


>>> a = ([42],)
>>> a[0] += [43, 44]
TypeError: 'tuple' object does not support item assignment
>>> a
([42, 43, 44],)
>>> a = ([42],)
>>> b = a[0]
>>> b += [43, 44]
>>> a
([42, 43, 44],)

>>> x = y = [1,2,3]
>>> x = x + [4]
>>> x == y
False
>>> x = y = [1,2,3]
>>> x += [4]
>>> x == y
True

>>> x = [[]]*5
>>> x
[[], [], [], [], []]
>>> x[0].append(0)
>>> x
[[0], [0], [0], [0], [0]]

, «'tuple' object does not support item assignment» : . , , , , . , , x[0].append(0) , CPython , . , .


— [] ? , , . , Clojure , - partial-copy-on-write . , .


? , , . , copy-on-write , : b.append.., b[].., b +=
 : , , — , , . , , , , // , .


copy-on-write? , , (), ( ), , .


- . ? , copy-on-write, «({'first': 1, 'second': 2},)», , , , , , «{'first': 1, 'second': 2}».



https://ru.wikipedia.org/wiki/_()
« — »


: https://en.wikipedia.org/wiki/Multiple_dispatch#Use_in_practice
, 13–32% (a.k.a. ), 2.7–6.5% — . : , , , . , , .


, — , . , , . , double char, , double .


float a = 2.0;
float *src = &a;
char *dest = malloc(sizeof(float));
memcpy(dest, src, sizeof(float));
printf("%f", *(double*)dest);

, (, , ) — - , , , .


, ( ) . : «a = b + c», , . ?


  • : «if (Py_TYPE(obj) == &PyLong_Type) {long val = PyLong_AsLong...}» type();
  • : «PyArg_ParseTuple()» , «this.number = int(param1); this.text = str(param2)» — , . , , ( , );
  • // . — .

, . , ? CPython, , , , , . , : . , ? : , / , / . C «a = b + c» ( , ):


def PyNumber_Add(b, c):
  slotb = b.__add__
  if not type(b) is type(c) and c.__add__:
    slotc = c.__add__
    if slotc is slotb:
      slotc = None
  if slotb:
    if slotc and isinstance(c, b.__class__):
      return slotc(b, c)
    else:
      return slotb(b, c)
  else:
    return slotb(b, c)

if isinstance(b, str) and isinstance(c, str):
  a = unicode_concatenate(b, c)
else:
  a = PyNumber_Add(b, c)

, , . « » — , , , , . , , , « », «», «», ; — .


: , . , . : , . , , — .


, . , , . , - , .


, , ( ), ( ), . , , ( ). , , — , ? .


— : , . , , . , , - , , : . , , — MRO:
https://www.python.org/download/releases/2.3/mro/
https://ru.wikipedia.org/wiki/C3-


, 3.4 « » ( Argument — Monty Python ), . « »  , : 3.4 2014, 1991.


. , (trait) Rust ( , , , , ):
https://doc.rust-lang.org/1.8.0/book/traits.html
, , , , , . «». , , . , - , , Rust-. , __iter__ — «». , , , , . , « - , ». , , C++ ranges, , , .
:


from collections.abc import Iterable, Container
from itertools import filterfalse

class MyList(Trait, Iterable, Container):
  pass

def __sub__(a: MyList, b: object):
  return list(filterfalse(lambda x: x == b, a))

def __sub__(a: MyList, b: Container):
  return list(filterfalse(lambda x: x in b, a))

a = MyList([1, 2, 3, 4, 5])
print(a - [2, 5]) #  , print(__sub__(a, b))
# : [1, 3, 4]
print(a - 3)
# : [1, 2, 4, 5]

MyList, Iterable ( __iter__) Container ( __contains__), list, list MyList, MyList list, list MyList. :


from collections.abc import Container
from itertools import filterfalse

class MyList(list):
  def __sub__(self, b):
    if isinstance(b, Container):
      return list(filterfalse(lambda x: x in b, a))
    else:
      return list(filterfalse(lambda x: x == b, a))

a = MyList([1, 2, 3, 4, 5])
print(a - [2, 5])
# : [1, 3, 4]
print(a - 3)
# : [1, 2, 4, 5]

: , «a», . « », -, .


, — , , , . , , .



, , . , , , , , , . - , — - , . :


>>> a = [1, 2, 3]
...
>>> a = '15'
...
>>> for i in map(lambda x: x*2, a):
>>>    print(i)
11
55


2
4
6

.


, , . None — , NoneType. , — , - , . :


>>> class A():
>>>   def __init__(self, value):
>>>     self.val = value
>>>
>>> a = A('2')
>>> a.val = []
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only assign str (not "list") to str
>>> a.myattr = []
>>> a.myattr = 2

A val, . , - (myattr) — , , .


, . — , , . , :


>>> class A():
>>>   def __init__(self, value):
>>>     self.val = value
>>>
>>> def func():
>>>   a = A(None)
>>>   a.val = 2
>>>   print(a.__dict__)
>>>
>>> func()
{'val': 2}

A<None or Int>, . , , .


: , : , , , «-», «». — , ; - , , , , . , , «list», , , list comprehension, , , BUILD_LIST LIST_APPEND ( CPython) — ? , « », « - ».


, - () , «a.val = int(newval)». , , , . , __setattr__ __setattribute__, c 2.2 __set__ ( https://www.python.org/dev/peps/pep-0252/ ). : , , — C++/Java/C#. , : __set__, __get__, __delete__, , :


>>> a = StrictDict({'first': 1 })
>>> a = { 'dummy': 666 }
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StrictDictError: "first" key is missing in the assignment source

, ( « »): copy-on-write , , -, copy-on-write :


>>> a = COWList([1, 2, 3])
>>> b = a
>>> a.append(4)
>>> b.append(5)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 5]

, CPython , «»:
https://github.com/python/cpython/blob/master/Objects/typeobject.c#L5074
, __slots__ . , , , , , , , , . ( ) . , : __slots__ , PyHeapTypeObject->ht_slots, __dict__, PyTypeObject->tp_dictoffset. , .



, , . , , , , , « , ; — "", ""», . , , . «<> = new <>()», «var = new <>()». , ReasonML , , — JS , , .


PyPy, V8 JavaScript LuaJIT, , . - , , . AOT , asm.js, WebAssembly, PNaCl.


:


  1. Bauer, A.M. and Saal, H.J. (1974). Does APL really need run-time checking? Software — Practice and Experience 4: 129–138.
  2. Kaplan, M.A. and Ullman, J.D. (1980). A scheme for the automatic inference of variable types. J. A CM 27(1): 128–145.
  3. Borning, A.H. and Ingalls, D.H.H. (1982). A type declaration and inference system for Smalltalk. In Conference Record of the Ninth Annual ACM Symposium on Principles of Programming Languages (pp. 133–141)
  4. https://ru.wikipedia.org/wiki/Standard_ML — 1984 .

, Standard ML , , .
, - , . , , — , , ( ), :


  1. Frank Pfenning. (1988). Partial polymorphic type inference and higher-order unification. In Proceedings of the 1988 ACM Conference on Lisp and Functional Programming, pp. 153–163
  2. Cardelli, Luca; Martini, Simone; Mitchell, John C.; Scedrov, Andre (1994). An extension of system F with subtyping. Information and Computation, vol. 9. North Holland, Amsterdam. pp. 4–56
  3. Benjamin C. Pierce, and David N. Turner. (1997). Local type inference. Indiana University CSCI Technical Report #493, pp. 1-25

    (1998) Local type inference. POPL '98 Proceedings of the 25th ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pp. 252-265

    (2000). Local type inference. ACM Transactions on Programming Languages and Systems (TOPLAS). Vol. 22(1), pp. 1-44

— , Scala, 2001 .


1991 , 1994 — , 1995 — «Matz», . , , . , , — , , , , , ZeroMQ, RabbitMQ, Kafka. , , , , , . , ? . - , Crystal, , .



— . , , .

Source: https://habr.com/ru/post/de481782/


All Articles