- ¿Cuántos arquitectos necesitas para implementar un lenguaje de programación?
- cien. Uno escribirá la implementación y 99 dirá qué pueden hacer mejor.
En este artículo, quiero cubrir no tanto el lenguaje en sí como los detalles de la implementación de CPython y su biblioteca estándar, que aseguran que no tendrá formas fáciles de hacer que la aplicación Python sea multiproceso, rápida o fácil de soportar, y por qué se creó tanto implementaciones alternativas (PyPy, Cython, Jython, IronPython, Python para .NET, Parakeet, Nuitka, Stackless, Unladen Swallow), la mitad de las cuales ya han muerto; y pocos entendieron por qué las alternativas no tenían posibilidades de ganar la lucha por la supervivencia contra otros idiomas. Sí, hay GDScript, que está diseñado para resolver problemas de rendimiento, está Nim, que está diseñado para resolver todos los problemas en general, sin requerir que el usuario declare tipos explícitamente. Sin embargo, dada la enorme inercia de la industria, me doy cuenta de que en los próximos 10 años, los nuevos idiomas definitivamente no ocuparán un nicho significativo. Sin embargo, creo que Python puede hacerse efectivo cambiando el estilo de escritura del código, en su mayor parte conservando la sintaxis original y preservando completamente la posibilidad de interacción entre el código nuevo y el antiguo. Me centraré en los problemas de CPython, no en su competidor más cercano, PyPy, ya que PyPy en realidad salta alrededor de los mismos problemas de CPython.
Soy un programador con siete años de experiencia, principalmente dedicado al desarrollo de aplicaciones de escritorio, con cierto énfasis en la web y bases de datos multiproceso. Usted pregunta, "espere un minuto, pero ¿qué tiene Python en común con la interfaz de usuario, los frentes web y el subprocesamiento múltiple?". Y responderé "eso es todo, nada". Usé C, Delphi, JavaScript y SQL para mis tareas. No estaba muy satisfecho con esta situación, y hace un tiempo intenté participar en el proyecto de Eric Snow para implementar el soporte para múltiples intérpretes en CPython:
https://www.python.org/dev/peps/pep-0554/
https://github.com/ericsnowcurrently/multi-core-python
Desafortunadamente, el entendimiento llegó rápidamente que:
- CPython tiene un soporte bastante pobre para un proyecto tan popular, y tiene un montón de viejos problemas que surgen al intentar volver a dibujar la implementación. Como resultado, Eric ha estado eligiendo al intérprete con progreso variable durante varios años;
- incluso después de la implementación exitosa de múltiples intérpretes, no está claro cómo organizar aún más la ejecución paralela. PEP sugiere usar canales simples, pero esta herramienta se vuelve peligrosa a medida que la tarea se vuelve más compleja, con amenazas de congelamiento y comportamiento impredecible;
- el lenguaje en sí tiene grandes problemas que impiden que los intérpretes intercambien datos directamente y brindan cierta garantía de comportamiento predecible.
Ahora con más detalle sobre los problemas.
Definiciones de clase modificables
Sí, entiendo que una clase en python se declara en tiempo de ejecución. Pero maldita sea, ¿por qué poner variables en él? ¿Por qué agregar nuevos métodos a objetos antiguos? No puede declarar funciones y variables fuera de las clases en ningún Java, pero no existe tal restricción en python (y python se creó antes de Java). Además, le pido que preste atención a cómo necesita doblegarse con cáncer para agregar métodos similares al objeto en sí, y no a la clase, esto requiere tipos.MethodType, function .__ get__, functools.partial, etc.
Para empezar, me gustaría hacer una pregunta extraña: ¿por qué las pitones necesitan métodos? No funciones, como en JavaScript cercano, sino métodos de clase. Uno de los factores: Guido no encontró mejores formas de hacer nombres cortos de funciones (para que no haya gtk_button_set_focus_on_click), ya que no está claro cómo seleccionar las funciones necesarias para este objeto específico de un conjunto de funciones similares con un nombre corto. Sin embargo, len, iter, next, isinstance, slice, dict, dir, str, repr, hash, type aparecieron en python; ahora estos son envoltorios sobre los métodos de clase correspondientes con guiones bajos en el nombre, y una vez incorporados, los tipos simples no eran clases y trabajó solo a través de estas funciones. Personalmente, no veo mucha diferencia entre la grabación del método (objeto) y el método object.method, especialmente si el método es una función estática, que, en general, no le importa qué primer argumento (self) aceptar.
Definiciones de clase dinámica en el caso general:
- No realice pruebas modulares. Un código correctamente elaborado en la prueba puede dar un error cuando todo el sistema está funcionando, y no estará protegido de esto dentro de CPython;
- Crear grandes dificultades de optimización . Una declaración de clase no le garantiza el funcionamiento real de la clase. Por esta razón, el único proyecto optimizador exitoso de PyPy utiliza el rastreo para detectar la secuencia real de acciones realizadas por el método de la sonda;
- No acople con ejecución de código paralelo . Por ejemplo, el mismo multiprocesamiento funciona con copias de las definiciones de clase y, si Dios no lo permite, cambie la descripción de las clases en una de las copias, entonces su aplicación corre el riesgo de desmoronarse.
Una versión más sutil de las clases dinámicas está anulando el acceso a los atributos a través de __getattribute__, __getattr__ y otros. A menudo se usan como getter-setters regulares, para delegar funciones a un objeto de campo y ocasionalmente para organizar un DSL . Todas estas funciones pueden implementarse de una manera más civilizada, sin convertir la clase en un basurero de descripciones, cuyo comportamiento a veces es difícil de garantizar. Por cierto, en el caso de getters / setters, dicho mecanismo ya existe; estos son los descriptores de atributos: https://www.python.org/dev/peps/pep-0252/#id5
El intercambio en caliente de clases es necesario para la depuración y la codificación común, pero aún así debe ser una herramienta especializada para el desarrollador, y no un artefacto en tiempo de ejecución que no pueda eliminarse.
Las clases compiladas son solo un pequeño paso que Cython y Nuitka ya han dado, pero este paso solo sin otros cambios no es suficiente para obtener un efecto significativo incluso en términos de velocidad de ejecución, porque, por ejemplo, python hace un uso extensivo de la vinculación de variables dinámicas, que no está en ninguna parte no desaparece en código compilado.
Herencia múltiple
Creo que este es el líder del desfile de sombreros. Ni siquiera está al nivel de las funciones C en la implementación de Python y sus extensiones. "¿Pero qué pasa con las interfaces?" Usted objeta. Se necesitan interfaces en C ++ y Java en la función de declarar protocolos para llamar a métodos de un objeto con el propósito de verificación posterior estática de estos protocolos en la compilación, así como para generar tablas de métodos que serán utilizados en tiempo de ejecución por otro código que no sabe nada sobre el objeto fuente. Estos roles se pierden casi por completo en Python, por lo tanto, no existe justificación para su existencia. Me gusta la forma en que se hacen las interfaces en Go: es muy similar a Python ABC: https://www.python.org/dev/peps/pep-3119
La herencia múltiple no es un problema directo para la paralelización y la optimización, pero complica la legibilidad y la facilidad de mantenimiento del código: este es el llamado código de lasaña (similar al código de espagueti).
Generadores
Este es un caso realmente descuidado de GoTo, cuando la ejecución no solo salta incontrolablemente sobre el código, sino que salta sobre las pilas. Un juego particularmente feroz ocurre cuando los generadores se cruzan con los administradores de contexto (hola PEP 567). Si hay una tendencia general en Python a enredar la aplicación en una bola estrecha de estados variables conectados que no dan lugar a pruebas, paralelización y optimización del programa, entonces los generadores son una guinda en este pastel.
¿Cuál crees que será el resultado del programa?
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.
:
- Bauer, A.M. and Saal, H.J. (1974). Does APL really need run-time checking? Software — Practice and Experience 4: 129–138.
- Kaplan, M.A. and Ullman, J.D. (1980). A scheme for the automatic inference of variable types. J. A CM 27(1): 128–145.
- 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)
- https://ru.wikipedia.org/wiki/Standard_ML — 1984 .
, Standard ML , , .
, - , . , , — , , ( ), :
- 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
- 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
- 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, , .
— . , , .