Coisas para lembrar para um programador que muda para Python

Era uma vez, nos meus dias de estudante, fui picado por uma píton, embora o período de incubação tenha sido atrasado e acabei me tornando um programador de pérolas.


No entanto, em algum momento, a pérola se esgotou e eu decidi pegar o python. No começo, fiz algo e descobri o que era necessário para essa tarefa. Depois, percebi que precisava de algum tipo de conhecimento sistemático e li vários livros:


  • Bill Lyubanovich “Python Simples. Estilo moderno de programação ”
  • Dan Bader "Python puro. As sutilezas da programação para profissionais »
  • Brett Slatkin “Segredos do Python: 59 dicas para escrever código eficaz”

O que me pareceu bastante adequado para entender as sutilezas básicas do idioma, embora não me lembre de mencionar espaços neles, mas não tenho certeza de que esse seja um recurso realmente necessário - se eu o pressionei de memória, provavelmente esse método não será suficiente, mas é claro tudo depende da situação.


Como resultado, acumulei algumas anotações sobre os recursos do python que, ao que me parece, podem ser úteis para quem deseja migrar para ele de outros idiomas.


Notei que, durante entrevistas em python, muitas vezes as pessoas fazem perguntas sobre coisas que não estão relacionadas ao desenvolvimento real, como o que pode ser a chave do dicionário (ou o que x = yield y significa), caras, na vida real, a chave pode ser apenas um número ou uma string; nesses casos únicos, quando não é assim, você pode ler a documentação e descobrir por que perguntar isso? Para descobrir o que o entrevistado não sabe? Portanto, no final, todos se lembrarão da resposta a essa pergunta em particular e ela deixará de funcionar.


Considero as versões python superiores a 3,5 relevantes ( é hora de esquecer o segundo python por um longo tempo ), pois esta é a versão no debian estável, o que significa que em todos os outros lugares existem versões mais recentes)


Como não sou um guru de python, espero que eles me corrijam nos comentários se de repente eu congelar algum tipo de estupidez.


Digitação


Python é uma linguagem de tipo dinâmico, ou seja, verifica a correspondência de tipos em tempo de execução, por exemplo:


 cat type.py a=5 b='5' print(a+b) 

execute:


 python3 type.py ... TypeError: unsupported operand type(s) for +: 'int' and 'str' 

No entanto, se o seu projeto amadureceu de acordo com a necessidade de digitação estática, o python também oferece essa oportunidade usando o analisador estático mypy :


 mypy type.py type.py:3: error: Unsupported operand types for + ("int" and "str") 

É verdade que nem todos os erros são capturados desta maneira:


 cat type2.py def greeting(name): return 'Hello ' + name greeting(5) 

mypy não vai jurar aqui, mas ocorrerá um erro durante a execução; portanto, as versões atuais do python suportam sintaxe especial para especificar tipos de argumentos de função:


 cat type3.py def greeting(name: str) -> str: return 'Hello ' + name greeting(5) 

e agora:


 mypy type3.py type3.py:4: error: Argument 1 to "greeting" has incompatible type "int"; expected "str" 

Variáveis ​​e Dados


Variáveis ​​em python não armazenam dados, mas apenas se referem a eles, e os dados podem ser mutáveis ​​(mutáveis) e imutáveis ​​(imutáveis).
Isso leva a um comportamento diferente, dependendo do tipo de dados em situações quase idênticas, por exemplo, um código como esse:


 x = 1 y = x x = 2 print(y) 

leva ao fato de que as variáveis x e y referem a dados diferentes, e isso:


 x = [1, 2, 3] y = x x[0] = 7 print(y) 

não, x e y permanecem links para a mesma lista (embora, como observado nos comentários, o exemplo não tenha sido muito bem-sucedido, mas ainda não pensei em um melhor), a propósito, em python, você pode verificar com o operador is (tenho certeza que o criador do Java perderá para sempre um bom sono de vergonha quando descobri esse operador em python).


Embora as linhas pareçam uma lista, elas são um tipo de dados imutáveis, isso significa que a própria sequência não pode ser alterada, você só pode criar uma nova, mas pode atribuir um valor diferente à variável, embora os dados originais não sejam alterados:


 >>> mystr = 'sss' >>> newstr = mystr #       >>> mystr[0] = 'a' ... TypeError: 'str' object does not support item assignment >>> mystr = 'ssa' #    >>> newstr #         'sss' 

Falando em strings, devido à sua imunidade, concatenar uma lista muito grande de strings adicionando ou anexando em um loop pode não ser muito eficaz (dependendo da implementação em um compilador / versão em particular), geralmente, nesses casos, é recomendável usar o método join , que se comporta um pouco inesperado:


 >>> str_list = ['ss', 'dd', 'gg'] >>> 'XXX'.join(str_list) 'ssXXXddXXXgg' >>> str = 'hello' >>> 'XXX'.join(str) 'hXXXeXXXlXXXlXXXo' 

Primeiro, a linha na qual o método é chamado se torna um separador, e não o início de uma nova linha, como se pode pensar; em segundo lugar, você precisa passar uma lista (um objeto iterável) e não uma linha separada, porque também é um objeto iterável e será simbolizado .


Como as variáveis ​​são links, é bastante normal querer fazer uma cópia de um objeto para não quebrar o objeto original, mas existe uma armadilha - a função de cópia copia apenas um nível, o que claramente não é o que se espera de uma função com esse nome, portanto, use deepcopy .


Um problema semelhante com a cópia pode ocorrer quando uma coleção é multiplicada por um escalar, conforme explicado aqui .


Âmbito de aplicação


O tópico do escopo provavelmente merece um artigo separado, mas há uma boa resposta para o SO .
Em resumo, o escopo é lexical e existem seis áreas de visibilidade - variáveis ​​no corpo da função, no encerramento, no módulo, no corpo da classe, funções python internas e variáveis ​​na lista e outras inclusões.
Há uma sutileza - a variável padrão é legível em namespaces anexados anexados, mas a modificação requer o uso de palavras-chave especiais nonlocal - nonlocal e global para modificar as variáveis ​​em um nível mais alto ou em visibilidade global, respectivamente.


Por exemplo, um código como este:


 x = 7 print(id(x)) def func(): print(id(x)) return x print(func()) 

Funciona com uma variável global, e esta:


 x = 7 print(id(x)) def func(): x = 1 print(id(x)) return x print(func()) print(x) 

já gera um local.
Do meu ponto de vista, isso não é muito bom; em princípio, qualquer uso de variáveis ​​não locais em uma função faz parte da interface pública da função, sua assinatura, o que significa que ela deve ser declarada explicitamente e visível no início da função. Além disso, as palavras-chave não são muito informativas - global soa como uma definição de uma função global, mas na verdade significa use global .


No python, não existe um ponto de entrada obrigatório a partir do qual o programa é iniciado, como é feito em muitas linguagens, apenas tudo o que é escrito no nível do módulo é executado sequencialmente, no entanto, como as variáveis ​​no nível do módulo são variáveis ​​globais, do meu ponto de vista, deve ser uma boa prática. colocando o código principal na função main() , seguido por sua chamada no final do arquivo:


 if __name__ == '__main__': main() 

essa condição funcionará se o arquivo for chamado como um script e não importado como um módulo.


Argumentos de Função


O Python oferece oportunidades simplesmente elegantes para definir argumentos de função - argumentos posicionais, nomeados e suas combinações.


Mas você precisa entender como os argumentos são passados ​​- porque em python, todas as variáveis ​​são links para dados, então você pode adivinhar que a transferência é por referência, mas há uma peculiaridade - o próprio link é passado por valor, ou seja, você pode modificar o valor mutável por referência:


 def add_element(mylist): mylist.append(3) mylist = [1,2] add_element(mylist) print(mylist) 

execute:


 python3 arg_modify.py [1, 2, 3] 

no entanto, você não pode substituir o link original em uma função:


 def try_del(mylist): mylist = [] return mylist mylist = [1,2] try_del(mylist) print(mylist) 

o link de origem está vivo e funcionando:


 python3 arg_kill.py [1, 2] 

Você também pode definir valores padrão para os argumentos, mas há uma coisa não óbvia a ser lembrada: os valores padrão são calculados uma vez ao definir a função, isso não cria problemas se você passar dados inalterados como valor padrão e se você passar dados variáveis ​​ou valor dinâmico, o resultado será um pouco inesperado:


dados mutáveis:


 cat arg_list.py def func(arg = []): arg.append('x') return arg print(func()) print(func()) print(func()) 

resultado:


 python3 arg_list.py ['x'] ['x', 'x'] ['x', 'x', 'x'] 

valor dinâmico:


 cat arg_now.py from datetime import datetime def func(arg = datetime.now()): return arg print(func()) print(func()) print(func()) 

nós obtemos:


 python3 arg_now.py 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879 2018-09-28 10:28:40.771879 

OOP


OOP em python foi feito de maneira muito interessante (algumas propriedades valem a pena) e esse é um tópico importante, mas os sapiens familiarizados com o OOP podem pesquisar no google tudo (ou encontrá-lo no hub ), então ele não precisa repeti-lo, embora valha a pena dizer que o python deve ser um pouco uma filosofia diferente - é que um programador inteligente máquinas, e não é uma ameaça (UPD: mais ), de modo que o padrão python não é habitual para outros modificadores de acesso idiomas: métodos privados implementadas pela adição de um sublinhado duplo (que muda o tempo de execução do nome do método não é OAPC Tópico chance de usá-lo), e protegida um sublinhado (que não faz nada, é apenas uma convenção de nomenclatura).
Aqueles que perdem a funcionalidade usual podem procurar tentativas de trazer essas oportunidades para o python, algumas opções ( lang , acesso ao python ) foram pesquisadas por mim, mas eu não as testei ou estudei.


O único ponto negativo das classes padrão é o código padrão em qualquer método Dunder , eu pessoalmente gosto da biblioteca attrs , é muito mais python.
Vale ressaltar que, como no Python todos os objetos, incluindo funções e classes, as classes podem ser criadas dinamicamente (sem o uso de eval ) pela função type .
Também vale a pena ler sobre metaclasses ( no Habr ) e descritores ( Habr ).
Uma peculiaridade que vale a pena lembrar é que os atributos de uma classe e um objeto não são a mesma coisa; no caso de atributos imutáveis, isso não causa problemas, pois os atributos são "sombreados" - os atributos do objeto com o mesmo nome são criados automaticamente, mas no caso de atributos mutáveis, você pode não obtenha exatamente o que era esperado:


 cat class_attr.py class MyClass: storage = [7,] def __init__(self, number): self.number = number obj = MyClass(1) obj2 = MyClass(2) obj.number = 5 obj.storage.append(8) print(obj2.storage, obj2.number) 

nós obtemos:


 python3 class_attr.py [7, 8] 2 

como você pode ver - eles mudaram obj e o storage mudou no obj2 . esse atributo (diferente do number ) não pertence à instância, mas à classe.


Constantes


Como no caso de modificadores de acesso, o python não tenta limitar o desenvolvedor, portanto, é impossível definir uma variável escalar protegida contra modificação da maneira padrão; existe simplesmente um acordo de que variáveis ​​com um nome em maiúsculas devem ser consideradas constantes.
Por outro lado, o Python possui estruturas de dados imutáveis, como tupla. Portanto, se você deseja tornar alguma estrutura global como uma configuração imutável e não quiser dependências adicionais, o nome de usuário duplo é uma boa opção, embora exija um pouco mais de esforço para descrever os tipos, portanto Gosto da implementação alternativa da estrutura imutável com notação de ponto - Box (consulte o parâmetro frozen_box).
Bem, se você quiser constantes escalares, poderá implementar o controle de acesso no estágio de "compilação", ou seja, verifica mypy, exemplo e detalhes .


.sort () vs classificado ()


Existem duas maneiras de classificar uma lista em python. O primeiro é o método .sort() que modifica a lista original e não retorna nada (Nenhum), ou seja, não pode fazer isso:


 my_list = my_list.sort() 

A segunda é a função sorted() , que gera uma nova lista e pode trabalhar com todos os objetos iteráveis. Quem quiser mais informações deve começar com SO .


Biblioteca padrão


Geralmente, a biblioteca python padrão inclui excelentes soluções para problemas comuns, mas vale a pena ser crítica, porque há esquisitices suficientes. É verdade, também acontece que o que parece estranho à primeira vista acaba sendo a melhor solução, você só precisa conhecer todas as condições (veja abaixo o alcance), mas ainda existem esquisitices.


Por exemplo, o módulo de unidade mais unido incluído no kit não tem nada a ver com python e cheira a Java, portanto, como o autor do python diz : "Todo mundo está usando py.test ...". Embora bastante interessante, embora nem sempre seja adequado, o módulo doctest vem como padrão.


O módulo urllib fornecido não possui uma interface tão bonita quanto o módulo de solicitações de terceiros.


A mesma história com o módulo para analisar os parâmetros da linha de comando - o argparse incluído é uma demonstração do POO do cérebro, e o módulo docopt parece ser apenas uma solução inteligente - a auto-documentação final! Embora, de acordo com os rumores, apesar do docopt e do clique, um nicho permaneça.


Também com o depurador - como eu o entendo, poucas pessoas usam o pdb incluído , existem muitas alternativas, mas parece que a maioria dos desenvolvedores usa o ipdb , que, do meu ponto de vista, é mais conveniente de usar através do módulo do wrapper de depuração .
Ele permite que, em vez de import ipdb;ipdb.set_trace() escreva simplesmente import debug , ele também adiciona um módulo see para facilitar a inspeção de objetos.


Para substituir o módulo de serialização padrão, o pickle é feito de endro , a propósito, vale lembrar que esses módulos não são adequados para a troca de dados em sistemas externos, pois restaurar objetos arbitrários recebidos de uma fonte não controlada não é seguro; nesses casos, existem json (para REST) ​​e gRPC (para RPC).


Para substituir o módulo de processamento de expressão regular padrão, refaz o módulo regex com todos os tipos de itens adicionais, como as classes de caracteres ala \p{Cyrillic} .
A propósito, algo não ocorreu para o python, um depurador divertido para expressões regulares semelhantes à cevada .


Aqui está outro exemplo - uma pessoa criou seu módulo no local para corrigir a curvatura e a incompletude da API do módulo de entrada de arquivo padrão na parte de edição do arquivo.


Bem, eu penso muito nesses casos, já que me deparei com mais de um, portanto, tenha cuidado e não se esqueça de olhar para todos os tipos de listas impressionantes úteis, acho que um bom nutricionista tem um cheiro da melhor maneira possível, este é, a propósito, um tópico para outra discussão - de acordo com meus sentimentos (é claro, não há estatísticas sobre esse assunto e aparentemente não pode ser) no mundo python, o nível de especialistas está acima da média, porque muitas vezes bons softwares acabam sendo escritos em python, escreva nos comentários o que você pensa sobre isso.


Concorrência e Concorrência


O Python oferece amplas oportunidades para programação paralela e competitiva, mas não sem recursos.


Se você precisar de paralelismo, e isso acontecer quando suas tarefas exigirem computação, preste atenção ao módulo de multiprocessamento .


E se suas tarefas têm muitas expectativas de E / S, o python oferece várias opções para escolher, de threads e gevent , a assíncio .
Todas essas opções parecem bastante adequadas para uso (embora os threads exijam muito mais recursos), mas há uma sensação de que o assíncio está lentamente espremendo o resto, inclusive graças a todos os tipos de guloseimas, como o uvloop .


Se alguém não notou - em python, os threads não são sobre paralelismo, não sou competente o suficiente para falar bem sobre o GIL , mas há material suficiente sobre esse tópico, portanto, não existe essa necessidade, o principal a lembrar é que os threads em python (mais precisamente no CPython) eles se comportam de maneira diferente de outras linguagens de programação - são executados em apenas um núcleo, o que significa que não são adequados para casos em que você precisa de um paralelismo real; no entanto, a execução do encadeamento é interrompida ao aguardar a entrada / saída, para que possam ser usados competir.


Outras esquisitices


Em python, a = a + b nem sempre é equivalente a a += b :


 a = [1] a = a + (2,3) TypeError: can only concatenate list (not "tuple") to list a += (2,3) a [1, 2, 3] 

Estou enviando para a SO para obter detalhes, até encontrar tempo para descobrir por que é assim, no sentido pelo motivo pelo qual eles fizeram isso, como se isso fosse novamente sobre mutabilidade.


Extravagâncias que não são estranhas


À primeira vista, me pareceu estranho que o tipo de faixa não inclua a borda direita, mas uma pessoa gentil me disse para ignorar onde eu precisava aprender e verificou-se que tudo é bastante lógico.


Um grande tópico separado é o arredondamento (embora esse problema seja comum a quase todas as linguagens de programação), além de usar o arredondamento como você quiser, exceto que todos estudaram no curso escolar de matemática, porque os problemas de representação de números de ponto flutuante ainda estão sobrepostos a ele, refiro-me a artigo detalhado.
Grosso modo, em vez do algoritmo comum de meia- volta para a matemática escolar, é usado o algoritmo de meio-par , que reduz a probabilidade de distorção na análise estatística e, portanto, é recomendado pelo padrão IEEE 754.


Além disso, eu não conseguia entender por que -22//10=-3 , e então outra pessoa amável apontou que isso inevitavelmente decorre da própria definição matemática, segundo a qual o restante não pode ser negativo, o que leva a um comportamento tão incomum. números negativos.
ACHTUNG! Agora, isso é novamente uma coisa estranha e eu não entendo nada, veja esta discussão .


Depuração de Expressão Regular


E aqui aconteceu que no mundo python não há ferramenta para depurar interativamente expressões regulares semelhantes ao excelente módulo pearl Regexp :: Debugger ( apresentação em vídeo ), é claro que existem várias ferramentas on-line, existem alguns tipos de soluções proprietárias do Windows, mas para mim não é isso, pode valer a pena usar uma ferramenta de barra de pérola, porque python rexes não são muito diferentes de barra de pérola, escreverei uma instrução para aqueles que não possuem barra de pérola:


 sudo apt install cpanminus cpanm Regexp::Debugger perl -I ~/perl5/lib/perl5/ -E "use Regexp::Debugger; 'ababc' =~ /(a|b) b+ c/x" 

Acho que mesmo uma pessoa não familiarizada com a pérola entenderá onde é necessário inserir a linha e onde está a expressão regular, x é uma bandeira semelhante ao python re.VERBOSE.
Pressione se passe pela expressão regular, uma descrição detalhada dos comandos disponíveis na documentação .


A documentação


Existe uma função de ajuda no python, que permite obter ajuda sobre qualquer função carregada (tirada de sua documentação), o nome da função é passado como parâmetro:


 $ python3 >>> help(help) 

mas essa nem sempre é uma maneira conveniente e geralmente é mais conveniente usar o utilitário pydoc:


 pydoc3 urllib.parse.urlparse 

o utilitário permite pesquisar por palavras-chave e até iniciar um servidor local com documentação html, mas não testei o último.

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


All Articles