❝Nossa imaginação é esticada ao máximo, não, como na ficção, para imaginar coisas que não existem realmente, mas apenas para compreender as coisas que existem.❞
- Richard Feynman
Mergulho
Cada linguagem de programação tem aquele único recurso, algo complicado intencionalmente simplificado. Se você está vindo de outra linguagem, pode facilmente perdê-lo, porque sua antiga linguagem não tornava isso simples (porque estava ocupado fazendo outra coisa simples). Este capítulo irá ensiná-lo sobre compreensões de lista, compreensões de dicionário e compreensões de conjunto: três conceitos relacionados centrados em torno de uma técnica muito poderosa. Mas, primeiro, quero fazer um pequeno desvio em dois módulos que o ajudarão a navegar em seu sistema de arquivos local.
Trabalho com arquivos e diretórios
Python 3 vem com um módulo chamado os
, que significa “sistema operacional”. O módulo os
contém uma infinidade de funções para obter informações - e, em alguns casos, manipular - diretórios locais, arquivos, processos e variáveis de ambiente. Python faz o seu melhor para oferecer uma API unificada em todos os sistemas operacionais suportados para que seus programas possam ser executados em qualquer computador com o mínimo possível de código específico de plataforma.
O diretório de trabalho atual
Quando você está começando a usar o Python, vai passar muito tempo no Python Shell. Ao longo deste livro, você verá exemplos semelhantes a este:
- Importe um dos módulos da pasta
examples
- Chame uma função nesse módulo
- Explique o resultado
Sempre há um diretório de trabalho atual.
Se você não souber sobre o diretório de trabalho atual, a etapa 1 provavelmente falhará com um ImportError
. Por quê? Porque o Python procurará o módulo de exemplo no caminho de pesquisa de importação, mas não o encontrará porque a pasta examples
não é um dos diretórios no caminho de pesquisa. Para superar isso, você pode fazer uma das duas coisas:
-
Adicione a pasta
examples
ao caminho de pesquisa de importação. -
Mude o diretório de trabalho atual para a pasta
examples
.
O diretório de trabalho atual é uma propriedade invisível que o Python mantém na memória o tempo todo. Sempre há um diretório de trabalho atual, esteja você no Python Shell, executando seu próprio script Python na linha de comando ou executando um script Python CGI em um servidor da web em algum lugar.
O módulo os
contém duas funções para lidar com o diretório de trabalho atual.
>>> import os ①
>>> print(os.getcwd()) ②
C:\Python31
>>> os.chdir('/Users/pilgrim/diveintopython3/examples') ③
>>> print(os.getcwd()) ④
C:\Users\pilgrim\diveintopython3\examples
-
O módulo
os
vem com Python; você pode importá-lo a qualquer hora, em qualquer lugar. -
Use a função
os.getcwd()
para obter o diretório de trabalho atual. Quando você executa o Python Shell gráfico, o diretório de trabalho atual começa como o diretório onde está o executável do Python Shell. No Windows, isso depende de onde você instalou o Python; o diretório padrão éc:\Python31
. Se você executar o Python Shell a partir da linha de comando, o diretório de trabalho atual começa como o diretório em que você estava quando executoupython3
. -
Use a função
os.chdir()
para alterar o diretório de trabalho atual. -
Quando chamei a função
os.chdir()
, usei um nome de caminho no estilo Linux (barras, sem letra de unidade), embora esteja no Windows. Este é um dos lugares onde o Python tenta esconder as diferenças entre os sistemas operacionais.
Trabalho com nomes de arquivos e diretórios
Já que estamos falando de diretórios, quero destacar o módulo os.path
. os.path
contém funções para manipular nomes de arquivos e nomes de diretórios.
>>> import os
>>> print(os.path.join('/Users/pilgrim/diveintopython3/examples/', 'humansize.py')) ①
/Users/pilgrim/diveintopython3/examples/humansize.py
>>> print(os.path.join('/Users/pilgrim/diveintopython3/examples', 'humansize.py')) ②
/Users/pilgrim/diveintopython3/examples\humansize.py
>>> print(os.path.expanduser('~')) ③
c:\Users\pilgrim
>>> print(os.path.join(os.path.expanduser('~'), 'diveintopython3', 'examples', 'humansize.py')) ④
c:\Users\pilgrim\diveintopython3\examples\humansize.py
-
A função
os.path.join()
constrói um nome de caminho a partir de um ou mais nomes de caminho parciais. Nesse caso, ele simplesmente concatena strings. -
Neste caso um pouco menos trivial, chamar a função
os.path.join()
adicionará uma barra extra ao nome do caminho antes de uni-lo ao nome do arquivo. É uma barra invertida em vez de uma barra normal, porque construí este exemplo no Windows. Se você replicar este exemplo no Linux ou Mac OS X, verá uma barra. Não mexa com barras; sempre useos.path.join()
e deixe o Python fazer a coisa certa. -
A função
os.path.expanduser()
irá expandir um nome de caminho que usa~
para representar o diretório pessoal do usuário atual. Isso funciona em qualquer plataforma em que os usuários tenham um diretório inicial, incluindo Linux, Mac OS X e Windows. O caminho retornado não tem uma barra final, mas a funçãoos.path.join()
não se importa. -
Combinando essas técnicas, você pode construir facilmente nomes de caminho para diretórios e arquivos no diretório pessoal do usuário. A função
os.path.join()
pode receber qualquer número de argumentos. Fiquei muito feliz quando descobri isso, já queaddSlashIfNecessary()
é uma das pequenas funções estúpidas que sempre preciso escrever ao construir minha caixa de ferramentas em uma nova linguagem. Não escreva esta pequena função estúpida em Python; pessoas inteligentes já cuidaram disso para você.
os.path
também contém funções para dividir nomes de caminhos completos, nomes de diretórios e nomes de arquivos em suas partes constituintes.
>>> pathname = '/Users/pilgrim/diveintopython3/examples/humansize.py'
>>> os.path.split(pathname) ①
('/Users/pilgrim/diveintopython3/examples', 'humansize.py')
>>> (dirname, filename) = os.path.split(pathname) ②
>>> dirname ③
'/Users/pilgrim/diveintopython3/examples'
>>> filename ④
'humansize.py'
>>> (shortname, extension) = os.path.splitext(filename) ⑤
>>> shortname
'humansize'
>>> extension
'.py'
-
A função
split
divide um caminho completo e retorna uma tupla contendo o caminho e o nome do arquivo. -
Lembra quando eu disse que você poderia usar a atribuição de várias variáveis para retornar vários valores de uma função? A função
os.path.split()
faz exatamente isso. Você atribui o valor de retorno da funçãosplit
em uma tupla de duas variáveis. Cada variável recebe o valor do elemento correspondente da tupla retornada. -
A primeira variável,
dirname
, recebe o valor do primeiro elemento da tupla retornada da funçãoos.path.split()
, o caminho do arquivo. -
A segunda variável,
nome do arquivo
, recebe o valor do segundo elemento da tupla retornado da funçãoos.path.split()
, o nome do arquivo. -
os.path
também contém a funçãoos.path.splitext()
, que divide o nome do arquivo e retorna uma tupla contendo o nome do arquivo e a extensão do arquivo. Você usa a mesma técnica para atribuir cada um deles a variáveis separadas.
Listagem de diretórios
O módulo glob
é outra ferramenta da biblioteca padrão do Python. É uma maneira fácil de obter o conteúdo de um diretório programaticamente e usa o tipo de curinga com o qual você pode já estar familiarizado por trabalhar na linha de comando.
O módulo
glob
usa curingas semelhantes a shell.
>>> os.chdir('/Users/pilgrim/diveintopython3/')
>>> import glob
>>> glob.glob('examples/*.xml') ①
['examples\\feed-broken.xml',
'examples\\feed-ns0.xml',
'examples\\feed.xml']
>>> os.chdir('examples/') ②
>>> glob.glob('*test*.py') ③
['alphameticstest.py',
'pluraltest1.py',
'pluraltest2.py',
'pluraltest3.py',
'pluraltest4.py',
'pluraltest5.py',
'pluraltest6.py',
'romantest1.py',
'romantest10.py',
'romantest2.py',
'romantest3.py',
'romantest4.py',
'romantest5.py',
'romantest6.py',
'romantest7.py',
'romantest8.py',
'romantest9.py']
-
O módulo
glob
recebe um curinga e retorna o caminho de todos os arquivos e diretórios que correspondem ao curinga. Neste exemplo, o curinga é um caminho de diretório mais “*.xml
”, que corresponderá a todos os arquivos.xml
no subdiretórioexamples
. -
Agora mude o diretório de trabalho atual para o subdiretório
examples
. A funçãoos.chdir()
pode ter nomes de caminhos relativos. -
Você pode incluir vários curingas em seu padrão glob. Este exemplo encontra todos os arquivos no diretório de trabalho atual que terminam em uma extensão
.py
e contêm a palavratest
em qualquer lugar em seu nome de arquivo.
Obtendo metadados de arquivo
Cada sistema de arquivos moderno armazena metadados sobre cada arquivo: data de criação, data da última modificação, tamanho do arquivo e assim por diante. Python fornece uma única API para acessar esses metadados. Você não precisa abrir o arquivo; tudo que você precisa é o nome do arquivo.
>>> import os
>>> print(os.getcwd()) ①
c:\Users\pilgrim\diveintopython3\examples
>>> metadata = os.stat('feed.xml') ②
>>> metadata.st_mtime ③
1247520344.9537716
>>> import time ④
>>> time.localtime(metadata.st_mtime) ⑤
time.struct_time(tm_year=2009, tm_mon=7, tm_mday=13, tm_hour=17,
tm_min=25, tm_sec=44, tm_wday=0, tm_yday=194, tm_isdst=1)
-
O diretório de trabalho atual é a pasta
examples
. -
feed.xml
é um arquivo na pastaexamples
. Chamar a funçãoos.stat()
retorna um objeto que contém vários tipos diferentes de metadados sobre o arquivo. -
st_mtime
é a hora da modificação, mas está em um formato que não é muito útil. (Tecnicamente, é o número de segundos desde a Época, que é definido como o primeiro segundo de 1º de janeiro de 1970. Sério). -
O módulo
time
faz parte da biblioteca padrão do Python. Ele contém funções para converter entre diferentes representações de tempo, formatar valores de tempo em strings e mexer com fusos horários. -
A função
time.localtime()
converte um valor de tempo de segundos desde a época (da propriedadest_mtime
retornada da funçãoos.stat()
) em uma estrutura mais útil de ano, mês, dia, hora, minuto, segundo e assim por diante. Este arquivo foi modificado pela última vez em 13 de julho de 2009, por volta das 17:25.
# continued from the previous example
>>> metadata.st_size ①
3070
>>> import humansize
>>> humansize.approximate_size(metadata.st_size) ②
'3.0 KiB'
-
A função
os.stat()
também retorna o tamanho de um arquivo, na propriedadest_size
. O arquivofeed.xml
tem3070
bytes. -
Você pode passar a propriedade
st_size
para a funçãoapproximate_size()
.
Construindo Nomes de Caminho Absolutos
Na seção anterior, a função glob.glob()
retornou uma lista de nomes de caminhos relativos. O primeiro exemplo tinha nomes de caminho como 'examples\feed.xml'
, e o segundo exemplo tinha nomes de caminho relativos ainda mais curtos como 'romantest1.py'
. Contanto que você permaneça no mesmo diretório de trabalho atual, esses nomes de caminho relativos funcionarão para abrir arquivos ou obter metadados de arquivos. Mas se você quiser construir um caminho de acesso absoluto - ou seja, um que inclua todos os nomes de diretório de volta ao diretório raiz ou letra de unidade - então você precisará da função os.path.realpath()
.
>>> import os
>>> print(os.getcwd())
c:\Users\pilgrim\diveintopython3\examples
>>> print(os.path.realpath('feed.xml'))
c:\Users\pilgrim\diveintopython3\examples\feed.xml
Compreensões de lista
Você pode usar qualquer expressão Python em uma compreensão de lista.
Uma compreensão de lista fornece uma maneira compacta de mapear uma lista em outra lista, aplicando uma função a cada um dos elementos da lista.
>>> a_list = [1, 9, 8, 4]
>>> [elem * 2 for elem in a_list] ①
[2, 18, 16, 8]
>>> a_list ②
[1, 9, 8, 4]
>>> a_list = [elem * 2 for elem in a_list] ③
>>> a_list
[2, 18, 16, 8]
-
Para entender isso, olhe da direita para a esquerda.
a_list
é a lista que você está mapeando. O python intérpreta percorrea_list
um elemento de cada vez, atribuir temporariamente o valor de cada elemento para a variávelelem
. O Python então aplica a operaçãoelem * 2
e anexa esse resultado à lista retornada. - Uma compreensão de lista cria uma nova lista; não altera a lista original.
- É seguro atribuir o resultado de uma compreensão de lista à variável que você está mapeando. O Python constrói a nova lista na memória e, quando a compreensão da lista é concluída, ele atribui o resultado à variável original.
Você pode usar qualquer expressão Python em uma compreensão de lista, incluindo as funções no módulo os
para manipular arquivos e diretórios.
>>> import os, glob
>>> glob.glob('*.xml') ①
['feed-broken.xml', 'feed-ns0.xml', 'feed.xml']
>>> [os.path.realpath(f) for f in glob.glob('*.xml')] ②
['c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml',
'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml',
'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml']
-
Isso retorna uma lista de todos os arquivos
.xml
no diretório de trabalho atual. -
Essa compreensão de lista pega essa lista de arquivos
.xml
e a transforma em uma lista de nomes de caminho completos.
As compreensões de lista também podem filtrar itens, produzindo um resultado que pode ser menor do que a lista original.
>>> import os, glob
>>> [f for f in glob.glob('*.py') if os.stat(f).st_size > 6000] ①
['pluraltest6.py',
'romantest10.py',
'romantest6.py',
'romantest7.py',
'romantest8.py',
'romantest9.py']
-
Para filtrar uma lista, você pode incluir uma cláusula
if
no final da compreensão da lista. A expressão após a palavra-chaveif
será avaliada para cada item da lista. Se a expressão for avaliada comoTrue
, o item será incluído na saída. Essa compreensão de lista examina a lista de todos os arquivos.py
no diretório atual e a instruçãoif
filtra essa lista testando se o tamanho de cada arquivo é maior do que6000
bytes. Existem seis desses arquivos, portanto, a compreensão da lista retorna uma lista de seis nomes de arquivo.
Todos os exemplos de compreensões de lista até agora apresentam expressões simples - multiplique um número por uma constante, chame uma única função ou simplesmente retorne o item original da lista (após a filtragem). Mas não há limite para o quão complexa pode ser a compreensão de uma lista.
>>> import os, glob
>>> [(os.stat(f).st_size, os.path.realpath(f)) for f in glob.glob('*.xml')] ①
[(3074, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-broken.xml'),
(3386, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed-ns0.xml'),
(3070, 'c:\\Users\\pilgrim\\diveintopython3\\examples\\feed.xml')]
>>> import humansize
>>> [(humansize.approximate_size(os.stat(f).st_size), f) for f in glob.glob('*.xml')] ②
[('3.0 KiB', 'feed-broken.xml'),
('3.3 KiB', 'feed-ns0.xml'),
('3.0 KiB', 'feed.xml')]
-
Esta compreensão de lista encontra todos os arquivos
.xml
no diretório de trabalho atual, obtém o tamanho de cada arquivo (chamando a funçãoos.stat()
) e constrói uma tupla do tamanho do arquivo e o caminho absoluto de cada arquivo (chamando a funçãoos.path.realpath()
). -
Essa compreensão se baseia na anterior para chamar a função
approximate_size()
com o tamanho do arquivo.xml
de cada arquivo.
Compreensão do dicionário
Uma compreensão de dicionário é como uma compreensão de lista, mas constrói um dicionário em vez de uma lista.
>>> import os, glob
>>> metadata = [(f, os.stat(f)) for f in glob.glob('*test*.py')] ①
>>> metadata[0] ②
('alphameticstest.py', nt.stat_result(st_mode=33206, st_ino=0, st_dev=0,
st_nlink=0, st_uid=0, st_gid=0, st_size=2509, st_atime=1247520344,
st_mtime=1247520344, st_ctime=1247520344))
>>> metadata_dict = {f:os.stat(f) for f in glob.glob('*test*.py')} ③
>>> type(metadata_dict) ④
<class 'dict'>
>>> list(metadata_dict.keys()) ⑤
['romantest8.py', 'pluraltest1.py', 'pluraltest2.py', 'pluraltest5.py',
'pluraltest6.py', 'romantest7.py', 'romantest10.py', 'romantest4.py',
'romantest9.py', 'pluraltest3.py', 'romantest1.py', 'romantest2.py',
'romantest3.py', 'romantest5.py', 'romantest6.py', 'alphameticstest.py',
'pluraltest4.py']
>>> metadata_dict['alphameticstest.py'].st_size ⑥
2509
-
Esta não é uma compreensão de dicionário; é uma compreensão de lista. Ele encontra todos os arquivos
.py
comtest
em seus nomes e, em seguida, constrói uma tupla do nome do arquivo e dos metadados do arquivo (a partir da chamada da funçãoos.stat()
). - Cada item da lista resultante é uma tupla.
-
Esta é uma compreensão de dicionário. A sintaxe é semelhante à compreensão de uma lista, com duas diferenças. Primeiro, ele é colocado entre chaves em vez de colchetes. Em segundo lugar, em vez de uma única expressão para cada item, ele contém duas expressões separadas por dois pontos. A expressão antes dos dois pontos (
f
neste exemplo) é a chave do dicionário; a expressão após os dois pontos (os.stat(f)
neste exemplo) é o valor. - Uma compreensão de dicionário retorna um dicionário.
-
As chaves deste dicionário específico são simplesmente os nomes de arquivo retornados da chamada para
glob.glob('*test*.py')
. -
O valor associado a cada chave é o valor de retorno da função
os.stat()
. Isso significa que podemos “pesquisar” um arquivo por nome neste dicionário para obter seus metadados de arquivo. Uma das partes dos metadados ést_size
o tamanho do arquivo. O arquivoalphameticstest.py
tem2509
bytes.
Como as compreensões de lista, você pode incluir uma cláusula if
em uma compreensão de dicionário para filtrar a sequência de entrada com base em uma expressão que é avaliada com cada item.
>>> import os, glob, humansize
>>> metadata_dict = {f:os.stat(f) for f in glob.glob('*')} ①
>>> humansize_dict = {os.path.splitext(f)[0]:humansize.approximate_size(meta.st_size) \
... for f, meta in metadata_dict.items() if meta.st_size > 6000} ②
>>> list(humansize_dict.keys()) ③
['romantest9', 'romantest8', 'romantest7', 'romantest6', 'romantest10', 'pluraltest6']
>>> humansize_dict['romantest9'] ④
'6.5 KiB'
-
Esta compreensão de dicionário constrói uma lista de todos os arquivos no diretório de trabalho atual (
glob.glob('*')
), obtém os metadados de cada arquivo (os.stat(f)
) e constrói um dicionário cujas chaves são nomes de arquivos e cujos valores são os metadados de cada arquivo. -
Esta compreensão de dicionário se baseia na compreensão anterior, filtra arquivos menores que
6000
bytes (if meta.st_size > 6000
) e usa essa lista filtrada para construir um dicionário cujas chaves são o nome do arquivo menos a extensão (os.path.splitext(f)[0]
) e cujos valores são o tamanho aproximado de cada arquivo (humansize.approximate_size(meta.st_size)
). - Como você viu em um exemplo anterior, existem seis desses arquivos, portanto, existem seis itens neste dicionário.
-
O valor de cada chave é a string retornada da função
approximate_size()
.
Outras coisas divertidas para fazer com as compreensões de dicionário
Aqui está um truque com compreensões de dicionário que pode ser útil algum dia: trocar as chaves e os valores de um dicionário.
>>> a_dict = {'a': 1, 'b': 2, 'c': 3}
>>> {value:key for key, value in a_dict.items()}
{1: 'a', 2: 'b', 3: 'c'}
Claro, isso só funciona se os valores do dicionário forem imutáveis, como strings ou tuplas. Se você tentar fazer isso com um dicionário que contenha listas, a falha será espetacular.
>>> a_dict = {'a': [1, 2, 3], 'b': 4, 'c': 5}
>>> {value:key for key, value in a_dict.items()}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <dictcomp>
TypeError: unhashable type: 'list'
Definir Compreensões
Para não ficar de fora, os conjuntos também têm sua própria sintaxe de compreensão. É notavelmente semelhante à sintaxe para compreensões de dicionário. A única diferença é que os conjuntos têm apenas valores em vez de pares chave: valor
.
>>> a_set = set(range(10))
>>> a_set
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
>>> {x ** 2 for x in a_set} ①
{0, 1, 4, 81, 64, 9, 16, 49, 25, 36}
>>> {x for x in a_set if x % 2 == 0} ②
{0, 8, 2, 4, 6}
>>> {2**x for x in range(10)} ③
{32, 1, 2, 4, 8, 64, 128, 256, 16, 512}
- As compreensões de conjuntos podem ter um conjunto como entrada. Esta compreensão de conjunto calcula os quadrados do conjunto de números de 0 a 9.
-
Como as compreensões de lista e de dicionário, as compreensões de conjunto podem conter uma cláusula
if
para filtrar cada item antes de retorná-lo no conjunto de resultados. - As compreensões de conjunto não precisam ter um conjunto como entrada; eles podem tomar qualquer sequência.
Leitura Adicional
-
módulo
os
-
os
- Acesso portátil a recursos específicos do sistema operacional -
módulo
os.path
-
os.path
- Manipulação independente de plataforma de nomes de arquivos -
módulo
glob
-
glob
- Correspondência de padrão de nome de arquivo -
módulo
time
-
time
- Funções para manipular o tempo do relógio - Compreensões de lista
- Compreensões de listas aninhadas
- Técnicas de looping
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