Tentang Masalah Penerjemah Python dan Memikirkan Kembali Bahasa

- Berapa banyak arsitek yang Anda butuhkan untuk mengimplementasikan bahasa pemrograman?
- Seratus. Seseorang akan menulis implementasinya, dan 99 akan mengatakan apa yang dapat mereka lakukan dengan lebih baik.


Pada artikel ini, saya ingin menyentuh tidak hanya pada bahasa itu sendiri, tetapi juga pada detail implementasi CPython dan pustaka standarnya, yang memastikan bahwa Anda tidak akan memiliki cara mudah untuk membuat aplikasi Python menjadi multithread, cepat, atau mudah didukung, dan mengapa begitu banyak yang dibuat implementasi alternatif (PyPy, Cython, Jython, IronPython, Python untuk .NET, Parkit, Nuitka, Stackless, Unladen Swallow), setengahnya sudah mati; dan hanya sedikit yang mengerti mengapa alternatif itu tidak memiliki kesempatan untuk memenangkan perjuangan untuk bertahan hidup melawan bahasa lain. Ya, ada GDScript, yang dirancang untuk menyelesaikan masalah kinerja, ada Nim, yang dirancang untuk menyelesaikan semua masalah secara umum, tanpa mengharuskan pengguna untuk secara eksplisit menyatakan jenis. Namun, mengingat inersia industri yang sangat besar, saya menyadari bahwa dalam 10 tahun ke depan, bahasa-bahasa baru pasti tidak akan menempati ceruk yang signifikan. Namun, saya percaya bahwa python dapat dibuat efektif dengan mengubah gaya penulisan kode, untuk sebagian besar mempertahankan sintaks asli, dan sepenuhnya menjaga kemungkinan interaksi antara kode gaya lama dan baru. Saya akan fokus pada masalah CPython, bukan pesaing terdekatnya, PyPy, karena PyPy benar-benar melompati semua masalah yang sama dari CPython.


Saya seorang programmer dengan tujuh tahun pengalaman, terutama terlibat dalam pengembangan aplikasi desktop, dengan beberapa penekanan pada web dan database multi-threaded. Anda bertanya, "tunggu sebentar, tapi apa kesamaan python dengan antarmuka pengguna, front web, dan multithreading?". Dan saya akan menjawab "itu saja - tidak ada." Saya menggunakan C, Delphi, JavaScript, dan SQL untuk tugas saya. Saya tidak terlalu senang dengan situasi ini, dan beberapa waktu yang lalu saya mencoba untuk berpartisipasi dalam proyek Eric Snow untuk mengimplementasikan dukungan untuk banyak penerjemah dalam CPython:
https://www.python.org/dev/peps/pep-0554/
https://github.com/ericsnowcurrently/multi-core-python


Sayangnya, pemahaman dengan cepat datang bahwa:


  • CPython agak kurang didukung untuk proyek yang begitu populer, dan memiliki banyak masalah lama yang muncul ketika mencoba menggambar ulang implementasi. Akibatnya, Eric telah memilih penerjemah dengan kemajuan variabel selama beberapa tahun;
  • bahkan setelah implementasi multi penerjemah yang berhasil, tidak jelas bagaimana mengatur eksekusi paralel lebih lanjut. PEP menyarankan menggunakan saluran sederhana, tetapi alat ini menjadi berbahaya karena tugasnya menjadi lebih kompleks, dengan ancaman pembekuan dan perilaku yang tidak terduga;
  • bahasa itu sendiri memiliki masalah besar mencegah penafsir dari bertukar data secara langsung dan memberikan jaminan perilaku yang dapat diprediksi.

Sekarang, lebih detail tentang masalahnya.


Definisi kelas yang dapat dimodifikasi


Ya, saya mengerti bahwa kelas dalam python dideklarasikan pada saat runtime. Tapi sialnya, mengapa memasukkan variabel ke dalamnya? Mengapa menambahkan metode baru ke objek lama? Anda tidak bisa mendeklarasikan fungsi dan variabel di luar kelas di Java apa pun, tetapi tidak ada batasan seperti itu di python (dan python dibuat sebelum Java). Selain itu, saya meminta Anda untuk memperhatikan bagaimana Anda perlu membungkuk dengan kanker untuk menambahkan metode serupa ke objek itu sendiri, dan bukan ke kelas - ini membutuhkan tipe. Metode, fungsi. Get___, functools.partial, dan sebagainya.
Sebagai permulaan, saya ingin mengajukan pertanyaan aneh: mengapa ular sanca membutuhkan metode sama sekali? Bukan fungsi, seperti pada JavaScript tutup, tetapi metode kelas. Salah satu faktor: Guido tidak datang dengan cara yang lebih baik untuk membuat nama pendek fungsi (sehingga tidak ada gtk_button_set_focus_on_click), karena tidak jelas bagaimana memilih fungsi yang diperlukan untuk objek spesifik ini dari sekelompok fungsi serupa dengan nama pendek. Namun demikian, len, iter, next, isinstance, slice, dict, dir, str, repr, hash, ketik muncul dengan python - sekarang ini adalah pembungkus atas metode kelas yang sesuai dengan garis bawah dalam nama, dan sekali tipe built-in sederhana tidak kelas dan hanya bekerja melalui fungsi-fungsi ini. Secara pribadi, saya tidak melihat banyak perbedaan antara pencatatan metode (objek) dan metode object.method - terutama jika metode adalah fungsi statis, yang, secara umum, tidak peduli dengan argumen mana (diri) yang harus diterima.
Definisi kelas dinamis dalam kasus umum:


  • Jangan memberikan pengujian modular. Sepotong kode yang dikerjakan dengan benar dalam pengujian dapat memberikan kesalahan ketika seluruh sistem bekerja, dan Anda tidak akan terlindungi dari hal ini di dalam CPython;
  • membuat kesulitan besar dalam pengoptimalan . Deklarasi kelas tidak menjamin operasi kelas yang sebenarnya. Untuk alasan ini, satu-satunya proyek pengoptimal yang berhasil dari PyPy menggunakan pelacakan untuk mendeteksi urutan tindakan aktual yang dilakukan oleh metode penyelidikan;
  • Jangan berlabuh dengan eksekusi kode paralel . Sebagai contoh, multiprosesing yang sama berfungsi dengan salinan definisi kelas, dan jika, Tuhan melarang, mengubah deskripsi kelas di salah satu salinan, maka aplikasi Anda berisiko hancur berantakan.

Versi kelas dinamis yang lebih halus menimpa akses atribut melalui __getattribute__, __getattr__, dan lainnya. Seringkali mereka digunakan sebagai pengambil rutin, untuk mendelegasikan fungsi ke objek bidang, dan kadang-kadang untuk mengatur DSL . Semua fungsi ini dapat diimplementasikan dengan cara yang lebih beradab, tanpa mengubah kelas menjadi tumpukan deskripsi, yang perilakunya terkadang sulit dijamin. Omong-omong, dalam kasus getter / setter, mekanisme seperti itu sudah ada - ini adalah deskriptor atribut: https://www.python.org/dev/peps/pep-0252/#id5


Swapping kelas panas diperlukan untuk debugging dan pengkodean biasa, tetapi masih harus menjadi alat khusus untuk pengembang, dan bukan artefak saat runtime yang tidak bisa dihilangkan.


Kelas yang dikompilasi hanyalah langkah kecil yang telah diambil oleh Cython dan Nuitka, tetapi langkah ini saja, tanpa perubahan lain, tidak cukup untuk mendapatkan efek yang signifikan bahkan dalam hal kecepatan eksekusi, karena, misalnya, python secara ekstensif menggunakan pengikatan variabel dinamis, yang mana-mana tidak hilang dalam kode yang dikompilasi.


Warisan berganda


Saya pikir ini adalah pemimpin parade topi. Bahkan tidak pada tingkat fungsi-C dalam implementasi python itu sendiri dan ekstensi-nya. "Tapi bagaimana dengan antarmuka?" Anda keberatan. Antarmuka dalam C ++ dan Java diperlukan dalam peran mendeklarasikan protokol untuk memanggil metode objek untuk tujuan verifikasi statis selanjutnya dari protokol ini saat dikompilasi, serta untuk menghasilkan tabel metode yang pada saat run time akan digunakan oleh kode lain yang tidak tahu apa-apa tentang objek sumber. Peran ini hampir sepenuhnya hilang dalam python, oleh karena itu tidak ada pembenaran untuk keberadaannya. Saya suka cara antarmuka dibuat di Go - sangat mirip dengan python ABC: https://www.python.org/dev/peps/pep-3119


Multiple inheritance bukan masalah langsung untuk paralelisasi dan optimisasi, tetapi mempersulit keterbacaan dan pemeliharaan kode - ini adalah kode lasagna (dengan analogi dengan kode spaghetti).


Generator


Ini adalah kasus GoTo yang benar-benar terabaikan, ketika eksekusi tidak hanya melompati kode secara tidak terkendali - melompati tumpukan. Terutama permainan sengit terjadi ketika generator bersinggungan dengan manajer konteks (halo PEP 567). Jika ada kecenderungan umum dalam python untuk mengacaukan aplikasi menjadi bola yang ketat dari keadaan yang dapat berubah yang terhubung yang tidak memberikan ruang untuk pengujian, paralelisasi, dan manuver program optimasi, maka generator adalah ceri pada kue ini.


Menurut Anda apa hasil dari program ini:


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


All Articles