Dicas úteis sobre Python que você não conheceu

Muitos artigos foram escritos sobre recursos interessantes do Python. Eles falam sobre como descompactar listas e tuplas em variáveis, sobre aplicação parcial de funções, sobre como trabalhar com objetos iteráveis. Mas no Python há muito mais. O autor do artigo que estamos traduzindo hoje diz que deseja falar sobre alguns dos recursos do Python que ele usa. Ao mesmo tempo, ele ainda não encontrou uma descrição dessas possibilidades, semelhante à aqui apresentada. É possível que você não tenha lido sobre eles em nenhum outro lugar.



Limpando dados da sequência de entrada


A tarefa de limpar os dados inseridos pelo usuário é relevante para praticamente qualquer programa. Geralmente, esse processamento de entrada se resume à conversão de caracteres para maiúsculas ou minúsculas. Às vezes, os dados podem ser limpos usando uma expressão regular. Mas nos casos em que a tarefa é complicada, você pode aplicar uma maneira mais bem-sucedida de resolvê-la. Por exemplo - isto:

user_input = "This\nstring has\tsome whitespaces...\r\n" character_map = {  ord('\n') : ' ',  ord('\t') : ' ',  ord('\r') : None } user_input.translate(character_map) # This string has some whitespaces... " 

Aqui você pode ver como os caracteres de espaço em branco "\n" e "\t" são substituídos por espaços regulares e como o caractere "\r" é completamente removido da string. Este é um exemplo simples, mas podemos estendê-lo criando tabelas de remapeamento de caracteres grandes usando o pacote unicodedata e sua função combining() . Essa abordagem permite remover das linhas tudo o que não é necessário lá.

Obtendo fatias do iterador


Se você tentar obter uma fatia de um iterador, encontrará um erro TypeError , que indica que não é possível assinar o objeto gerador. No entanto, esse problema pode ser resolvido:

 import itertools s = itertools.islice(range(50), 10, 20) # <itertools.islice object at 0x7f70fab88138> for val in s: ... 

Usando o método itertools.islice , itertools.islice pode criar um objeto islice , que é um iterador que islice elementos necessários. No entanto, é importante observar aqui que essa construção usa todos os elementos do gerador até o início da fatia e todos os elementos no objeto islice .

Ignorar o início do objeto iterável


Às vezes, você precisa trabalhar com um arquivo que, como você sabe, começa com um certo número de linhas desnecessárias - como linhas com comentários. Para pular essas linhas, é possível recorrer novamente aos itertools do itertools :

 string_from_file = """ // Author: ... // License: ... // // Date: ... Actual content... """ import itertools for line in itertools.dropwhile(lambda line: line.startswith("//"), string_from_file.split("\n")): print(line) 

Esse código retorna apenas linhas após o bloco de comentários localizado no início do arquivo. Essa abordagem pode ser útil quando você precisa descartar apenas elementos (no nosso caso, linhas) no início do objeto iterável, mas seu número exato não é conhecido.

Funções que suportam apenas argumentos nomeados (kwargs)


Para possibilitar o uso de uma determinada função para que apenas argumentos nomeados possam ser transmitidos a ela, você pode fazer o seguinte:

 def test(*, a, b): pass test("value for a", "value for b") # TypeError: test() takes 0 positional arguments... test(a="value", b="value 2") #   - ... 

Isso pode ser útil para melhorar a compreensibilidade do código. Como você pode ver, nosso problema é facilmente resolvido usando o argumento * na frente da lista de argumentos nomeados. Aqui, o que é bastante óbvio, você também pode usar argumentos posicionais - se você os colocar antes do argumento * .

Criando objetos que suportam a instrução with


Todo mundo sabe como, por exemplo, abrir um arquivo ou, possivelmente, como definir um bloqueio usando a instrução with . Mas é possível implementar independentemente o mecanismo de controle de bloqueio? Sim, isso é bem real. O protocolo de gerenciamento do contexto de execução é implementado usando os métodos __exit__ e __exit__ :

 class Connection: def __init__(self):  ... def __enter__(self):  #  ... def __exit__(self, type, value, traceback):  #  ... with Connection() as c: # __enter__() executes ... # conn.__exit__() executes 

Essa é a maneira mais comum de implementar os recursos do gerenciador de contexto em Python, mas a mesma coisa pode ser feita mais facilmente:

 from contextlib import contextmanager @contextmanager def tag(name): print(f"<{name}>") yield print(f"</{name}>") with tag("h1"): print("This is Title.") 

Aqui, o protocolo de gerenciamento de contexto é implementado usando o decorador de gerenciador de contextmanager . A primeira parte da função de tag (antes do yield ) é executada quando você entra no bloco with . Este bloco é então executado e, em seguida, o restante da função de tag é executado.

Economize memória com __slots__


Se você já gravou programas que criam um número realmente grande de instâncias de uma determinada classe, poderá perceber que esses programas podem inesperadamente exigir muita memória. Isso ocorre porque o Python usa dicionários para representar os atributos das instâncias de classe. Isso tem um bom efeito no desempenho, mas, em termos de consumo de memória, é ineficiente. Geralmente, no entanto, esse recurso não causa problemas. No entanto, se você estiver com falta de memória em uma situação semelhante, tente usar o atributo __slots__ :

 class Person: __slots__ = ["first_name", "last_name", "phone"] def __init__(self, first_name, last_name, phone):  self.first_name = first_name  self.last_name = last_name  self.phone = phone 

Aqui, quando declaramos o atributo __slots__ , o Python usa uma pequena matriz de tamanho fixo para armazenar os atributos, não um dicionário. Isso reduz seriamente a quantidade de memória necessária para cada instância da classe. Existem algumas desvantagens em usar o atributo __slots__ . Portanto, usando-o, não podemos declarar novos atributos, estamos limitados apenas àqueles que estão em __slots__ . Além disso, as classes com o atributo __slots__ não podem usar herança múltipla.

Limites de CPU e memória


Se, em vez de otimizar o programa ou melhorar a maneira como ele usa o processador, você só precisa definir uma restrição estrita nos recursos disponíveis, pode usar a biblioteca apropriada:

 import signal import resource import os #     def time_exceeded(signo, frame): print("CPU exceeded...") raise SystemExit(1) def set_max_runtime(seconds): #   signal     soft, hard = resource.getrlimit(resource.RLIMIT_CPU) resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard)) signal.signal(signal.SIGXCPU, time_exceeded) #     def set_max_memory(size): soft, hard = resource.getrlimit(resource.RLIMIT_AS) resource.setrlimit(resource.RLIMIT_AS, (size, hard)) 

Isso mostra a limitação do tempo do processador e tamanho da memória. Para limitar o uso do processador pelo programa, primeiro obtemos os valores de limites não rígidos (flexíveis) e rígidos (rígidos) para um recurso específico ( RLIMIT_CPU ). Em seguida, definimos o limite usando um certo número de segundos especificado pelo argumento seconds e o valor-limite obtido anteriormente. Depois disso, registramos o manipulador de signal que, quando o tempo do processador alocado para o programa é excedido, inicia o procedimento de saída. No caso da memória, obtemos novamente valores para limites não rígidos e rígidos, após os quais definimos o limite usando o método setrlimit , para o qual passamos o tamanho da restrição ( size ) e o valor obtido anteriormente do limite rígido.

Controlando o que pode ser importado do módulo e o que não pode


Algumas linguagens possuem mecanismos de exportação extremamente claros a partir de módulos de variáveis, métodos e interfaces. Por exemplo, apenas as entidades cujos nomes começam com uma letra maiúscula são exportadas para Golang. No Python, tudo é exportado. Mas apenas até o atributo __all__ ser __all__ :

 def foo(): pass def bar(): pass __all__ = ["bar"] 

No exemplo acima, apenas a função bar será exportada. E se você deixar o atributo __all__ branco, nada será exportado do módulo. Tentar importar algo desse módulo gerará um erro AttributeError .

Simplifique a criação de operadores de comparação


Existem muitos operadores de comparação. Por exemplo, __lt__ , __le__ , __gt__ , __ge__ . Poucas pessoas vão gostar da perspectiva de sua implementação para uma determinada classe. Existe alguma maneira de simplificar essa tarefa chata? Sim, você pode - com a ajuda do decorador functools.total_ordering :

 from functools import total_ordering @total_ordering class Number: def __init__(self, value):  self.value = value def __lt__(self, other):  return self.value < other.value def __eq__(self, other):  return self.value == other.value print(Number(20) > Number(3)) print(Number(1) < Number(5)) print(Number(15) >= Number(15)) print(Number(10) <= Number(2)) 

O decorador functools.total_ordering usado aqui para simplificar o processo de implementação da ordenação de instâncias de classe. Para garantir sua operação, é necessário apenas que os operadores de comparação __lt__ e __eq__ . É o mínimo que um decorador precisa para construir os demais operadores de comparação.

Sumário


Isso não quer dizer que tudo o que falei aqui seja absolutamente necessário no trabalho diário de todo programador Python. Mas algumas das técnicas apresentadas aqui, de tempos em tempos, podem ser muito úteis. Além disso, eles são capazes de simplificar a solução de problemas, cuja solução usual pode exigir muito código e uma grande quantidade de trabalho monótono. Além disso, gostaria de observar que tudo o que foi discutido faz parte da biblioteca padrão do Python. Para ser sincero, alguns desses recursos parecem inesperados para a biblioteca padrão. Isso sugere que, se alguém vai implementar no Python algo que não parece muito comum, ele deve primeiro vasculhar a biblioteca padrão. Se você não encontrar o que precisa imediatamente, talvez valha a pena cavar novamente, com muito cuidado. É verdade que, se uma pesquisa completa não foi bem-sucedida, provavelmente o que você precisa não está lá. E, nesse caso, você deve recorrer a bibliotecas de terceiros. Neles, pode ser encontrado definitivamente.

Caros leitores! Você conhece algum recurso padrão do Python que possa parecer incomum à primeira vista chamado de "padrão"?


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


All Articles