A propos des problĂšmes de traducteur Python et repenser la langue

- De combien d'architectes avez-vous besoin pour implémenter un langage de programmation?
- Cent. L'un écrira l'implémentation et 99 diront ce qu'ils peuvent faire de mieux.


Dans cet article, je ne couvre pas tant le langage lui-mĂȘme que les dĂ©tails de l'implĂ©mentation de CPython et de sa bibliothĂšque standard, ce qui garantit que vous n'aurez aucun moyen simple de rendre l'application Python multithread, rapide ou facilement prise en charge, et pourquoi tant de choses ont Ă©tĂ© crĂ©Ă©es implĂ©mentations alternatives (PyPy, Cython, Jython, IronPython, Python pour .NET, Parakeet, Nuitka, Stackless, Unladen Swallow), dont la moitiĂ© sont dĂ©jĂ  mortes; et peu comprenaient pourquoi les alternatives n'avaient aucune chance de gagner la lutte pour la survie contre d'autres langues. Oui, il y a GDScript, qui est conçu pour rĂ©soudre les problĂšmes de performances, il y a Nim, qui est conçu pour rĂ©soudre tous les problĂšmes en gĂ©nĂ©ral, sans exiger que l'utilisateur dĂ©clare trop explicitement les types. Cependant, Ă©tant donnĂ© l'Ă©norme inertie de l'industrie, je me rends compte qu'au cours des 10 prochaines annĂ©es, les nouvelles langues n'occuperont certainement pas une niche importante. Cependant, je pense que le python peut ĂȘtre rendu efficace en changeant le style d'Ă©criture du code, en conservant pour la plupart la syntaxe d'origine, et en prĂ©servant pleinement la possibilitĂ© d'interaction entre le code nouveau et l'ancien. Je me concentrerai sur les problĂšmes de CPython, pas sur son concurrent le plus proche, PyPy, car PyPy contourne en fait tous les mĂȘmes problĂšmes de CPython.


Je suis programmeur avec sept ans d'expérience, principalement engagé dans le développement d'applications bureautiques, avec un accent particulier sur le web et les bases de données multi-thread. Vous demandez, "attendez une minute, mais qu'est-ce que python a en commun avec l'interface utilisateur, les fronts Web et le multithreading?". Et je répondrai "c'est tout - rien." J'ai utilisé C, Delphi, JavaScript et SQL pour mes tùches. Je n'étais pas trÚs satisfait de cette situation, et il y a un certain temps j'ai essayé de participer au projet d'Eric Snow pour implémenter le support de plusieurs interprÚtes en CPython:
https://www.python.org/dev/peps/pep-0554/
https://github.com/ericsnowcurrently/multi-core-python


Malheureusement, la compréhension est venue rapidement que:


  • CPython est plutĂŽt mal pris en charge pour un projet aussi populaire, et a un tas d'anciens problĂšmes qui surviennent lorsque vous essayez de redessiner l'implĂ©mentation. En consĂ©quence, Eric choisit l'interprĂšte avec des progrĂšs variables depuis plusieurs annĂ©es;
  • mĂȘme aprĂšs la mise en Ɠuvre rĂ©ussie de plusieurs interprĂštes, on ne sait pas encore comment organiser l'exĂ©cution parallĂšle. PEP suggĂšre d'utiliser des canaux simples, mais cet outil devient dangereux Ă  mesure que la tĂąche devient plus complexe, avec des menaces de gel et un comportement imprĂ©visible;
  • la langue elle-mĂȘme a de gros problĂšmes empĂȘchant les interprĂštes d'Ă©changer directement des donnĂ©es et donnant une certaine garantie de comportement prĂ©visible.

Maintenant, plus en détail sur les problÚmes.


DĂ©finitions de classe modifiables


Oui, je comprends qu'une classe en python est dĂ©clarĂ©e au moment de l'exĂ©cution. Mais bon sang, pourquoi y mettre des variables? Pourquoi ajouter de nouvelles mĂ©thodes aux anciens objets? Vous ne pouvez pas dĂ©clarer des fonctions et des variables en dehors des classes dans n'importe quel Java, mais il n'y a pas une telle restriction en python (et python a Ă©tĂ© crĂ©Ă© avant Java). De plus, je vous demande de faire attention Ă  la façon dont vous devez vous pencher contre le cancer afin d'ajouter des mĂ©thodes similaires Ă  l'objet lui-mĂȘme, et non Ă  la classe - cela nĂ©cessite des types.MethodType, function .__ get__, functools.partial, etc.
Pour commencer, je voudrais poser une étrange question: pourquoi avez-vous besoin de méthodes en python? Pas des fonctions, comme dans JavaScript proche, mais des méthodes de classe. L'un des facteurs: Guido n'a pas trouvé de meilleures façons de créer des noms courts de fonctions (de sorte qu'il n'y ait pas de gtk_button_set_focus_on_click), car il n'est pas clair comment sélectionner parmi un tas de fonctions similaires avec un nom court nécessaire pour cet objet spécifique. Néanmoins, len, iter, next, isinstance, slice, dict, dir, str, repr, hash, type sont apparus en python - maintenant ce sont des wrappers sur les méthodes de classe correspondantes avec des soulignements dans le nom, et une fois les types simples intégrés n'étaient pas classes et travaillé uniquement à travers ces fonctions. Personnellement, je ne vois pas beaucoup de différence entre l'enregistrement de méthode (objet) et object.method - surtout si la méthode est une fonction statique, qui, en général, ne se soucie pas du premier argument (soi) à accepter.
Définitions de classe dynamique dans le cas général:


  • Ne faites pas de tests modulaires. Un morceau de code correctement Ă©laborĂ© dans le test peut donner une erreur lorsque tout le systĂšme fonctionne, et vous ne serez pas protĂ©gĂ© contre cela dans CPython;
  • crĂ©er de grandes difficultĂ©s d' optimisation . Une dĂ©claration de classe ne vous garantit pas le fonctionnement rĂ©el de la classe. Pour cette raison, le seul projet d'optimisation rĂ©ussi de PyPy utilise le traçage pour dĂ©tecter la sĂ©quence rĂ©elle des actions effectuĂ©es par la mĂ©thode de sonde;
  • Ne pas ancrer avec l'exĂ©cution de code parallĂšle . Par exemple, le mĂȘme multitraitement fonctionne avec des copies des dĂ©finitions de classe et si, Ă  Dieu ne plaise, changez la description des classes dans l'une des copies, votre application risque de s'effondrer.

Une version plus subtile des classes dynamiques remplace l'accĂšs aux attributs via __getattribute__, __getattr__ et autres. Souvent, ils sont utilisĂ©s comme des getter-setters rĂ©guliers, pour dĂ©lĂ©guer des fonctions Ă  un objet champ, et parfois pour organiser un DSL . Toutes ces fonctions peuvent ĂȘtre implĂ©mentĂ©es de maniĂšre plus civilisĂ©e, sans faire de la classe un dĂ©potoir de descriptions, dont le comportement est parfois difficile Ă  garantir. Soit dit en passant, dans le cas des getters / setters, un tel mĂ©canisme existe dĂ©jĂ  - ce sont des descripteurs d'attributs: https://www.python.org/dev/peps/pep-0252/#id5


L'Ă©change Ă  chaud de classes est nĂ©cessaire pour le dĂ©bogage et le codage banal, mais il doit toujours ĂȘtre un outil spĂ©cialisĂ© pour le dĂ©veloppeur, et non un artefact au moment de l'exĂ©cution qui ne peut pas ĂȘtre Ă©liminĂ©.


Les classes compilĂ©es ne sont qu'une petite Ă©tape que Cython et Nuitka ont dĂ©jĂ  franchies, mais cette Ă©tape seule sans autres changements n'est pas suffisante pour obtenir un effet significatif mĂȘme en termes de vitesse d'exĂ©cution, car, par exemple, python utilise largement la liaison de variable dynamique, qui n'est nulle part ne disparaĂźt pas dans le code compilĂ©.


HĂ©ritage multiple


Je pense que c'est le chef du dĂ©filĂ© de chapeaux. Ce n'est mĂȘme pas au niveau des fonctions C dans l'implĂ©mentation de python lui-mĂȘme et de ses extensions. "Mais qu'en est-il des interfaces?" Vous objectez. Des interfaces en C ++ et Java sont nĂ©cessaires dans le rĂŽle de dĂ©claration des protocoles pour appeler les mĂ©thodes d'un objet Ă  des fins de vĂ©rification statique ultĂ©rieure de ces protocoles lors de la compilation, ainsi que pour gĂ©nĂ©rer des tables de mĂ©thodes qui seront utilisĂ©es au moment de l'exĂ©cution par un autre code qui ne sait rien de l'objet source. Ces rĂŽles sont presque complĂštement perdus en python, il n'y a donc aucune justification Ă  leur existence. J'aime la façon dont les interfaces sont faites dans Go - c'est trĂšs similaire Ă  python ABC: https://www.python.org/dev/peps/pep-3119


L'héritage multiple n'est pas un problÚme direct pour la parallélisation et l'optimisation, mais complique la lisibilité et la maintenabilité du code - c'est le soi-disant code lasagne (similaire au code spaghetti).


Générateurs


C'est un cas vraiment nĂ©gligĂ© de GoTo, lorsque l'exĂ©cution ne saute pas simplement de maniĂšre incontrĂŽlable sur le code - elle saute par-dessus les piles. Un jeu particuliĂšrement fĂ©roce se produit lorsque les gĂ©nĂ©rateurs croisent les gestionnaires de contexte (bonjour PEP 567). S'il y a une tendance gĂ©nĂ©rale en python Ă  emmĂȘler l'application dans une boule serrĂ©e d'Ă©tats mutables connectĂ©s qui ne donnent pas de place aux tests, Ă  la parallĂ©lisation et aux manƓuvres d'optimisation de programme, alors les gĂ©nĂ©rateurs sont une cerise sur le gĂąteau.


Selon vous, quel sera le résultat du programme:


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/fr481782/


All Articles