❝Minha ortografia é instável. É boa ortografia, mas oscila e as letras ficam nos lugares errados.❞
- Ursinho Pooh
Mergulho
Tendo crescido como filho de um bibliotecário e formado em inglês, sempre fui fascinado por línguas. Não são linguagens de programação. Bem, sim, linguagens de programação, mas também linguagens naturais. Faça o inglês. O inglês é uma língua esquizofrênica que empresta palavras do alemão, francês, espanhol e latim (para citar alguns). Na verdade, “pede emprestado” é a palavra errada; “Pilhagens” é mais parecido. Ou talvez “assimila” - como os Borg. Sim eu gosto disso.
We are the Borg. Your linguistic and etymological distinctiveness will be added to our own. Resistance is futile.
Neste capítulo, você aprenderá sobre substantivos no plural. Além disso, funções que retornam outras funções, expressões regulares avançadas e geradores. Mas primeiro, vamos falar sobre como fazer substantivos no plural. (Se você ainda não leu o capítulo sobre expressões regulares, agora seria uma boa hora. Este capítulo pressupõe que você entende os fundamentos das expressões regulares e rapidamente desce para usos mais avançados).
Se você cresceu em um país onde se fala inglês ou aprendeu inglês em uma escola formal, provavelmente você está familiarizado com as regras básicas:
- Se uma palavra terminar em S, X ou Z, adicione ES. Bass torna-se Basses, fax torna-se faxes, e waltz se torna waltzes.
- Se uma palavra terminar em um H barulhento, adicione ES; se terminar em H silencioso, basta adicionar S. O que é um H barulhento? Um que se combina com outras letras para fazer um som que você pode ouvir. Assim, o coach se torna um coaches e a rash se transforma em rashes, porque você pode ouvir os sons de CH e SH quando os diz. Mas cheetah torna-se cheetahs, porque o H é silenciosa.
- Se uma palavra terminar em Y que soe como I, altere Y para IES; se o Y é combinado com uma vogal a soar como algo mais, basta adicionar S. Então vacancy se torna vacancies, mas day se torna days.
- Se tudo mais falhar, basta adicionar S e esperar o melhor.
(Eu sei, há uma série de exceções. Man se torna men e woman se torna women, mas human se torna humans. Mouse torna-se mice e louse torna-se lice, mas house torna-se houses. Knife torna-se knives e wife torna-se wives, mas lowlife se torna lowlifes. E nem me fale em palavras que estão no seu próprio plural, como sheep, deer e haiku).
Outras línguas, é claro, são completamente diferentes.
Vamos projetar uma biblioteca Python que pluraliza automaticamente os substantivos em inglês. Começaremos apenas com essas quatro regras, mas lembre-se de que você inevitavelmente precisará adicionar mais.
Eu sei, vamos usar expressões regulares!
Então você está olhando para palavras, o que, pelo menos em inglês, significa que você está olhando para cadeias de caracteres. Você tem regras que dizem que você precisa encontrar diferentes combinações de caracteres e, em seguida, fazer coisas diferentes com eles. Isso parece um trabalho para expressões regulares!
import re
def plural(noun):
if re.search('[sxz]$', noun): ①
return re.sub('$', 'es', noun) ②
elif re.search('[^aeioudgkprt]h$', noun):
return re.sub('$', 'es', noun)
elif re.search('[^aeiou]y$', noun):
return re.sub('y$', 'ies', noun)
else:
return noun + 's'
-
Esta é uma expressão regular, mas usa uma sintaxe que você não viu nas Expressões regulares. Os colchetes significam "corresponder exatamente a um desses caracteres". Então
[sxz]
significa “s
, oux
, ouz
”, mas apenas um deles. O$
deve ser familiar; ele corresponde ao final da string. Combinadas, isso testa expressão regulares, sesubstantivo
termina coms
,x
ouz
. -
Esta função
re.sub()
executa substituições de string baseadas em expressões regulares.
Vejamos as substituições de expressões regulares com mais detalhes.
>>> import re
>>> re.search('[abc]', 'Mark') ①
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.sub('[abc]', 'o', 'Mark') ②
'Mork'
>>> re.sub('[abc]', 'o', 'rock') ③
'rook'
>>> re.sub('[abc]', 'o', 'caps') ④
'oops'
-
Será que a string
Mark
contera
,b
ouc
? Sim, contéma
. -
OK, agora encontre
a
,b
ouc
e substitua-o poro
.Mark
torna-seMork
. -
A mesma função transforma
rock
emrook
. -
Você pode pensar que isso iria transformar
caps
emoaps
, mas isso não acontece.re.sub
substitui todas as correspondências, não apenas a primeira. Portanto, essa expressão regular transformacaps
emoops
, porque tanto oc
quanto oa
são transformados emo
.
E agora, de volta à função plural()
...
def plural(noun):
if re.search('[sxz]$', noun):
return re.sub('$', 'es', noun) ①
elif re.search('[^aeioudgkprt]h$', noun): ②
return re.sub('$', 'es', noun)
elif re.search('[^aeiou]y$', noun): ③
return re.sub('y$', 'ies', noun)
else:
return noun + 's'
-
Aqui, você está substituindo o final da string (combinada por
$
) pela stringes
. Em outras palavras, adicionandoes
à string. Você poderia realizar a mesma coisa com a concatenação de strings, por exemplonoun + 'es'
, mas optei por usar expressões regulares para cada regra, por motivos que ficarão claros posteriormente neste capítulo. -
Olhe bem, esta é outra nova variação. O
^
como o primeiro caractere dentro dos colchetes significa algo especial: negação.[^abc]
significa “qualquer caractere único, excetoa
,b
ouc
”. Então,[^aeioudgkprt]
significa qualquer caractere, excetoa
,e
,i
,o
,u
,d
,g
,k
,p
,r
, out
. Então, esse caractere precisa ser seguido porh
, seguido pelo final da string. Você está procurando palavras que terminam em H, onde o H pode ser ouvido. -
Mesmo padrão aqui: coincidem com as palavras que terminam em Y, onde o caractere antes do Y é qualquer caracter diferente de
a
,e
,i
,o
, ouu
. Você está procurando palavras que terminam em Y que soam como I.
Vejamos as expressões regulares de negação com mais detalhes.
>>> import re
>>> re.search('[^aeiou]y$', 'vacancy') ①
<_sre.SRE_Match object at 0x001C1FA8>
>>> re.search('[^aeiou]y$', 'boy') ②
>>>
>>> re.search('[^aeiou]y$', 'day')
>>>
>>> re.search('[^aeiou]y$', 'pita') ③
-
vacancy
corresponde a esta expressão regular, porque termina nocy
, ec
não éa
,e
,i
,o
, ouu
. -
boy
não corresponde, porque termina emoy
, e você disse especificamente que o caractere anterior aoy
não poderia sero
.day
não corresponde, porque termina emay
. -
pita
não corresponde, porque não termina emy
.
>>> re.sub('y$', 'ies', 'vacancy') ①
'vacancies'
>>> re.sub('y$', 'ies', 'agency')
'agencies'
>>> re.sub('([^aeiou])y$', r'\1ies', 'vacancy') ②
'vacancies'
-
Essa expressão regular transforma
vacancy
emvacancies
eagency
emagencies
, que é o que você queria. Observe que também transformariaboy
emboies
, mas isso nunca acontecerá na função porque você chamoure.search
primeiro para descobrir se deveria fazer essa substituição (re.sub
). -
De passagem, quero salientar que é possível combinar essas duas expressões regulares (uma para descobrir se a regra se aplica e outra para aplicá-la de fato) em uma única expressão regular. É assim que ficaria. A maior parte deve parecer familiar: você está usando um grupo lembrado, que aprendeu no Estudo de caso: análise de números de telefone. O grupo é usado para lembrar o caractere antes da letra
y
. Então, na string de substituição, você usa uma nova sintaxe\1
, que significa “ei, aquele primeiro grupo que você lembrou? coloque bem aqui”. Neste caso, você se lembra dec
antes doy
; quando você faz a substituição, você substituic
no lugar dec
, eies
no lugar dey
. (Se você tiver mais de um grupo lembrado, você pode usar\2
e\3
e assim por diante).
As substituições de expressões regulares são extremamente poderosas e a sintaxe \1
as torna ainda mais poderosas. Mas combinar a operação inteira em uma expressão regular também é muito mais difícil de ler e não mapeia diretamente para a maneira como você descreveu pela primeira vez as regras de pluralização. Você originalmente estabeleceu regras como “se a palavra terminar em S, X ou Z, adicione ES”. Se você olhar para esta função, terá duas linhas de código que dizem “se a palavra terminar em S, X ou Z, adicione ES”. Não fica muito mais direto do que isso.
Uma lista de funções
Agora você vai adicionar um nível de abstração. Você começou definindo uma lista de regras: se isso, faça aquilo, caso contrário, vá para a próxima regra. Vamos complicar temporariamente parte do programa para que você possa simplificar outra parte.
import re
def match_sxz(noun):
return re.search('[sxz]$', noun)
def apply_sxz(noun):
return re.sub('$', 'es', noun)
def match_h(noun):
return re.search('[^aeioudgkprt]h$', noun)
def apply_h(noun):
return re.sub('$', 'es', noun)
def match_y(noun): ①
return re.search('[^aeiou]y$', noun)
def apply_y(noun): ②
return re.sub('y$', 'ies', noun)
def match_default(noun):
return True
def apply_default(noun):
return noun + 's'
rules = ((match_sxz, apply_sxz), ③
(match_h, apply_h),
(match_y, apply_y),
(match_default, apply_default)
)
def plural(noun):
for matches_rule, apply_rule in rules: ④
if matches_rule(noun):
return apply_rule(noun)
-
Agora, cada regra de correspondência é sua própria função, que retorna os resultados da chamada da função
re.search()
. -
Cada regra de aplicação também é sua própria função, que chama a função
re.sub()
para aplicar a regra de pluralização apropriada. -
Em vez de ter uma função (
plural()
) com várias regras, você tem a estrutura de dados,rules
, que é uma sequência de pares de funções. -
Como as regras foram divididas em uma estrutura de dados separada, a nova função
plural()
pode ser reduzida a algumas linhas de código. Usando um loopfor
, você pode retirar a correspondência e aplicar duas regras por vez (uma correspondência, uma aplicação) da estrutura derules
. Na primeira iteração do loopfor
,match_rule
será obtidomatch_sxz
eapply_rule
será obtidoapply_sxz
. Na segunda iteração (presumindo que você tenha chegado tão longe),match_rule
será atribuídomatch_h
, eapply_rule
será atribuídoapply_h
. A função tem a garantia de retornar algo eventualmente, porque a regra de correspondência final (match_default
) simplesmente retornaTrue
, o que significa que a regra de aplicação correspondente (apply_default
) sempre será aplicado.
A variável “rules” é uma sequência de pares de funções.
O motivo pelo qual essa técnica funciona é que tudo em Python é um objeto, incluindo funções. A estrutura de dados das rules
contém funções - não nomes de funções, mas objetos de função reais. Quando eles são atribuídos no loop for
, match_rule
e apply_rule
são funções reais que você pode chamar. Na primeira iteração do loop for
, isso equivale a chamar matches_sxz(noun)
e, se retornar uma correspondência, chamar apply_sxz(noun)
.
Se esse nível adicional de abstração for confuso, tente desenrolar a função para ver a equivalência. Todo o loop for
é equivalente ao seguinte:
def plural(noun):
if match_sxz(noun):
return apply_sxz(noun)
if match_h(noun):
return apply_h(noun)
if match_y(noun):
return apply_y(noun)
if match_default(noun):
return apply_default(noun)
A vantagem aqui é que a função plural()
agora foi simplificada. Ele pega uma sequência de regras, definida em outro lugar, e itera por meio delas de maneira genérica.
- Obtenha uma regra de correspondência.
- Combina? Em seguida, chame a regra de aplicação e retorne o resultado.
- Sem correspondência? Vá para a etapa 1.
As regras podem ser definidas em qualquer lugar, de qualquer maneira. A função plural()
não importa.
Agora, adicionar esse nível de abstração. Valeu a pena? Bem, ainda não. Vamos considerar o que seria necessário para adicionar uma nova regra à função. No primeiro exemplo, seria necessário adicionar uma instrução if
à função plural()
. Neste segundo exemplo, seria necessário adicionar duas funções match_foo()
e apply_foo()
, em seguida, atualizar a sequência de rules
para especificar onde, na ordem, as novas funções de correspondência e aplicação devem ser chamadas em relação às outras regras.
Mas este é realmente apenas um trampolim para a próxima seção. Vamos continuar…
Uma lista de padrões
Definir funções nomeadas separadas para cada correspondência e aplicar regra não é realmente necessário. Você nunca liga para eles diretamente; você os adiciona à sequência de rules
e os chama por lá. Além disso, cada função segue um de dois padrões. Todas as funções de correspondência são chamadas re.search()
e todas as funções de aplicação são chamadas re.sub()
. Vamos fatorar os padrões para que seja mais fácil definir novas regras.
import re
def build_match_and_apply_functions(pattern, search, replace):
def matches_rule(word): ①
return re.search(pattern, word)
def apply_rule(word): ②
return re.sub(search, replace, word)
return (matches_rule, apply_rule) ③
-
build_match_and_apply_functions()
é uma função que cria outras funções dinamicamente. Ela pegapattern
,search
esubstitui
, então define uma funçãomatches_rule()
que chamare.search()
com opattern
que foi passado para a funçãobuild_match_and_apply_functions()
e aword
que foi passada para a funçãomatches_rule()
que você está construindo. Uau. -
Construir a função
apply_rule
funciona da mesma maneira. A funçãoapply_rule
é uma função que recebe um parâmetro e chamare.sub()
com os parâmetros desearch
ereplace
que foram passados para a funçãobuild_match_and_apply_functions()
e aword
que foi passada para a funçãoapply_rule()
que você está construindo. Essa técnica de usar os valores de parâmetros externos dentro de uma função dinâmica é chamada de closures. Você está essencialmente definindo constantes dentro de
que você está construindo: leva um parâmetro (apply_rule
word
), mas então atua sobre ele mais dois outros valores (search
ereplace
) que foram configurados quando você definiu a função de aplicação. -
Finalmente, a função
build_match_and_apply_functions()
retorna uma tupla de dois valores: as duas funções que você acabou de criar. As constantes que você definiu nessas funções (pattern
dentro da funçãomatches_rule()
esearch
ereplace
dentro da funçãoapply_rule()
) permanecem com essas funções, mesmo depois de retornar debuild_match_and_apply_functions()
. Isso é incrivelmente legal.
Se isso é incrivelmente confuso (e deveria ser, isso é coisa esquisita), pode ficar mais claro quando você vir como usá-lo.
patterns = \ ①
(
('[sxz]$', '$', 'es'),
('[^aeioudgkprt]h$', '$', 'es'),
('(qu|[^aeiou])y$', 'y$', 'ies'),
('$', '$', 's') ②
)
rules = [build_match_and_apply_functions(pattern, search, replace) ③
for (pattern, search, replace) in patterns]
-
Nossas “regras” de pluralização são agora definidas como uma tupla de tuplas de strings (não funções). A primeira string em cada grupo é o padrão de expressão regular que você usaria
re.search()
para ver se essa regra corresponde. A segunda e a terceira strings em cada grupo são as expressões de pesquisa e substituição que você usariare.sub()
para realmente aplicar a regra para transformar um substantivo em seu plural. -
Há uma pequena mudança aqui, na regra de fallback. No exemplo anterior, a função
match_default()
simplesmente retornouTrue
, o que significa que se nenhuma das regras mais específicas correspondesse, o código simplesmente adicionaria ums
ao final da palavra dada. Este exemplo faz algo funcionalmente equivalente. A expressão regular final pergunta se a palavra tem um final ($
corresponde ao final de uma string). É claro que toda string tem um fim, até mesmo uma string vazia, portanto, essa expressão sempre corresponde. Assim, ele tem o mesmo propósito que a funçãomatch_default()
que sempre retornouTrue
: ele garante que, se nenhuma regra mais específica corresponder, o código adiciona ums
ao final da palavra dada. -
Esta linha é mágica. Ele pega a sequência de strings em
patterns
e os transforma em uma sequência de funções. Como? Ao “mapear” as strings para a funçãobuild_match_and_apply_functions()
. Ou seja, ele pega cada trinca de strings e chama a funçãobuild_match_and_apply_functions()
com essas três strings como argumentos. A funçãobuild_match_and_apply_functions()
retorna uma tupla de duas funções. Isso significa que asrules
acabam sendo funcionalmente equivalentes ao exemplo anterior: uma lista de tuplas, onde cada tupla é um par de funções. A primeira função é a função de correspondência que chamare.search()
e a segunda função é a função de aplicação que chamare.sub()
.
Completando esta versão do script está o principal ponto de entrada, a função plural()
.
def plural(noun):
for matches_rule, apply_rule in rules: ①
if matches_rule(noun):
return apply_rule(noun)
-
Como a lista de
rules
é a mesma do exemplo anterior (realmente é), não deve ser surpresa que a funçãoplural()
não tenha mudado em nada. É completamente genérico; ele pega uma lista de funções de regra e as chama em ordem. Não importa como as regras são definidas. No exemplo anterior, eles foram definidos como funções nomeadas separadas. Agora eles são construídos dinamicamente mapeando a saída da funçãobuild_match_and_apply_functions()
em uma lista de strings brutas. Não importa; a funçãoplural()
ainda funciona da mesma maneira.
Um arquivo de padrões
Você fatorou todo o código duplicado e adicionou abstrações suficientes para que as regras de pluralização sejam definidas em uma lista de strings. A próxima etapa lógica é pegar essas strings e colocá-las em um arquivo separado, onde podem ser mantidas separadamente do código que as usa.
Primeiro, vamos criar um arquivo de texto que contém as regras que você deseja. Nenhuma estrutura de dados extravagante, apenas strings delimitadas por espaços em branco em três colunas. Vamos chamá-lo plural4-rules.txt
.
[sxz]$ $ es [^aeioudgkprt]h$ $ es [^aeiou]y$ y$ ies $ $ s
Agora vamos ver como você pode usar este arquivo de regras.
import re
def build_match_and_apply_functions(pattern, search, replace): ①
def matches_rule(word):
return re.search(pattern, word)
def apply_rule(word):
return re.sub(search, replace, word)
return (matches_rule, apply_rule)
rules = []
with open('plural4-rules.txt', encoding='utf-8') as pattern_file: ②
for line in pattern_file: ③
pattern, search, replace = line.split(None, 2) ④
rules.append(build_match_and_apply_functions( ⑤
pattern, search, replace))
-
A função
build_match_and_apply_functions()
não mudou. Você ainda está usando closures para construir duas funções dinamicamente que usam variáveis definidas na função externa. -
A função global
open()
abre um arquivo e retorna um objeto de arquivo. Nesse caso, o arquivo que estamos abrindo contém as strings de padrão para substantivos pluralizantes. A instruçãowith
cria o que é chamado de contexto: quando o blocowith
termina, o Python fecha automaticamente o arquivo, mesmo se uma exceção for levantada dentro do blocowith
. Você aprenderá mais sobre blocoswith
e objetos de arquivo no capítulo Arquivos. -
for line in <fileobject>
lê os dados do arquivo aberto, uma linha por vez, e atribui o texto à variávelline
. Você aprenderá mais sobre como ler arquivos no capítulo Arquivos. -
Cada linha do arquivo realmente tem três valores, mas eles são separados por espaços em branco (tabulações ou espaços, não faz diferença). Para dividir, use o método
split()
das strings. O primeiro argumento do métodosplit()
éNone
, que significa “dividir em qualquer espaço em branco (tabulações ou espaços, não faz diferença)”. O segundo argumento é2
, que significa "dividir em espaços em branco 2 vezes (dividir uma vez retorna dois valores, dividir duas vezes retorna três valores e assim por diante) e, em seguida, deixe o resto da linha sozinho." Uma linha como[sxz]$ $ es
será dividida na lista['[sxz]$', '$', 'es']
, o que significa que opattern
será obtido'[sxz]$'
,search
obterá'$'
e areplace
obterá'es'
. Isso é muito poder em uma pequena linha de código. -
Finalmente, você passa
pattern
,search
ereplace
para a funçãobuild_match_and_apply_functions()
, que retorna uma tupla de funções. Você anexa essa tupla à lista derules
e asrules
acabam armazenando a lista de funções de correspondência e aplicação que a funçãoplural()
espera.
A melhoria aqui é que você separou completamente as regras de pluralização em um arquivo externo, para que ele possa ser mantido separadamente do código que o usa. Código é código, dados são dados e a vida é boa.
Geradores
Não seria ótimo ter uma função plural()
genérica que analisa o arquivo de regras? Obtenha regras, verifique se há uma correspondência, aplique a transformação apropriada e seguir para a próxima regra. Isso é tudo que a função plural()
precisa fazer, e isso é tudo que a função plural()
deve fazer.
def rules(rules_filename):
with open(rules_filename, encoding='utf-8') as pattern_file:
for line in pattern_file:
pattern, search, replace = line.split(None, 2)
yield build_match_and_apply_functions(pattern, search, replace)
def plural(noun, rules_filename='plural5-rules.txt'):
for matches_rule, apply_rule in rules(rules_filename):
if matches_rule(noun):
return apply_rule(noun)
raise ValueError('no matching rule for {0}'.format(noun))
Como diabos isso funciona? Vejamos primeiro um exemplo interativo.
>>> def make_counter(x):
... print('entering make_counter')
... while True:
... yield x ①
... print('incrementing x')
... x = x + 1
...
>>> counter = make_counter(2) ②
>>> counter ③
<generator object at 0x001C9C10>
>>> next(counter) ④
entering make_counter
2
>>> next(counter) ⑤
incrementing x
3
>>> next(counter) ⑥
incrementing x
4
-
A presença da palavra-chave
yield
emmake_counter
significa que esta não é uma função normal. É um tipo especial de função que gera valores um de cada vez. Você pode pensar nisso como uma função recuperável. Chamá-lo retornará um gerador que pode ser usado para gerar valores sucessivos dex
. -
Para criar uma instância do gerador
make_counter
, basta chamá-la como qualquer outra função. Observe que isso não executa realmente o código da função. Você pode dizer isso porque a primeira linha da funçãomake_counter()
chamaprint()
, mas nada foi impresso ainda. -
A função
make_counter()
retorna um objeto gerador. -
A função
next()
pega um objeto gerador e retorna seu próximo valor. A primeira vez que você chamanext()
com o gerador decounter
, ele executa o códigomake_counter()
até a primeira instruçãoyield
e retorna o valor gerado. Nesse caso, será2
, porque você criou originalmente o gerador chamandomake_counter(2)
. -
Chamar repetidamente
next()
com o mesmo objeto gerador continua exatamente de onde parou e continua até chegar à próxima instruçãoyield
. Todas as variáveis, estado local, etc. são salvosyield
e restaurados emnext()
. A próxima linha de código esperando para ser executada chamaprint()
, que imprimeincrementing x
. Depois disso, a declaraçãox = x + 1
. Em seguida, ele percorre o loopwhile
novamente e a primeira coisa que atinge é a instruçãoyield x
, que salva o estado de tudo e retorna o valor atual dex
(agora3
). -
Na segunda vez que você chama
next(counter)
, você faz todas as mesmas coisas novamente, mas desta vezx
é agora4
.
Uma vez que make_counter
configura um loop infinito, você poderia teoricamente fazer isso para sempre, e ele simplesmente continuaria incrementando x
e emitindo valores. Mas, em vez disso, vamos dar uma olhada em usos mais produtivos de geradores.
Um gerador de Fibonacci
“Yield” pausa uma função. “Next()” continua de onde parou.
def fib(max):
a, b = 0, 1 ①
while a < max:
yield a ②
a, b = b, a + b ③
-
A sequência de Fibonacci é uma sequência de números onde cada número é a soma dos dois números anteriores. Ele começa com 0 e 1 aumenta lentamente no início, depois cada vez mais rapidamente. Para iniciar a sequência, você precisa de duas variáveis:
a
começa em 0 eb
começa em1
. -
a
é o número atual na sequência, portanto, forneça-o. -
b
é o próximo número na sequência, então atribua-o aa
, mas também calcule o próximo valor (a + b
) e atribua-o ab
para uso posterior. Observe que isso acontece em paralelo; sea
for3
eb
for5
,a, b = b, a + b
definiráa
como5
(o valor anterior deb
) eb
como8
(a soma dos valores anteriores dea
eb
).
Portanto, você tem uma função que produz números de Fibonacci sucessivos. Claro, você poderia fazer isso com recursão, mas dessa forma é mais fácil de ler. Além disso, funciona bem com loops for
.
>>> from fibonacci import fib
>>> for n in fib(1000): ①
... print(n, end=' ') ②
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> list(fib(1000)) ③
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
-
Você pode usar um gerador como
fib()
em um loopfor
diretamente. O loopfor
irá chamar automaticamente a funçãonext()
para obter valores do geradorfib()
e atribuí-los à variáveln
de índice do loopfor
. -
Cada vez que percorre o loop
for
,n
obtém um novo valor da instruçãoyield
emfib()
e tudo o que você precisa fazer é imprimi-lo. Uma vez que os números defib()
acabam (a
torna-se maior quemax
, o que neste caso é1000
), o loopfor
termina normalmente. -
Este é útil: passe um gerador para a função
list()
e ele iterará por todo o gerador (assim como o loopfor
no exemplo anterior) e retornará uma lista de todos os valores.
Um gerador de regra plural
Vamos voltar para o script plural5.py
e ver como essa versão da função plural()
funciona.
def rules(rules_filename):
with open(rules_filename, encoding='utf-8') as pattern_file:
for line in pattern_file:
pattern, search, replace = line.split(None, 2) ①
yield build_match_and_apply_functions(pattern, search, replace) ②
def plural(noun, rules_filename='plural5-rules.txt'):
for matches_rule, apply_rule in rules(rules_filename): ③
if matches_rule(noun):
return apply_rule(noun)
raise ValueError('no matching rule for {0}'.format(noun))
-
Nenhuma mágica aqui. Lembre-se de que as linhas do arquivo de regras têm três valores separados por espaços em branco, então você usa
line.split(None, 2)
para obter as três “colunas” e atribuí-las a três variáveis locais. -
E então você cede. O que você cede? Duas funções, construídas dinamicamente com seu velho amigo
build_match_and_apply_functions()
, que são idênticas aos exemplos anteriores. Em outras palavras,rules()
é um gerador que gera correspondência e aplica funções sob demanda. -
Como
rules()
é um gerador, você pode usá-lo diretamente em um loopfor
. Na primeira vez no loopfor
, você chamará a funçãorules()
, que abrirá o arquivo de padrões, lerá a primeira linha, construirá dinamicamente uma função de correspondência e uma função de aplicação a partir dos padrões nessa linha e produzirá as funções construídas dinamicamente. Na segunda vez no loopfor
, você continuará exatamente de onde parourules()
(que foi no meio do loopfor line in pattern_file
). A primeira coisa que ele fará é ler a próxima linha do arquivo (que ainda está aberta), construir dinamicamente outra correspondência e aplicar a função com base nos padrões dessa linha no arquivo e produzir as duas funções.
O que você ganhou no estágio 4? Hora de inicialização. No estágio 4, quando você importou o módulo plural4
, ele leu todo o arquivo de padrões e construiu uma lista de todas as regras possíveis, antes mesmo que você pudesse pensar em chamar a função plural()
. Com os geradores, você pode fazer tudo preguiçosamente: você lê a primeira regra, cria funções e as experimenta, e se isso funcionar, você nunca lê o resto do arquivo ou cria quaisquer outras funções.
O que você perdeu? Desempenho! Cada vez que você chama a função plural()
, o gerador rules()
começa do início - o que significa reabrir o arquivo de padrões e ler desde o início, uma linha de cada vez.
E se você pudesse ter o melhor dos dois mundos: custo mínimo de inicialização (não execute nenhum código import
) e desempenho máximo (não crie as mesmas funções repetidamente). Ah, e você ainda deseja manter as regras em um arquivo separado (porque código é código e dados são dados), contanto que nunca precise ler a mesma linha duas vezes.
Para fazer isso, você precisará construir seu próprio iterador. Mas antes de fazer isso, você precisa aprender sobre as classes Python.
Leitura adicional
- PEP 255: Geradores Simples
- Compreendendo a declaração “with” do Python
- Fechamentos em Python
- Números de Fibonacci
- Substantivos plurais irregulares do inglês
Esse artigo é uma tradução de um capítulo do livro "Dive Into Python 3" escrito por Mark Pilgrim. Você pode ler o livro desde o início em português clicando aqui.
Traduzido por Acervo Lima. O original pode ser acessado aqui.
0 comentários:
Postar um comentário