- Quantos arquitetos você precisa para implementar uma linguagem de programação?
Cem. Um escreverá a implementação e 99 dirá o que eles podem fazer melhor.
Neste artigo, quero cobrir não apenas a linguagem em si, mas também os detalhes da implementação do CPython e sua biblioteca padrão, o que garante que você não tenha maneiras fáceis de tornar o aplicativo Python multithread, rápido ou facilmente suportado, e por que tanto foi criado implementações alternativas (PyPy, Cython, Jython, IronPython, Python para .NET, Periquito, Nuitka, Stackless, Unladen Swallow), metade das quais já morreram; e poucos entenderam por que as alternativas não tinham chance de vencer a luta pela sobrevivência contra outras línguas. Sim, existe o GDScript, projetado para resolver problemas de desempenho, e o Nim, projetado para resolver todos os problemas em geral, sem exigir que o usuário declare explicitamente os tipos. No entanto, dada a enorme inércia da indústria, percebo que nos próximos 10 anos, os novos idiomas definitivamente não ocuparão um nicho significativo. No entanto, acredito que o python pode ser efetivo alterando o estilo de escrever código, mantendo a sintaxe original e preservando totalmente a possibilidade de interação entre o código novo e o antigo. Vou me concentrar nos problemas do CPython, não no seu concorrente mais próximo, o PyPy, já que o PyPy realmente se destaca dos mesmos problemas do CPython.
Sou programador com sete anos de experiência, principalmente engajado no desenvolvimento de aplicativos de desktop, com alguma ênfase na Web e em bancos de dados multithread. Você pergunta: “espere um minuto, mas o que o python tem em comum com a interface do usuário, as frentes da web e o multithreading?”. E eu responderei "é isso - nada". Eu usei C, Delphi, JavaScript e SQL para minhas tarefas. Não fiquei muito satisfeito com essa situação e, há algum tempo, tentei participar do projeto de Eric Snow para implementar o suporte a vários intérpretes no CPython:
https://www.python.org/dev/peps/pep-0554/
https://github.com/ericsnowcurrently/multi-core-python
Infelizmente, rapidamente entendeu que:
- O CPython é pouco suportado para um projeto tão popular e possui vários problemas antigos que surgem ao tentar redesenhar a implementação. Como resultado, Eric escolheu o intérprete com progresso variável por vários anos;
- mesmo após a implementação bem-sucedida de vários intérpretes, não está claro como organizar ainda mais a execução paralela. O PEP sugere o uso de canais simples, mas essa ferramenta se torna perigosa à medida que a tarefa se torna mais complexa, com ameaças de congelamento e comportamento imprevisível;
- a própria linguagem tem grandes problemas para impedir que os intérpretes troquem dados diretamente e dê alguma garantia de comportamento previsível.
Agora com mais detalhes sobre os problemas.
Definições de classe modificáveis
Sim, entendo que uma classe em python é declarada em tempo de execução. Mas caramba, por que colocar variáveis nele? Por que adicionar novos métodos a objetos antigos? Você não pode declarar funções e variáveis fora das classes em nenhum Java, mas não existe essa restrição no python (e o python foi criado antes do Java). Além disso, peço que você preste atenção em como você precisa se curvar com o câncer para adicionar métodos semelhantes ao próprio objeto, e não à classe - isso requer tipos. MétodoMétodo, função .__ get__, functools.partial, e assim por diante.
Para começar, eu gostaria de fazer uma pergunta estranha: por que os pitães precisam de métodos? Não funciona, como no JavaScript próximo, mas métodos de classe. Um dos fatores: o Guido não encontrou maneiras melhores de criar nomes abreviados de funções (para que não haja gtk_button_set_focus_on_click), pois não está claro como selecionar de um monte de funções semelhantes com um nome abreviado necessário para esse objeto em particular. No entanto, o tipo len, iter, next, isinstance, slice, dict, dir, str, repr, hash apareceu em python - agora são wrappers dos métodos de classe correspondentes com sublinhados no nome e, uma vez que tipos simples internos não eram classes e trabalhou somente através dessas funções. Pessoalmente, não vejo muita diferença entre a gravação do método (objeto) e do objeto. Método - especialmente se o método for uma função estática, que, em geral, não se importa com o primeiro argumento (auto) a ser aceito.
Definições de classe dinâmica no caso geral:
- Não faça testes modulares. Um trecho de código corretamente elaborado no teste pode causar um erro quando todo o sistema está funcionando, e você não estará protegido contra isso no CPython;
- criar grandes dificuldades de otimização . Uma declaração de classe não garante a operação real da classe. Por esse motivo, o único projeto otimizador de sucesso do PyPy usa o rastreamento para detectar a sequência real de ações executadas pelo método de análise;
- Não encaixe com a execução de código paralelo . Por exemplo, o mesmo multiprocessamento funciona com cópias das definições de classe e, se Deus proibir, alterar a descrição das classes em uma das cópias, seu aplicativo corre o risco de desmoronar.
Uma versão mais sutil das classes dinâmicas está substituindo o acesso ao atributo por meio de __getattribute__, __getattr__ e outros. Geralmente, eles são usados como getter-setters regulares, para delegar funções a um objeto de campo e, ocasionalmente, para organizar uma DSL . Todas essas funções podem ser implementadas de uma maneira mais civilizada, sem transformar a classe em um despejo de descrições, cujo comportamento às vezes é difícil de garantir. A propósito, no caso de getters / setters, esse mecanismo já existe - estes são descritores de atributos: https://www.python.org/dev/peps/pep-0252/#id5
A troca a quente de classes é necessária para depuração e codificação comum, mas ainda deve ser uma ferramenta especializada para o desenvolvedor, e não um artefato em tempo de execução que não pode ser eliminado.
As classes compiladas são apenas um pequeno passo que o Cython e o Nuitka já deram, mas esse passo sozinho, sem outras alterações, não é suficiente para obter um efeito significativo, mesmo em termos de velocidade de execução, porque, por exemplo, o python faz uso extensivo da ligação dinâmica de variáveis, que não está em lugar nenhum não desaparece no código compilado.
Herança múltipla
Eu acho que este é o líder da parada de chapéus. Não é nem no nível das funções C na implementação do próprio python e suas extensões. "Mas e as interfaces?" Você se opõe. As interfaces em C ++ e Java são necessárias na função de declarar protocolos para chamar métodos de um objeto para fins de verificação estática subsequente desses protocolos na compilação, bem como para gerar tabelas de métodos que serão usadas em tempo de execução por outro código que não sabe nada sobre o objeto de origem. Essas funções são quase completamente perdidas em python, portanto, não há justificativa para sua existência. Eu gosto da maneira como as interfaces são feitas no Go - é muito semelhante ao python ABC: https://www.python.org/dev/peps/pep-3119
A herança múltipla não é um problema direto para paralelização e otimização, mas complica a legibilidade e a manutenção do código - este é o chamado código de lasanha (por analogia com o código de espaguete).
Geradores
Este é um caso realmente negligenciado do GoTo, quando a execução não pula incontrolavelmente o código - ele pula as pilhas. O jogo particularmente feroz ocorre quando os geradores se cruzam com os gerenciadores de contexto (olá PEP 567). Se houver uma tendência geral no python de envolver o aplicativo em uma bola apertada de estados mutáveis conectados que não dão espaço para manobras de teste, paralelização e otimização de programa, os geradores são a cereja do bolo.
O que você acha que será o resultado do 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, , .
— . , , .