2. FUNDAMENTOS DA LINGUAGEM PYTHON#

Abrir no Google Colab

2.1 Sintaxe básica: variáveis, operadores, expressões#

O entendimento da sintaxe básica do Python é fundamental para começar a programar nesta linguagem, especialmente em aplicações voltadas para o geoprocessamento. Nesta seção abordaremos três elementos essenciais da sintaxe do Python: variáveis, operadores e expressões.

2.1.1 Variáveis#

Em Python, as variáveis são usadas para armazenar informações que podem ser referenciadas e manipuladas em um programa. A atribuição de valores a variáveis em Python é feita usando o operador “=”, e não é necessário declarar explicitamente o tipo da variável, já que Python é uma linguagem de tipagem dinâmica. São exemplos de variáveis:

x = 10
y = 8
nome = 'Ana' 
idade = 30       
altura_metros = 1.75

Tipagem dinâmica refere-se ao mecanismo pelo qual o tipo de uma variável é determinado em tempo de execução, ao contrário da tipagem estática, onde o tipo de uma variável é determinado em tempo de compilação. Em linguagens com tipagem dinâmica, o tipo de uma variável pode mudar durante a execução do programa, dependendo do valor que lhe é atribuído.

Cada linguagem de programação possui suas próprias regras e convenções para nomear variáveis, mas existem algumas práticas gerais recomendadas:

Regras comuns:

  • O nome geralmente começa com uma letra ou sublinhado (_);

  • Pode conter letras (maiúsculas ou minúsculas), números ou sublinhados;

  • Não deve conter espaços ou caracteres especiais;

  • Não deve ser uma palavra reservada da linguagem.

Convenções (de acordo com PEP 8)

O PEP 8 é a convenção de estilo para o código Python. PEP é uma sigla para “Python Enhancement Proposal” (Proposta de Melhoria do Python), e o número 8 refere-se ao número específico deste PEP. O PEP 8 fornece um conjunto de regras e recomendações para formatar o código Python, tornando-o mais legível e consistente em toda a comunidade Python.

  • Nomes de Variáveis: devem ser escritos em letras minúsculas, com palavras separadas por sublinhados. Exemplo: minha_variavel, contador, taxa_de_juros;

  • Variáveis Privadas: Para variáveis que são destinadas a uso interno dentro de um módulo ou classe e que não devem ser acessadas diretamente de fora, é comum usar um sublinhado antes do nome, como _variavel_privada;

  • Variáveis Muito Privadas: Se um nome de variável começa com dois sublinhados __, indica que a variável é “muito privada” e, geralmente, é usada para evitar conflitos de nome em subclasses;

  • Variáveis de Sistema: Se um nome de variável termina com dois sublinhados, é uma variável de sistema ou “mágica” que tem um uso especial em Python, como init, name, etc;

  • Constantes: Constantes são geralmente declaradas em letras maiúsculas com palavras separadas por sublinhados, por exemplo: PI, TAXA_FIXA;

  • Evitar o uso de l (letra ‘el’ minúscula) e O (letra ‘o’ maiúscula): Estes podem ser confundidos com o número 1 e 0, respectivamente.

Atribuição de valores a variáveis

Atribuição de valores a variáveis é o processo de guardar um valor em uma variável. Em muitas linguagens de programação, incluindo Python, o operador de atribuição é o sinal de igual (=). Isso significa que você está associando um valor à variável à esquerda do sinal de igual.

Em Python, a operação de atribuição cria uma referência entre o nome da variável e o valor ou objeto. Portanto, a variável não contém o valor em si, mas sim uma referência para o valor.

Atribuição simples

A atribuição simples é o método mais básico e direto de associar um valor a uma variável. Na atribuição simples, utilizamos o operador “=” para designar um valor a uma variável.

A sintaxe básica é: nome_da_variavel = valor

Aqui estão alguns exemplos usando Python:

x = 15             # Atribui o valor inteiro 15 à variável x

altura = 1.80      # Atribui o valor real 1.80 à variável altura

nome = 'Julia'     # Atribui a string 'Julia' à variável nome

ativo = True       # Atribui o valor booleano True (verdadeiro) à variável ativo

Em linguagens com tipagem dinâmica, como Python, o tipo da variável é determinado automaticamente com base no valor atribuído. Assim, você não precisa declarar explicitamente o tipo da variável ao criá-la.

A principal coisa a lembrar sobre a atribuição simples é que ela designa um valor à variável, e a variável passa a referenciar esse valor. Se o valor for um objeto, a variável não contém o objeto em si, mas sim uma referência a ele.

nome = 'Ana'            # A variável 'nome' recebe o valor 'Ana'.

idade = 30                 # A variável 'idade' recebe o valor '30'.

altura_metros = 1.75       # A variável 'altura_metros' recebe o valor '1.75'.

estudante = True           # A variável 'estudante' recebe o valor 'True'.

notas = [90, 85, 88, 76]   # A variável 'notas' recebe a lista de valores '[90, 85, 88, 76]'

Uma vez que uma variável é nomeada, seu nome não pode ser alterado. No entanto, o valor associado a essa variável pode.

x = 10  # A variável 'x' recebe o valor 10.

x = 20  # O valor de 'x' é alterado para 20, mas seu nome permanece 'x'.

Atribuição múltipla

A atribuição múltipla é uma característica disponível em algumas linguagens de programação, incluindo o Python, que permite que você atribua valores a várias variáveis simultaneamente em uma única instrução. Vamos ver como a atribuição múltipla funciona em Python:

x, y, z = 15, 25, 35

Neste caso, x receberá o valor 15, y receberá o valor 25, e z receberá o valor 35.

Uma das vantagens da atribuição múltipla é a facilidade com que você pode trocar valores entre duas variáveis.

x = 7
y = 12
x, y = y, x
print('O valor de x é:', x)
print('O valor de y é:', y)
O valor de x é: 12
O valor de y é: 7

A atribuição múltipla torna o código mais conciso e, em muitos casos, mais legível, especialmente quando usada de maneira apropriada e não excessiva.

2.1.2 Operadores: aritméticos, comparação, lógicos#

Operadores aritméticos

Os operadores aritméticos são usados em Python para realizar operações matemáticas entre valores ou variáveis. Eles são fundamentais em qualquer linguagem de programação e formam a base para cálculos numéricos. O quadro 1 traz os principais operadores aritméticos em Python.

Adição:

7 + 4
11

Subtração:

7 - 4
3

Multiplicação:

7 * 4
28

Divisão:

9 / 4
2.25

Divisão inteira: Realiza a divisão entre operandos, retornando somente a parte inteira do quociente.

9 // 4
2

Módulo: Retorna o resto da divisão entre operandos.

9 % 4
1

Exponenciação:

7 ** 4
2401

Radiciação:

9 ** 0.5
3.0

É importante notar que a ordem das operações segue a precedência matemática padrão. Por exemplo, a multiplicação e a divisão são avaliadas antes da adição e subtração. Se desejar modificar a ordem das operações, pode usar parênteses. Por exemplo:

(5 + 3) * 2
16
5 + 3 * 2    
11

Operadores de comparação

Os operadores de comparação em Python são usados para comparar dois valores e retornar um valor booleano (True ou False) com base no resultado da comparação. Eles são fundamentais para estruturas de controle condicional, como instruções if, elif e else. O quadro 2 traz os principais operadores de comparação em Python.

Igual a:

6 == 6
True

Diferente de:

6 != 6
False

Menor que:

5 < 6
True

Maior que:

5 > 6
False

Menor ou igual a:

6 <= 6
True

Maior ou igual a:

6 >= 8
False

Operadores lógicos

Os operadores lógicos são usados para avaliar múltiplas condições ou combinar o resultado de diferentes comparações, resultando em um valor booleano (True ou False). O quadro 3 traz os operadores lógicos em Python.

Operador and: Retorna True se ambas as condições forem verdadeiras.

idadeLuiza = 19
idadePietra = 14

Operador or: Retorna True se pelo menos uma das condições for verdadeira.

idadeLuiza >= 18 and idadePietra >= 18 
False

Operador not: Inverte o valor booleano.

idadeLuiza >= 18 or idadePietra >= 18
True

Esses operadores são essenciais para controle de fluxo em programação, permitindo combinações complexas de condições em estruturas como if, while, entre outras. A capacidade de avaliar e combinar condições é uma parte fundamental da lógica de programação. No decorrer de nosso curso, aprofundaremos nosso estudo sobre os operadores, contemplando ainda os operadores de atribuição, de identidade e de associação.

2.1.3 Expressões#

Expressões em Python referem-se a combinações de valores, variáveis e operadores que são avaliadas pelo interpretador Python para produzir um resultado. Em termos simples, uma expressão é como uma instrução matemática que o Python resolve e retorna um valor. No contexto de uma expressão:

  • Valores: São os dados básicos com os quais trabalhamos, como números (2, 4.5) ou strings (“geoprocessamento”, “Python”).

  • Operadores: São símbolos que realizam operações sobre valores, como adição (“+”), subtração (“-”), multiplicação (“*”), divisão (“/”) e muitos outros. Exemplo: “3 + 4” é uma expressão onde “3” e “4” são valores e “+” é um operador.

  • Variáveis: São nomes que designam locais na memória para armazenar valores. Uma vez que um valor é atribuído a uma variável, a variável pode ser usada em expressões. Exemplo: “x = 5” (Aqui, “x” é uma variável e “5” é um valor. Depois disso, é possível usar “x” em expressões como “x + 2”.)

  • Funções: são blocos de código reutilizáveis que realizam uma tarefa específica. Em expressões, funções podem ser usadas para processar valores e produzir resultados. Exemplo: “print(“Geoprocessamento com Python”)” (Aqui, “print” é uma função que exibe seu argumento.)

  • Avaliação: Quando o Python encontra uma expressão, seja em um script ou no terminal interativo, ele avalia (ou calcula) a expressão e retorna um resultado. Exemplo: Na expressão “7 * 6”, o Python avaliará isso e retornará “42”.

2.2 Tipos básicos de dados#

Os tipos básicos de dados (figura 1), também conhecidos como tipos primitivos ou fundamentais, referem-se às categorias mais simples e fundamentais de informações que uma linguagem de programação pode representar e manipular diretamente. Em Python, os principais tipos básicos de dados são: Inteiros, Ponto Flutuante, Strings, Booleanos e Bytes. Esses tipos de dados são a base para a representação e manipulação de informações em Python. O entendimento desses tipos básicos é essencial, pois eles são frequentemente usados em combinação com as estruturas mais avançadas.

Figura 1

Figura 1: Tipos básicos de dados em Python.

2.2.1 Inteiros#

Inteiros são números sem uma parte fracionária, podendo ser positivos, negativos ou zero. Eles são fundamentais na matemática e na ciência da computação e são um dos tipos de dados primitivos mais comuns em linguagens de programação. Em Python, o tipo para inteiros é int.

Características dos Inteiros:

  • Natureza: não têm uma parte decimal. Podem ser tanto números positivos quanto negativos, incluindo o zero;

  • Operações Básicas: Com inteiros, você pode realizar operações aritméticas padrão como adição, subtração, multiplicação e divisão (embora a divisão de dois inteiros, em muitas linguagens de programação modernas, possa resultar em um número de ponto flutuante);

  • Tamanho e Limites: o tamanho (ou a quantidade de memória ocupada) por um inteiro pode variar, mas há um limite. Em versões recentes do Python, o tamanho dos inteiros é flexível e expande conforme necessário, mas é limitado pela quantidade de memória disponível.

Exemplos:

Definindo inteiros:

a = 5
b = -3
c = 0

Imprimindo o tipo para confirmar que são inteiros:

print(type(a))  
<class 'int'>

Operações básicas:

soma = a + b  
print(soma)   
2
subtracao = a - b  
print(subtracao) 
8
multiplicacao = a * b  
print(multiplicacao)  
-15
divisao = a / b  
print(divisao)  
-1.6666666666666667

No último exemplo, embora ambos a e b sejam inteiros, a divisão resulta em um número de ponto flutuante (float) em Python. Se você quiser um resultado inteiro da divisão (descartando a parte fracionária), pode usar o operador de divisão inteira:

divisao_inteira = a // b
print(divisao_inteira)
-2

Além disso, Python oferece operadores específicos, como o operador módulo, que retorna o resto da divisão de dois inteiros:

resto = a % b
print(resto)  
-1

2.2.2 Floats (Números de Ponto Flutuante)#

São números que têm uma parte decimal. Eles são usados para representar quantidades que não são inteiras. Em termos técnicos, “ponto flutuante” refere-se à maneira como estes números são representados internamente e a operações que podem ser realizadas com eles. Em Python, o tipo para números de ponto flutuante é float.

Características dos Floats:

  • Natureza: Podem representar tanto números fracionários quanto inteiros;

  • Precisão: Devido à maneira como os floats são armazenados e representados internamente, eles têm uma precisão limitada, o que pode levar a pequenas imprecisões em cálculos;

  • Operações Básicas: Como os inteiros, os floats também suportam operações aritméticas padrão, como adição, subtração, multiplicação e divisão;

  • Representação: Em algumas situações, números que parecem ser simples podem ter representações imprecisas quando armazenados como floats. Por exemplo, 0.1 pode não ser armazenado exatamente como 0.1 devido à maneira como os números de ponto flutuante são representados.

Exemplos:

Definindo floats:

x = 3.14
y = -0.001
z = 2.0 

Imprimindo o tipo para confirmar que são floats:

print(type(x))  
<class 'float'>

Operações básicas:

soma = x + y  
print(soma)  
3.1390000000000002
multiplicacao = x * y  
print(multiplicacao)
-0.00314
divisao = x / z  
print(divisao) 
1.57

No contexto de aritmética de ponto flutuante em Python, é importante estar ciente da precisão. Veja o seguinte exemplo:

Saída = 0.1 + 0.1 + 0.1
print(Saída)  
0.30000000000000004
print(Saída == 0.3) 
False

Saída pode ser ‘False’ devido a imprecisões de ponto flutuante.

A maneira como os números de ponto flutuante é representada internamente pode levar a essa imprecisão. Em muitos casos, é aconselhável usar uma comparação com alguma “margem de erro” em vez de uma comparação direta:

epsilon = 0.00001
print(abs(Saída - 0.3) < epsilon)  
True

Conversão entre tipos numéricos: inteiro e float

Em Python, é comum ser necessário converter valores entre diferentes tipos numéricos, especialmente entre int (números inteiros) e float (números de ponto flutuante). Para converter um número inteiro para um número de ponto flutuante, você pode usar a função float().

num_inteiro = 5
num_float = float(num_inteiro)
print(num_float) 
5.0

Conversão de float para int

Para converter um número de ponto flutuante para um inteiro, você pode usar a função int(). É importante notar que essa conversão simplesmente descarta a parte decimal do número, sem arredondá-la.

num_float = 5.7
num_inteiro = int(num_float)

print(num_inteiro)
5

Avisos:

  • Ao converter de float para int, é importante lembrar que a parte decimal é truncada, não arredondada. Assim, 4.9 se tornará 4, e não 5.

  • A conversão de um número muito grande ou muito pequeno pode resultar em imprecisões. Sempre esteja ciente das limitações da precisão do ponto flutuante ao trabalhar com conversões.

  • É sempre uma boa prática verificar os valores antes de convertê-los para evitar erros em tempo de execução.

Em algumas situações, você pode querer arredondar o número de ponto flutuante antes de convertê-lo para inteiro. Nesse caso, você pode usar a função round() para arredondar ao número inteiro mais próximo.

num_float1 = 5.7
num_inteiro1 = round(num_float1)
num_float2 = 5.2
num_inteiro2 = round(num_float2)
print(num_inteiro1)  
6
print(num_inteiro2)  
5

Em Python, a conversão de tipo pode ser feita de maneira implícita, ocorrendo automaticamente pelo interpretador sem a necessidade de ação direta do programador. Quando você realiza uma operação aritmética entre um int e um float, o Python converte automaticamente o int em float.

inteiro = 3
flutuante = 2.5
Saída = inteiro + flutuante
print(Saída)  
5.5
print(type(Saída))  
<class 'float'>

Note que a variável resultado é do tipo float mesmo que apenas um dos operandos fosse originalmente float.

2.2.3 Booleanos#

Os booleanos são um tipo de dado que representa uma das duas possíveis verdades: verdadeiro ou falso. Em Python, essas verdades são representadas pelas palavras-chave True (verdadeiro) e False (falso). O tipo booleano é fundamental em programação, pois muitas decisões e operações lógicas são baseadas em testes que resultam em um valor verdadeiro ou falso. Em Python, o tipo para valores booleanos é bool.

Características dos Booleanos:

  • Simplicidade: Booleanos só têm dois possíveis valores: True ou False;

  • Operações Lógicas: Os booleanos são frequentemente usados com operadores lógicos, como and, or, e not, para criar expressões mais complexas;

  • Comparação: Operadores de comparação, como “==”, “!=”, “<”, “>”, “<=”, e “>=”, frequentemente resultam em valores booleanos.

Exemplos de código:

Definindo booleanos

esta_feliz = True
esta_triste = False

Imprimindo o tipo para confirmar que são booleanos

print(type(esta_feliz)) 
<class 'bool'>

Operações lógicas

Saída1 = esta_feliz and esta_triste
print(Saída1) 
False
Saída2 = esta_feliz or esta_triste
print(Saída2)  
True
Saída3 = not esta_feliz
print(Saída3)  
False

Usando booleanos em comparações:

idade = 25
maior_de_idade = idade >= 18
print(maior_de_idade) 
True

Comparações podem ser usadas diretamente em instruções condicionais

if idade > 30:
    print('Idade é maior que 30.')
else:
    print('Idade é 30 ou menor.')
Idade é 30 ou menor.

Estudaremos as instruções condicionais mais adiante em nosso curso. Em Python, muitos outros tipos de dados podem ser avaliados em um contexto booleano, significando que eles podem ser tratados como True ou False sob certas condições. Por exemplo:

  • Valores numéricos 0 (zero) são tratados como False, enquanto outros valores numéricos são tratados como True;

  • Coleções vazias (como listas, tuplas e strings vazias) são tratadas como False. Coleções não vazias são True;

  • None é sempre tratado como False.

2.2.4 Bytes#

Bytes são sequências imutáveis de inteiros pequenos no intervalo de 0 a 255. Eles são frequentemente usados para representar dados binários, como arquivos de imagem, áudio ou qualquer outro tipo de dado que não seja simplesmente texto. Em Python, dados do tipo byte são essenciais quando lidamos com operações de I/O (entrada e saída), especialmente quando os dados não são representáveis como strings de texto. Em Python, você pode criar bytes usando a sintaxe “b’’ ou com a função bytes().

Características dos Bytes:

  • Imutabilidade: são imutáveis. Uma vez definidos, seus valores não podem ser alterados;

  • Representação: são sequências de inteiros (byte literals) que variam de 0 a 255;

  • Uso comum: Ideal para lidar com dados binários, como operações de I/O, comunicação de rede, arquivos binários, etc.

Exemplos:

Criar bytes usando a sintaxe b’’

dados_binarios = b'Ol\xc3\xa1 Mundo'
print(dados_binarios)  
b'Ol\xc3\xa1 Mundo'

Converter uma string em bytes

string_normal = 'Olá Mundo'
bytes_convertidos = string_normal.encode('utf-8')
print(bytes_convertidos)  
b'Ol\xc3\xa1 Mundo'

Acessar elementos dos bytes (semelhante a listas e strings)

primeiro_byte = dados_binarios[0]
print(primeiro_byte)  
79

Bytes são imutáveis. A tentativa de mudar um valor resultará em um erro.

# dados_binarios[0] = 80  

# TypeError: 'bytes' object does not support item assignment

Criando bytes a partir de uma lista de inteiros

lista_de_inteiros = [65, 66, 67]
dados_binarios2 = bytes(lista_de_inteiros)
print(dados_binarios2)  
b'ABC'

Vale mencionar que, além de bytes, Python também oferece o tipo bytearray, que é uma versão mutável dos bytes. Enquanto os valores dentro de um objeto bytes não podem ser modificados após sua criação, os valores dentro de um bytearray podem ser alterados.

2.2.5 Strings#

Strings em Python são sequências de caracteres que representam texto. São fundamentais em programação, pois permitem representar e manipular dados textuais, armazenar informações, exibir mensagens ao usuário, entre outras funções. Algumas das características das Strings são:

  • Imutabilidade: Depois de definida, uma string não pode ter seu conteúdo alterado. Qualquer operação que “modifique” a string, na realidade, cria uma nova string com o conteúdo alterado;

  • Indexação e fatiamento: Como as strings são sequências, podemos acessar seus caracteres por índices e fatiar sub-strings;

  • Métodos integrados: Python fornece muitos métodos úteis para processar e manipular strings.

Definição de Strings com o uso de aspas

Strings podem ser definidas usando aspas simples, aspas duplas ou três aspas duplas (para strings de várias linhas). Todas essas formas são válidas e a escolha entre elas muitas vezes se resume a preferência pessoal ou à necessidade de incluir certos caracteres na string (por exemplo, se você quiser incluir um apóstrofo em uma string, pode ser mais fácil usar aspas duplas para definir a string).

Definindo strings:

nome = 'Python'
frase = 'Aprendendo Python!'
paragrafo = '''Python é uma linguagem de programação
importante no contexto do Geoprocessamento.'''

Imprimindo strings:

print(nome) 
Python
print(frase) 
Aprendendo Python!
print(paragrafo)
Python é uma linguagem de programação
importante no contexto do Geoprocessamento.

Indexação de Strings

Em Python, strings são sequências de caracteres. Cada caractere em uma string tem um índice associado a ele, começando em 0 para o primeiro caractere, 1 para o segundo, e assim por diante. Isso permite que você acesse caracteres específicos em uma string usando seu índice. Na figura 2, temos a string “Python” com sua indexação.

Figura 2

Figura 2: Índice da string Python.

Aqui estão algumas questões básicas relacionadas ao acesso de string por índice:

Acessar um Caractere Específico:

nome = 'Python'
print(nome[0])  
P
nome = 'Python'
print(nome[3])  
h

Índices Negativos: Python suporta índices negativos, o que significa começar a contar a partir do final da string.

print(nome[-1])  
n
print(nome[-2])  
o

Limites de Índice: Se você tentar acessar um índice que está fora da faixa da string, você receberá um erro IndexError.

#print(nome[6])  

# IndexError: string index out of range

O índice 6 está fora da faixa.

Fatiamento de Strings: Além de acessar caracteres individuais, você também pode acessar subconjuntos ou “fatias” de uma string usando o conceito de fatiamento.

print(nome[1:4])  
yth

Imutabilidade de Strings

Como vimos anteriormente, strings são consideradas objetos imutáveis em Python. Isso significa que, uma vez que uma string é criada, você não pode modificar diretamente seu conteúdo. Se você tentar reatribuir um valor a uma posição específica da string, você receberá um erro. Por exemplo, se você tentar alterar o primeiro caractere de nome para ‘C’, você receberá um TypeError, indicando que a operação não é permitida.

# nome[0] = 'C'  

Concatenação de strings

Concatenação é o processo de combinar duas ou mais strings para formar uma única string. Em Python, o operador “+” é usado para concatenar strings. Isso significa que ele combina as strings fornecidas para formar uma nova string.

nome = 'Python'
mensagem = 'Olá, ' + nome + '!'
print(mensagem)  
Olá, Python!

Interpolação de Strings

A interpolação de strings, particularmente usando f-strings (introduzida no Python 3.6), é uma maneira eficiente e legível de formatar strings. Ela permite que você incorpore expressões diretamente dentro de strings literais, usando {}. Os benefícios das f-strings incluem:

  • Legibilidade: tornam o código mais legível, especialmente quando você tem múltiplas variáveis ou expressões a serem interpoladas em uma string;

  • Performance: geralmente oferecem uma performance melhor em comparação com outras técnicas de formatação de string em Python;

  • Flexibilidade: Você pode incorporar qualquer expressão válida do Python dentro das chaves {}, o que torna as f-strings versáteis.

Exemplo:

valor_venda = 30
mensagem_venda = f'O valor total da compra foi R$ {valor_venda}.'
print(mensagem_venda)  
O valor total da compra foi R$ 30.

Nesse código:

a. Definimos uma variável valor_venda com o valor 30;

b. Criamos uma f-string “mensagem_venda” onde incorporamos a variável “valor_venda” diretamente dentro da string usando chaves {}. Isso significa que o valor de “valor_venda” será inserido na posição onde {valor_venda} está na string;

c. Usando a função print(), imprimimos o valor da variável “mensagem_venda” no console;

d. Como “mensagem_venda” contém a string “O valor total da compra foi R$ 30.”, essa é a saída.

Métodos integrados

As strings em Python vêm com uma variedade de métodos integrados que permitem realizar operações comuns em strings sem a necessidade de escrever funções adicionais. Esses métodos são essencialmente funções que estão “ligadas” a objetos de string e podem ser chamados diretamente em qualquer string. Abaixo estão algumas das operações comuns e seus métodos correspondentes:

Conversão de Caso:

  • upper(): Converte todos os caracteres da string para maiúsculas.

  • lower(): Converte todos os caracteres da string para minúsculas.

  • capitalize(): Converte o primeiro caractere da string para maiúscula.

  • title(): Converte o primeiro caractere de cada palavra para maiúscula.

Verificação:

  • startswith(substring): Retorna True se a string começar com a substring especificada.

  • endswith(substring): Retorna True se a string terminar com a substring especificada.

  • isalpha(): Retorna True se todos os caracteres da string forem letras.

  • isdigit(): Retorna True se todos os caracteres da string forem dígitos.

Manipulação:

  • replace(old, new): Substitui todas as ocorrências da substring old pela substring new.

  • strip(): Remove espaços em branco (ou outros caracteres especificados) do início e do final da string.

  • split(delimiter): Divide a string no delimiter especificado e retorna uma lista de substrings.

Busca:

  • find(substring): Retorna o índice da primeira ocorrência da substring. Se a substring não for encontrada, retorna “-1”.

  • count(substring): Retorna o número de ocorrências não sobrepostas da substring na string original.

Outros:

  • join(iterable): Une um iterável (como uma lista) em uma única string usando a string como delimitador.

  • len(string): Embora não seja exatamente um método de string, a função len() retorna o número de caracteres em uma string.

Exemplos:

texto = 'Introdução a programação, para Geoprocessamento.'
print(texto.lower())  
introdução a programação, para geoprocessamento.
print(texto.upper())  
INTRODUÇÃO A PROGRAMAÇÃO, PARA GEOPROCESSAMENTO.
print(texto.replace('ã', 'a')) 
Introduçao a programaçao, para Geoprocessamento.
print(texto.split(','))  
['Introdução a programação', ' para Geoprocessamento.']

2.3 Estruturas de dados#

As estruturas de dados são formas organizadas e eficientes de armazenar, acessar e manipular conjuntos de dados. Elas definem a relação entre os dados e as operações que podem ser realizadas sobre eles. A escolha da estrutura de dados adequada é crucial para a implementação eficiente de algoritmos e pode impactar significativamente o desempenho e a usabilidade de um programa. As estruturas de dados (figura 3) que serão contempladas em nosso estudo são as Listas, as Tuplas, os Dicionários e os Conjuntos.

Figura 3

Figura 3: Estruturas de dados em Python.

As razões pelas quais as estruturas de dados são tão importantes incluem:

  • Eficiência Computacional: Diferentes estruturas de dados têm diferentes custos em termos de tempo e espaço. Escolher a estrutura de dados certa pode permitir que os algoritmos operem mais rapidamente, economizando tempo de CPU e memória;

  • Organização de Dados: Estruturas de dados permitem que os dados sejam organizados de forma a serem facilmente acessíveis. Por exemplo, um dicionário em Python permite recuperar um valor em tempo constante, dada uma chave;

  • Facilita a Implementação de Algoritmos: Muitos algoritmos têm requisitos específicos quanto à forma como os dados devem ser armazenados para que funcionem corretamente e eficientemente. As estruturas de dados fornecem os meios para atender a esses requisitos;

  • Flexibilidade: Com a variedade de estruturas de dados disponíveis, os programadores podem escolher a estrutura que melhor se adapta à natureza dos dados e às operações que precisam ser realizadas sobre eles;

  • Redução de Complexidade: Usar a estrutura de dados apropriada pode reduzir a complexidade do código, tornando-o mais legível e fácil de manter;

  • Abstração: Estruturas de dados geralmente vêm com operações padrão que podem ser realizadas sobre elas (como inserir, excluir, pesquisar). Isso permite voltar a atenção para o problema que se está tentando resolver ao invés de se preocupar com os detalhes de como essas operações são implementadas;

  • Escalabilidade: À medida que os conjuntos de dados crescem, a importância de usar a estrutura de dados correta torna-se ainda mais crítica. Algumas estruturas que funcionam bem para pequenas quantidades de dados tornam-se impraticáveis em escala maior;

  • Aprimoramento da Tomada de Decisões: Em aplicações de negócios e análise de dados, as estruturas de dados adequadas podem facilitar a análise de grandes conjuntos de dados, levando a decisões mais informadas.

2.3.1 Listas#

Listas são estruturas de dados fundamentais em programação que representam coleções ordenadas de itens. Em muitas linguagens, inclusive em Python, as listas são dinâmicas, o que significa que elas podem crescer ou diminuir em tamanho conforme necessário. Os itens em uma lista são organizados sequencialmente, permitindo que os usuários acessem cada elemento por meio de um índice, que é uma posição numérica na lista. Esta natureza ordenada torna as listas adequadas para tarefas que exigem a manutenção da ordem dos dados, como quando se precisa de uma sequência específica de elementos.

As listas podem conter elementos de qualquer tipo, seja números, strings, ou até mesmo outras listas e objetos complexos. A mutabilidade é outra característica crucial das listas em Python. Isso significa que, após a criação de uma lista, é possível modificar seu conteúdo, adicionar novos itens ou remover itens existentes.

No contexto do geoprocessamento, as listas desempenham papel essencial na manipulação e análise de dados geoespaciais. Por exemplo, uma lista pode ser usada para armazenar uma série de pontos que definem uma rota ou um caminho. Em análises de cobertura de terreno, listas podem conter amostras de elevações em pontos específicos para gerar perfis topográficos. Em sistemas de informações geográficas (SIG), ao trabalhar com múltiplas camadas de dados, como áreas urbanas, corpos d’água e vegetação, uma lista pode ser empregada para armazenar e iterar sobre essas camadas. Além disso, ao realizar análises de proximidade, as listas podem ser utilizadas para armazenar e comparar coordenadas de diferentes objetos, ajudando a determinar, por exemplo, todos os pontos de interesse dentro de uma determinada distância de uma estrada ou rio.

Criação de listas

Listas são criadas colocando uma sequência de valores separados por vírgulas entre colchetes. Exemplos:

#Lista de números
numeros = [1, 2, 3, 4, 5]
#Lista de strings:
pais = ['Brasil', 'Uruguai', 'Argentina']
#Lista mista (com diferentes tipos de dados):
dados_mistos = [42, 'Manaus', 3.14, True, 'Python', 7]
#Lista vazia:
lista_vazia = []
#Lista aninhada:
lista_aninhada = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Acessando os elementos da lista

  • Acesso pelo Índice:

As listas são indexadas por números inteiros, começando por 0 para o primeiro item.

#Acessando o primeiro elemento da lista 'país':
primeiro_pais = pais[0]
print(primeiro_pais)  
Brasil
#Acessando o terceiro elemento da lista numeros:
terceiro_numero = numeros[2]
print(terceiro_numero)  
3

Atenção:

Ao tentar acessar um índice que não existe na lista (por exemplo, pais[3] quando a lista tem apenas 3 elementos) resultará em um IndexError.

# pais[3]

# IndexError: list index out of range

  • Acesso com Índices Negativos:

Os índices negativos permitem acessar a lista do final para o começo.

# Acessando o último item da lista país:
pais = ['Brasil', 'Uruguai', 'Argentina']
ultimo_pais = pais[-1]
print(ultimo_pais)
Argentina
penultimo_pais = pais[-2]
print(penultimo_pais)  
Uruguai
  • Fatiamento:

O fatiamento é uma técnica em Python que permite acessar uma subseção (ou “fatia”) de uma lista. A sintaxe geral do fatiamento é `lista[início:fim:passo]`, onde “início” é o índice inicial da fatia, “fim” é o índice onde a fatia termina (exclusivo) e “passo” é o intervalo entre os itens da fatia. Vejamos alguns exemplos.

dados_mistos = [42, 'Manaus', 3.14, True, 'Python', 7]
#Acessando os três primeiros itens:
primeiros_tres = dados_mistos[:3]
print(primeiros_tres)
[42, 'Manaus', 3.14]
#Acessando itens do terceiro ao quinto:
meio = dados_mistos[2:5]
print(meio)  
[3.14, True, 'Python']
#Acessando os últimos três itens:
ultimos_tres = dados_mistos[-3:]
print(ultimos_tres)  
[True, 'Python', 7]
#Acessando itens de dois em dois:
passo_dois = dados_mistos[::2]
print(passo_dois)  
[42, 3.14, 'Python']
  • Acesso múltiplo (ou desempacotamento):

Permite atribuir vários elementos de uma lista (ou tupla) a variáveis diferentes em uma única linha. Suponha que temos uma lista representando dados sobre uma cidade: nome da cidade, latitude e longitude.

cidade_info = ['Brasília', -15.7797, -47.9297]

Agora, vamos usar o desempacotamento para acessar múltiplos elementos da lista. É importante garantir que o número de variáveis à esquerda da atribuição corresponda ao número de elementos que você está tentando desempacotar da lista. Se houver uma discrepância, o Python lançará um erro.

nome, lat, lon = cidade_info

Neste exemplo, o valor “Brasília” da lista cidade_info será atribuído à variável nome, o valor -17.7797 será atribuído à variável “lat”, e o valor -47.9297 será atribuído à variável “lon”. Imprimindo os valores das variáveis:

print(nome, lat, lon) 
Brasília -15.7797 -47.9297

  • Acesso por funções Built-in:

Podemos acessar os dados de uma lista utilizando funções do Python. Funções comumente utilizadas são len(), max() e min():

altitudes = [320, 540, 890, 1200, 45, 650]

Usando len() para determinar o número de elementos na lista:

numero_registros = len(altitudes)
print(numero_registros)
6

Usando max() para encontrar a maior altitude:

maior_altitude = max(altitudes)

Usando o min() para determinar a menor altitude

menor_altitude = min(altitudes)
print(menor_altitude) 
45

Apresentamos nesta seção alguns conceitos básicos sobre a criação de listas e acesso aos seus elementos em Python. Mais adiante, veremos como acessar listar por iteração. Outro conceito importante que será estudado é o de “List comprehension”.

Modificação de listas: adição e remoção de elementos

As listas são mutáveis, o que significa que podemos alterar seus elementos após a criação. No contexto de modificar listas, há duas operações fundamentais: adicionar e remover elementos. Adicionar elementos pode envolver a inclusão de um único item, anexando-o ao final da lista, ou inserindo-o em uma posição específica. Também é possível juntar duas listas, expandindo uma com os elementos da outra. Por outro lado, a remoção de elementos pode se referir à exclusão de um item específico, independentemente de sua posição, ou à retirada de um item com base em sua posição. A capacidade de manipular listas dessa maneira oferece uma flexibilidade imensa, sendo um pilar central na manipulação de dados em Python.

  • Adicionando elementos a uma lista:

Método append(): Adiciona um item ao final da lista.

pontos_referencia = ['Montanha', 'Rio', 'Floresta']
pontos_referencia.append('Lago')
print(pontos_referencia) 
['Montanha', 'Rio', 'Floresta', 'Lago']

Método insert(): Insere um item em uma posição específica.

pontos_referencia.insert(0, 'Vale')
print(pontos_referencia)  
['Vale', 'Montanha', 'Rio', 'Floresta', 'Lago']

Método extend() ou “+=”: Adiciona múltiplos elementos à lista.

coordenadas = [(12.34, 56.78), (23.45, 67.89)]
novas_coordenadas = [(34.56, 78.90), (45.67, 89.01)]
coordenadas.extend(novas_coordenadas)
print(coordenadas)  
[(12.34, 56.78), (23.45, 67.89), (34.56, 78.9), (45.67, 89.01)]
# Usando +=
coordenadas2 = [(12.34, 56.78), (23.45, 67.89)]
coordenadas2 += [(34.56, 78.90), (45.67, 89.01)]
print(coordenadas2)
[(12.34, 56.78), (23.45, 67.89), (34.56, 78.9), (45.67, 89.01)]
  • Removendo elementos de uma lista:

Método remove(): Remove a primeira ocorrência do valor especificado.

rios = ['Amazonas', 'São Francisco', 'Tietê']
rios.remove('Tietê')
print(rios)
['Amazonas', 'São Francisco']

Método pop(): Remove e retorna o item na posição especificada (ou o último item se a posição não for especificada).

montanhas = ['Andes', 'Himalaia', 'Alpes']
montanha_removida = montanhas.pop(1)
print(montanha_removida)  # Saída: 'Himalaia'
print(montanhas)  
Himalaia
['Andes', 'Alpes']

Instrução del: Remove o item na posição especificada ou uma fatia de itens.

regioes = ['Norte', 'Sul', 'Sudeste', 'Nordeste', 'Centro-Oeste']
del regioes[2:4]  # Remove as regiões 'Sudeste' e 'Nordeste'
print(regioes)  
['Norte', 'Sul', 'Centro-Oeste']

Operações comuns: ordenamento, fatiamento, concatenação, repetição, membership.

No geoprocessamento, frequentemente trabalhamos com conjuntos de dados que precisam ser manipulados, analisados e transformados. As listas em Python oferecem várias operações comuns que são extremamente úteis neste contexto. Vamos explorar algumas delas com exemplos.

  • Ordenamento:

Listas podem ser ordenadas para obter uma sequência crescente ou decrescente. Exemplo: Suponha que temos uma lista de altitudes de diferentes locais e queremos ordená-las.

altitudes = [312, 980, 45, 1235, 910]
altitudes.sort()
print(altitudes)  
[45, 312, 910, 980, 1235]

Para ordenar a lista em ordem decrescente (ordenamento inverso), você pode usar o argumento reverse=True com o método sort().

altitudes.sort(reverse=True)
print(altitudes) 
[1235, 980, 910, 312, 45]
  • Fatiamento (Slicing):

O fatiamento permite obter subconjuntos de uma lista. Exemplo: Se tivermos uma lista de coordenadas e quisermos obter apenas as três primeiras:

coordenadas = [(12.34, 56.78), (23.45, 67.89), (34.56, 78.90), (45.67, 89.01)]
primeiras_tres = coordenadas[:3]
print(primeiras_tres)  
[(12.34, 56.78), (23.45, 67.89), (34.56, 78.9)]
  • Concatenação:

Podemos combinar listas para criar uma nova. Exemplo: Se tivermos duas listas de cidades e quisermos juntá-las:

cidades_A = ['Manaus', 'Fortaleza']
cidades_B = ['Goiânia', 'Florianópolis']
todas_cidades = cidades_A + cidades_B
print(todas_cidades)  
['Manaus', 'Fortaleza', 'Goiânia', 'Florianópolis']
  • Repetição:

Listas podem ser repetidas usando o operador “*”. Exemplo: Criar uma lista de valores padrão para a qualidade do solo em diversas regiões:

qualidade_padrao = ['fértil'] * 4
print(qualidade_padrao)  
['fértil', 'fértil', 'fértil', 'fértil']
  • Membership:

Podemos verificar se um elemento pertence a uma lista usando a palavra-chave “in”. Exemplo: Verificar se uma determinada cidade está em nossa lista de cidades monitoradas:

resultado = 'Rio de Janeiro' in cidades_A
print(resultado)  
False

2.3.2 Tuplas#

As tuplas são uma das estruturas de dados em Python que permitem armazenar uma coleção ordenada de itens. Assim como as listas, as tuplas podem conter elementos de tipos diferentes. No entanto, há algumas diferenças importantes entre listas e tuplas:

  • Imutabilidade: Uma vez que uma tupla é criada, você não pode modificar seus elementos. Isso significa que você não pode adicionar, remover ou alterar elementos após a tupla ser definida. Essa imutabilidade faz das tuplas uma escolha segura para representar coleções de dados que não devem ser alteradas durante a execução de um programa;

  • Sintaxe: Tuplas são geralmente definidas colocando os elementos entre parênteses (), enquanto listas usam colchetes;

  • Uso: Devido à sua natureza imutável, as tuplas são frequentemente usadas em situações em que é necessário garantir que os dados não sejam modificados. Alguns exemplos incluem:

    • Chaves em dicionários: Em Python, as chaves de um dicionário devem ser imutáveis, tornando as tuplas uma opção adequada para chaves compostas;

    • Retorno de múltiplos valores de funções: É comum usar tuplas para retornar múltiplos valores de uma função;

    • Armazenar dados que não devem ser alterados: Se você tem uma coleção de valores que nunca devem ser alterados durante a vida útil do programa (por exemplo, constantes), as tuplas são uma opção natural.

No contexto do geoprocessamento, as tuplas desempenham vários papéis importantes, aproveitando sua natureza imutável e ordenada. Aqui estão algumas maneiras de como as tuplas podem ser aplicadas:

  • Coordenadas Geográficas: As tuplas são uma escolha natural para representar pontos no espaço, como coordenadas (latitude, longitude). Sua natureza imutável garante que as coordenadas de um ponto específico não sejam modificadas acidentalmente;

  • Dados matriciais: Em análises de dados matriciais, cada pixel pode ser representado por uma tupla que denota seu valor em várias bandas (por exemplo, bandas de imagem de satélite);

  • Atributos Compostos: Em muitos Sistemas de Informações Geográficas (SIG), os atributos associados a um objeto podem ser armazenados como tuplas. Por exemplo, um objeto representando um edifício pode ter um atributo que é uma tupla contendo (número de andares, área, ano de construção);

  • Chaves Únicas: No geoprocessamento, frequentemente, trabalhamos com bancos de dados espaciais. Quando se deseja criar chaves compostas para tabelas, as tuplas podem ser usadas para representar combinações únicas de diferentes colunas;

  • Retorno de Funções: Muitas funções em bibliotecas de geoprocessamento podem retornar múltiplos valores. Por exemplo, uma função que calcula a distância e o ângulo entre dois pontos retornaria ambos os valores como uma tupla;

  • Definição de Extensões Espaciais: Em algumas operações, como cortar ou recortar um dataset, é necessário definir a extensão espacial (bounding box). Isso pode ser representado por uma tupla de valores mínimos e máximos (xmin, ymin, xmax, ymax);

  • Especificação de Parâmetros: Muitas operações geoespaciais exigem um conjunto de parâmetros que não mudam durante a operação. Uma tupla pode armazenar esses valores de maneira confiável.

Criação de tuplas

A criação de tuplas pode ser realizada de acordo com os exemplos a seguir:

Tupla vazia:

nome = ()

Tupla com coordenadas geográficas:

coordenada = (45.4215, -75.6972)  

As tuplas podem ser criadas com ou sem a inserção de parênteses:

coordenada = 45.4215, -75.6972 

Tupla mista, com dados do tipo string e do tipo inteiro:

rio_info = ('Rio Amazonas', 6575, 'América do Sul')  

Tupla aninhada (contendo tuplas e listas):

tupla_aninhada = ((1, 2, 3), [4, 5, 6], (7, 8), [9, 10, 11])

Acessando os elementos de uma tupla

O acesso aos elementos de uma tupla em Python é feito de maneira análoga às listas. Seja por índice, acesso negativo, fatiamento, desempacotamento, uso de funções built-in ou loops, as técnicas usadas para acessar os elementos são as mesmas para ambas as estruturas de dados. A seguir temos um exemplo de acessos aos elementos da tupla “temperaturas”.

Criação de tupla com elementos representando a altitude:

temperaturas = (20.5, 23.2, 18.7, 21.9, 19.4)
primeira_temp = temperaturas[0]
print(primeira_temp)  
20.5
ultima_temp = temperaturas[-1]
print(ultima_temp) 
19.4
sub_tupla = temperaturas[1:4]
print(sub_tupla) 
(23.2, 18.7, 21.9)
t1, t2, t3, t4, t5 = temperaturas
print(t1, t2, t3, t4, t5) 
20.5 23.2 18.7 21.9 19.4
tamanho = len(temperaturas)
print(tamanho) 
5
temp_max = max(temperaturas)
print(temp_max) 
23.2
temp_min = min(temperaturas)
print(temp_min) 
18.7

Conversão entre listas e tuplas

A conversão entre listas e tuplas é uma tarefa comum e fácil de realizar em Python. Tanto é possível converter uma lista em uma tupla quanto converter uma tupla em uma lista, conforme exemplos a seguir:

Convertendo uma lista em uma tupla:

lista = [1, 2, 3, 4]
tupla = tuple(lista)

Verificando o tipo de dado:

print('Tipo de dado após a conversão:', type(tupla))
Tipo de dado após a conversão: <class 'tuple'>

Convertendo uma tupla em uma lista:

tupla = (1, 2, 3, 4)
lista = list(tupla)

Verificando o tipo de dado:

print('Tipo de dado após a conversão:', type(lista))
Tipo de dado após a conversão: <class 'list'>

Essas conversões são úteis quando você tem uma tupla, mas precisa modificar alguns de seus elementos (o que não pode ser feito diretamente, pois as tuplas são imutáveis) ou quando você tem uma lista e quer garantir que seus elementos não sejam alterados acidentalmente em outra parte do código. No entanto, é importante observar que essas operações criam novas instâncias de objetos e não alteram as originais. Então, se você modificar a nova lista criada a partir de uma tupla, a tupla original permanecerá inalterada e vice-versa.

Como escolher entre a utilização de tuplas ou de listas?

Escolher entre tuplas e listas em Python geralmente depende do contexto e da intenção do uso. Aqui estão algumas diretrizes para ajudá-lo a decidir:

a) Imutabilidade:

  • Tuplas: São imutáveis. Uma vez que você cria uma tupla, não pode alterar seus elementos ou seu tamanho. Isso é útil quando você quer garantir que os dados permaneçam constantes e não sejam alterados acidentalmente em qualquer parte do programa.

  • Listas: São mutáveis. Você pode modificar, adicionar ou remover elementos de uma lista após sua criação.

b) Semântica:

  • Tuplas: Em muitos contextos, tuplas são usadas para representar coleções de itens heterogêneos (por exemplo, coordenadas (x, y), dados de um banco (nome, idade, endereço)). Elas geralmente têm um número fixo de elementos, cada um com um significado específico.

  • Listas: São geralmente usadas para coleções homogêneas de itens, onde cada item tem o mesmo tipo e significado (por exemplo, uma lista de números, uma lista de nomes).

c) Desempenho:

  • Tuplas: Como são imutáveis, tuplas podem ser ligeiramente mais rápidas do que listas em certas operações, como iteração.

  • Listas: Devido à sua natureza mutável, operações que alteram a lista (como adicionar ou remover elementos) podem ter um custo de desempenho.

d) Uso em dicionários:

  • Tuplas: Podem ser usadas como chaves em dicionários, devido à sua imutabilidade.

  • Listas: Não podem ser usadas como chaves em dicionários, porque são mutáveis.

e) Intenção:

  • Tuplas: Transmitir ao leitor do código que a coleção não deve ser modificada.

  • Listas: Indicar que a coleção pode ser modificada, e que funções ou métodos que alteram listas podem ser aplicados.

f) Espaço em memória:

  • Tuplas: Podem ser mais eficientes em termos de espaço em relação às listas, porque não têm o overhead adicional associado à mutabilidade das listas.

Na prática, use tuplas para:

  • Representar coleções imutáveis de itens;

  • Atuar como chaves em dicionários;

  • Garantir que os dados não sejam modificados acidentalmente;

  • Retornar múltiplos valores de funções.

Use listas para:

  • Representar coleções que podem requerer alterações;

  • Realizar diversas operações como inserção, remoção, etc.;

  • Armazenar coleções de dados que são dinâmicos por natureza.

2.3.3 Dicionários#

Dicionários são estruturas de dados em Python que armazenam pares chave-valor. Cada chave em um dicionário deve ser única, e os valores associados a essas chaves podem ser acessados diretamente pela chave. A natureza de mapeamento dos dicionários os torna altamente úteis para representar coisas como dados de configuração, registros de tabelas ou qualquer estrutura de dados que associe um identificador (a chave) a um valor. Eles são especialmente úteis quando se deseja criar estruturas de dados complexas e altamente personalizadas. É importante destacar que os dicionários são mutáveis, o que significa que você pode adicionar, modificar e remover pares de chave-valor após o dicionário ter sido criado.

Dada a complexidade inerente aos dados geográficos - que muitas vezes envolvem não apenas coordenadas, mas também uma variedade de atributos associados - os dicionários são ferramentas ideais para armazenar e acessar essas informações de forma estruturada.

Uma das aplicações mais comuns dos dicionários no geoprocessamento é a representação de atributos de entidades geográficas:

ponto_interesse = {
    'tipo': 'Hidrografia',
    'nome': 'Rio Amazonas',
    'extensão': 7062  # em km
}

Em sistemas de informações geográficas (SIGs), por exemplo, cada entidade, seja ela um ponto, linha ou polígono, pode ter vários atributos relacionados, como nome, tipo, área, entre outros. Os dicionários oferecem uma maneira intuitiva de associar cada um desses atributos à sua respectiva entidade.

Metadados associados a imagens de satélite, que descrevem características como data de captura, resolução e sensor utilizado, são frequentemente representados como dicionários:

metadados_imagem = {
    'satélite': 'Sentinel-2',
    'data_captura': '2023-01-15',
    'resolução': 10,  # em metros/pixel
    'bandas': ['B1', 'B2', 'B3', 'B4']
}

Isso facilita o processamento, a análise e a visualização dessas imagens, garantindo que todas as informações relevantes estejam prontamente disponíveis.

Em ferramentas e scripts de geoprocessamento, os dicionários são usados para armazenar configurações e parâmetros que orientam a análise. Isso inclui detalhes como a distância de um buffer em análises de proximidade, opções de interpolação em modelagem de terreno, entre outros.

config_analise = {
    'buffer_distância': 50,  # em metros
    'sobreposição_permitida': True,
    'unidade_medida': 'metros'
}

No contexto de formatos de dados padrão, como o GeoJSON, que é amplamente utilizado para representar estruturas geográficas, a estrutura chave-valor se alinha perfeitamente com a estrutura de dicionários em Python. Esse alinhamento permite que desenvolvedores manipulem e transformem facilmente dados geográficos entre aplicativos e plataformas.

Os dicionários são delimitados por chaves, e os pares de chave-valor são separados por vírgulas. A chave e o valor são separados por dois pontos. Por exemplo:

{'tipo': 'Hidrografia', 'nome': 'Rio Amazonas', 'extensão': 7062}
{'tipo': 'Hidrografia', 'nome': 'Rio Amazonas', 'extensão': 7062}

Neste dicionário, “tipo”, “nome” e “extensão” são as chaves. Já “Hidrografia”, “Rio Amazonas” e “7062” são os valores.

Com relação as chaves (Keys):

  • Cada elemento em um dicionário tem uma chave associada a ele;

  • As chaves são únicas dentro de um dicionário, ou seja, não podem existir duas entradas com a mesma chave;

  • Uma chave pode ser de qualquer tipo imutável, como números, strings ou tuplas.

Com relação aos valores (Values):

  • Cada chave é associada a um valor;

  • Os valores não precisam ser únicos;

  • Os valores podem ser de qualquer tipo: números, strings, listas, outros dicionários, objetos, etc.

Até a versão 3.7 do Python, os dicionários eram desordenados, o que significa que a ordem dos itens poderia não ser a mesma em que foram adicionados. A partir dessa versão, os dicionários mantêm a ordem de inserção, mas ainda é importante notar que os dicionários são essencialmente estruturas de mapeamento e não sequências.

Criação de dicionários

Dicionários são criados usando chaves ou com a função built-in dict(). Exemplos:

#Criação de dicionário usando chaves {}
ponto_geografico = {
    'nome': 'Pico da Neblina',
    'latitude': -0.8495,
    'longitude': -66.9083,
    'elevação': 2994  # em metros
}
#Criação de dicionário usando dict()
ponto_geografico = dict(
    nome='Pico da Neblina',
    latitude=-0.8495,
    longitude=-66.9083,
    elevação=2994  # em metros
)
Outros exemplos de criação de questionários:
# Dicionário para armazenar dados sobre municípios
municipio = {
    'nome': 'São Paulo',
    'estado': 'SP',
    'população': 12330000,  # aproximação
    'zona_climática': 'Tropical de altitude'
}
#Dicionário com dados do Landsat
sensor_satelite = {
    'modelo': 'Landsat 8',
    'bandas': ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8'],
    'resolução_espacial': 30,  # em metros
    'operador': 'USGS e NASA'
}

Acessando os elementos de um dicionário

Acessar informações em um dicionário é uma operação corriqueira em Python, e as principais formas de fazer isso são através das chaves, dos métodos associados e por iteração (que veremos mais adiante em nosso curso). Vamos explorar essas maneiras no contexto do geoprocessamento utilizando o dicionário “metadados_imagem” apresentado anteriormente.

metadados_imagem = {
    'satélite': 'Sentinel-2',
    'data_captura': '2023-01-15',
    'resolução': 10,  # em metros/pixel
    'bandas': ['B1', 'B2', 'B3', 'B4']
}
  • Acesso Direto pelas Chaves:

Você pode acessar um valor em um dicionário diretamente utilizando sua chave. Se a chave estiver no dicionário, o valor correspondente será retornado; caso contrário, um erro KeyError será gerado.

Exemplo: Se tivermos um dicionário representando metadados de uma imagem de satélite, podemos acessar a data de captura com metadados.

print(metadados_imagem['data_captura'])  
2023-01-15
  • Método get():

Semelhante ao acesso direto, o método get() permite recuperar o valor associado a uma chave. A diferença é que, se a chave não estiver presente, get() retornará None (ou um valor padrão especificado) em vez de gerar um erro.

Exemplo: Retornar um valor a partir de uma chave. Caso não exista a chave que se está buscando, imprimi o seguinte texto: ‘Não informado’.

print(metadados_imagem.get('data_captura', 'Não informado'))  
2023-01-15
print(metadados_imagem.get('altitude', 'Não informado'))  
Não informado
  • Acesso às Chaves, Valores e Itens:

keys(): Retorna uma lista das chaves no dicionário.

values(): Retorna uma lista dos valores no dicionário.

items(): Retorna uma lista de tuplas, onde cada tupla consiste em uma chave e um valor.

Exemplo:

# Obter todas as chaves
print(metadados_imagem.keys())  
dict_keys(['satélite', 'data_captura', 'resolução', 'bandas'])
# Obter todos os valores
print(metadados_imagem.values())  
dict_values(['Sentinel-2', '2023-01-15', 10, ['B1', 'B2', 'B3', 'B4']])
# Obter tanto as chaves quanto os valores
print(metadados_imagem.items())  
dict_items([('satélite', 'Sentinel-2'), ('data_captura', '2023-01-15'), ('resolução', 10), ('bandas', ['B1', 'B2', 'B3', 'B4'])])

Modificação de valores no dicionário

Os dicionários são estruturas de dados mutáveis em Python, o que significa que você pode modificar seus valores após a criação. Para modificar um dicionário, você simplesmente atribui um novo valor a uma chave existente ou adiciona uma nova chave-valor ao dicionário. Vamos explorar a modificação de valores em dicionários usando o dicionário metadados_imagem:

metadados_imagem = {
    'satélite': 'Sentinel-2',
    'data_captura': '2023-01-15',
    'resolução': 10,  # em metros/pixel
    'bandas': ['B1', 'B2', 'B3', 'B4']
}
  • Modificar um valor existente:

Se você deseja alterar o satélite registrado, pode fazer o seguinte:

metadados_imagem['satélite'] = 'Sentinel-3'
print(metadados_imagem['satélite'])  
Sentinel-3
  • Adicionar uma novo par chave-valor:

Vamos supor que você deseja adicionar informações sobre a área de cobertura da imagem:

metadados_imagem['area_cobertura'] = 'América do Sul'
print(metadados_imagem['area_cobertura'])  
América do Sul
  • Modificar uma lista dentro do dicionário:

Você pode alterar ou adicionar novas bandas à lista de bandas:

metadados_imagem['bandas'].append('B5')
print(metadados_imagem['bandas'])  
['B1', 'B2', 'B3', 'B4', 'B5']
  • Remover uma chave-valor:

Se por algum motivo você precisar remover a resolução, pode usar o método pop:

resolucao_removida = metadados_imagem.pop('resolução')
print(resolucao_removida)  # Saída: 10
print('resolução' in metadados_imagem)  
10
False

Remoção de elementos usando del, pop() ou popitem()

Em Python, a remoção de elementos de estruturas de dados é facilitada por métodos e comandos intuitivos. O comando del é versátil e pode ser usado para deletar itens de listas ou chaves de dicionários. Por outro lado, o método pop(), frequentemente utilizado com listas, também serve para dicionários e permite remover um item especificado pela sua posição ou chave, retornando o valor correspondente. No contexto de dicionários, o popitem() é particularmente útil, pois elimina o último par chave-valor inserido, adaptando-se ao comportamento de ordem de inserção preservada dos dicionários a partir do Python 3.7.

Vamos recriar o dicionário metadados_imagem:

metadados_imagem = {
    'satélite': 'Sentinel-2',
    'data_captura': '2023-01-15',
    'resolução': 10,  # em metros/pixel
    'bandas': ['B1', 'B2', 'B3', 'B4']
}
  • Usando del:

Este é um comando para deletar uma chave e seu valor correspondente. Se você tentar remover uma chave que não existe usando del, um erro será gerado.

del metadados_imagem['data_captura']
  • Usando pop():

Este método remove uma chave específica e retorna o valor correspondente. Se a chave não existir e nenhum valor padrão for fornecido, um erro será gerado.

resolucao = metadados_imagem.pop('resolução')
print(resolucao)
10
  • Usando popitem():

Este método remove o último par chave-valor inserido no dicionário e o retorna como uma tupla. Este comportamento é útil quando trabalhamos com dicionários a partir do Python 3.7, onde os dicionários mantêm a ordem de inserção.

item_removido = metadados_imagem.popitem()
print(item_removido)  
('bandas', ['B1', 'B2', 'B3', 'B4'])

Após executar todas essas operações, o dicionário “metadados_imagem” terá apenas o par chave-valor “satélite: Sentinel-2”. {‘satélite’: ‘Sentinel-2’}

Atualização de um dicionário com outro dicionário ou com pares de chave-valor

Atualizar um dicionário em Python pode ser feito de várias maneiras, mas uma das abordagens mais diretas é usar o método update(). Este método permite combinar dois dicionários ou adicionar pares chave-valor a um dicionário existente.

  • Atualizando com outro dicionário:

Suponha que você tenha dois dicionários relacionados referentes aos metadados de imagens do Sentinel-2:

metadados_imagem1 = {
    'satélite': 'Sentinel-2',
    'resolução': 10
}
metadados_imagem2 = {
    'data_captura': '2023-01-15',
    'bandas': ['B1', 'B2', 'B3', 'B4']
}

Para combinar esses dois dicionários, você pode usar o método update():

metadados_imagem1.update(metadados_imagem2)
metadados_imagem1
{'satélite': 'Sentinel-2',
 'resolução': 10,
 'data_captura': '2023-01-15',
 'bandas': ['B1', 'B2', 'B3', 'B4']}

Após essa operação, metadados_imagem1 terá todos os pares chave-valor dos dois dicionários originais.

  • Atualizando com pares de chave-valor

Para adicionar uma nova chave “tipo” com o valor “ótico”:

metadados_imagem1.update({'tipo': 'ótico'})
metadados_imagem1
{'satélite': 'Sentinel-2',
 'resolução': 10,
 'data_captura': '2023-01-15',
 'bandas': ['B1', 'B2', 'B3', 'B4'],
 'tipo': 'ótico'}

Também é possível adicionar múltiplas chaves e valores simultaneamente:

metadados_imagem1.update(tipo='ótico', operador='ESA')
metadados_imagem1
{'satélite': 'Sentinel-2',
 'resolução': 10,
 'data_captura': '2023-01-15',
 'bandas': ['B1', 'B2', 'B3', 'B4'],
 'tipo': 'ótico',
 'operador': 'ESA'}

Após qualquer uma dessas operações, o dicionário metadados_imagem1 será atualizado com as novas chaves e valores fornecidos.

Outras operações comuns em dicionários

  • Verificação de presença de uma chave com in:

Em Python, para verificar se uma chave está presente em um dicionário, você pode usar o operador in. A vantagem de usar esse operador é que ele fornece uma maneira rápida e legível de verificar a existência de uma chave. Além disso, evita a ocorrência de KeyError, que pode ocorrer se você tentar acessar diretamente um valor usando uma chave que não exista no dicionário.

dicionario = {'cidade': 'Natal', 'estado': 'RN', 'país': 'Brasil'}
existe = 'cidade' in dicionario
  • Uso da função len():

A função len() retorna o número de pares chave-valor presentes no dicionário.

tamanho = len(dicionario)
tamanho
3
  • Uso do método clear():

O método clear() Remove todos os pares chave-valor do dicionário, tornando-o um dicionário vazio.

dicionario.clear()
dicionario
{}

2.3.4 Conjuntos (Sets)#

Conjuntos (ou sets, em inglês) são uma das estruturas de dados fundamentais em Python. Eles são coleções não ordenadas de elementos únicos. Isso significa que um conjunto não pode conter elementos duplicados. Devido à sua natureza não ordenada, conjuntos não suportam indexação ou fatiamento, como listas ou strings. Aqui estão algumas características e operações fundamentais associadas aos conjuntos:

  • Não ordenado: Os itens têm uma ordem não definida e não podem ser referenciados por índice ou chave.

  • Elementos únicos: Os conjuntos não permitem elementos duplicados.

  • Mutável: Você pode adicionar ou remover itens após a criação do conjunto.

  • Permite diferentes tipos de dados: Um conjunto pode conter números, strings, tuplas e outros tipos imutáveis de dados.

Conjuntos são particularmente úteis quando você quer eliminar itens duplicados de uma lista ou quando precisa realizar operações matemáticas de conjunto, como união, interseção e diferença.

Criação de Conjuntos

  • Usando chaves:

# Criando um conjunto de números inteiros
conjunto_inteiros = {1, 2, 3, 4, 5}
print(conjunto_inteiros)  
{1, 2, 3, 4, 5}
# Criando um conjunto de strings
tipos_dados_geo = {"ponto", "linha", "polígono"}
print(tipos_dados_geo)
{'linha', 'ponto', 'polígono'}
# Observe que os elementos duplicados são automaticamente removidos
conjunto_duplicados = {1, 2, 2, 3, 4, 4, 5}
print(conjunto_duplicados)
{1, 2, 3, 4, 5}
  • Usando a função set():

# Convertendo uma lista em conjunto

lista = [1, 2, 3, 4, 5]
conjunto_a_partir_da_lista = set(lista)
print(conjunto_a_partir_da_lista) 
{1, 2, 3, 4, 5}
# Convertendo uma tupla em conjunto
operacoes = ("intersecção", "união", "diferença", "intersecção")
conjunto_operacoes = set(operacoes)
print(conjunto_operacoes)
{'união', 'intersecção', 'diferença'}
# Criando um conjunto vazio usando set() 
# Não é possível criar um conjunto vazio com {}
conjunto_vazio = set()
print(conjunto_vazio) 
set()

Operações Básicas com Conjuntos

Os conjuntos em Python vêm com um conjunto de métodos integrados que facilitam a manipulação e a gestão dos seus elementos. Aqui, apresentaremos algumas das operações mais comuns que você pode realizar em conjuntos:

  • add: Esse método permite adicionar um único elemento ao conjunto. Se o elemento já estiver presente, o conjunto permanecerá inalterado, pois não pode haver duplicatas.

  • update: Usado para adicionar múltiplos elementos ao conjunto. É semelhante ao método add, mas aceita uma lista de elementos.

  • remove: Remove um elemento especificado do conjunto. Se o elemento não estiver presente, gerará um erro.

  • discard: Semelhante ao método remove, mas não gera um erro se o elemento a ser descartado não estiver presente no conjunto.

  • clear: Este método esvazia o conjunto, removendo todos os seus elementos.

Exemplos:

Criar dois conjuntos:

A = {1, 2, 3}
B = {3, 4, 5}

Adicionar um único elemento no conjunto:

A.add(6)
print(A)
{1, 2, 3, 6}

Adicionar múltiplos elementos:

A.update([7, 8])
print(A)
{1, 2, 3, 6, 7, 8}

Remover um elemento:

A.remove(1)
print(A) 
{2, 3, 6, 7, 8}

Remover um elemento sem levantar erro caso ele não exista:

A.discard(10)  
# Não acontece nada, já que 10 não está no conjunto

Remover todos os elementos de um conjunto, deixando-o vazio:

A.clear()
print(f"Conjunto após limpar todos os elementos: {A}")
Conjunto após limpar todos os elementos: set()

Operações de Conjunto

Os conjuntos, em qualquer contexto matemático ou de programação, são coleções não ordenadas de itens únicos. No Python, eles não são apenas úteis para garantir unicidade, mas também para realizar operações matemáticas clássicas de conjuntos. Estas operações permitem analisar relações entre diferentes grupos de dados e encontrar semelhanças, diferenças, interseções, entre outros. Algumas das operações de conjunto mais comuns são:

  • União: Esta operação combina os elementos de dois conjuntos, criando um novo conjunto que contém todos os elementos únicos dos conjuntos originais. No Python, a união pode ser realizada usando o método union() ou o operador “|”.

# Criando dois conjuntos
A = {1, 2, 3}
B = {3, 4, 5}
uniao = A.union(B)
print(uniao) 
{1, 2, 3, 4, 5}
# Ou usando o operador |
uniao = A | B
print(uniao)
{1, 2, 3, 4, 5}
  • Interseção: Encontra os elementos que são comuns a ambos os conjuntos. Pode ser realizada usando o método intersection() ou o operador “&”.

interseccao = A.intersection(B)
print(interseccao)
{3}
# Ou usando o operador &
interseccao = A & B
print(interseccao) 
{3}
  • Diferença: Retorna os elementos que estão no primeiro conjunto, mas não no segundo. É feito usando o método difference() ou o operador “-“.

diferenca = A.difference(B)
print(diferenca)
{1, 2}
# Ou usando o operador -
diferenca = A - B
print(diferenca)
{1, 2}
  • Diferença Simétrica: Identifica os elementos que estão apenas em um dos conjuntos, mas não em ambos. Pode ser obtida com o método symmetric_difference() ou o operador “^”.

dif_simetrica = A.symmetric_difference(B)
print(dif_simetrica)
{1, 2, 4, 5}
# Ou usando o operador ^
dif_simetrica = A ^ B
print(dif_simetrica)
{1, 2, 4, 5}

Além dessas operações básicas, existem métodos para verificar subconjuntos, superconjuntos e se conjuntos são disjuntos (não têm elementos em comum).

  • Subconjunto: verifica se um conjunto é um subconjunto de outro conjunto.

C = {1, 2}
print(C.issubset(A))
True
  • Superconjunto: verifica se um conjunto é um superconjunto de outro conjunto.

print(A.issuperset(C))
True
  • Conjuntos Disjuntos: verifica se dois conjuntos não têm elementos em comum.

D = {7, 8, 9}
print(A.isdisjoint(D))
True

2.4 Estruturas de controle#

Em qualquer linguagem de programação, devemos ter a possibilidade de tomar decisões e de repetir tarefas. As estruturas de controle em Python fornecem os mecanismos necessários para dirigir o fluxo de um programa, permitindo que os desenvolvedores codifiquem lógicas mais complexas e adaptativas.

Primeiramente, temos as estruturas condicionais, representadas principalmente pelas palavras-chave if, elif e else. Elas permitem que o programa tome decisões, executando blocos específicos de código com base em condições predefinidas. Por exemplo, pode-se verificar se um número é positivo, negativo ou zero e, com base nessa condição, tomar ações diferentes.

Já as estruturas de repetição, ou loops, são responsáveis por executar um bloco de código várias vezes, enquanto uma condição específica for atendida ou até que uma lista de itens seja processada. Em Python, os loops mais comuns são representados por for e while. O loop for é frequentemente usado para iterar sobre sequências (como listas ou strings), enquanto o while continua executando enquanto uma determinada condição for verdadeira.

Além dessas estruturas básicas, Python também oferece recursos avançados, como compreensões de lista (list comprehensions) para criar listas de forma concisa e a palavra-chave break para sair prematuramente de um loop.

O geoprocessamento refere-se ao conjunto de técnicas utilizadas para coletar, tratar, manipular e apresentar informações espaciais. Em razão de sua complexidade e variedade, o uso das estruturas de controle em linguagens de programação, como Python, é fundamental para automatizar e otimizar processos relacionados à análise geoespacial.

As estruturas condicionais são amplamente aplicadas em cenários de tomada de decisão. Por exemplo, em análises de uso do solo, um programa pode avaliar pixels de uma imagem de satélite e, com base em sua refletância, classificá-los como água, vegetação, solo exposto, entre outros. Assim, a lógica condicional permite que cada pixel seja categorizado corretamente.

Já as estruturas de repetição são importantes em operações que requerem análise iterativa. Em modelagem hidrológica, por exemplo, é comum que se necessite percorrer uma matriz digital de elevação, célula por célula, para calcular direções de fluxo ou acumulação de água. Os loops, nesse contexto, permitem iterar sobre grandes conjuntos de dados espaciais, aplicando algoritmos específicos a cada ponto ou região.

Em operações mais avançadas, como a execução de algoritmos de busca em redes ou grafos (como a determinação do caminho mais curto entre dois pontos em uma rede rodoviária), as estruturas de controle gerenciam a lógica, os critérios de parada e as iterações necessárias para o cálculo.

2.4.1 Estruturas condicionais: estrutura e sintaxe do if, elif e else#

A base de uma estrutura condicional é um teste lógico, que pode resultar em um valor verdadeiro ou falso. Dependendo desse resultado, o programa pode seguir diferentes caminhos ou executar blocos distintos de instruções. Essa capacidade de avaliar condições e agir de acordo oferece uma grande flexibilidade ao programador, possibilitando a criação de software mais dinâmico e responsivo.

No mundo real, tomamos decisões condicionais o tempo todo: “Se estiver chovendo, vou levar um guarda-chuva”. Na programação, essa lógica é similar. A estrutura condicional avalia se uma determinada condição é atendida (por exemplo, se uma variável excede um certo valor) e, com base nessa avaliação, toma uma decisão sobre quais ações devem ser executadas. Essa capacidade de decidir sobre o fluxo de execução é fundamental para criar programas que possam lidar com uma ampla variedade de cenários e entradas.

Em Python, as estruturas condicionais são representadas principalmente pelos comandos “if”, “elif” e “else”, que possibilitam avaliar múltiplas condições e direcionar o fluxo do programa de acordo com os resultados dessas avaliações. Os operadores de comparação e os operadores lógicos são comumente utilizados no contexto das estruturas condicionais.

Os operadores de atribuição (quadro 4) são frequentemente usados em estruturas condicionais, especialmente quando há a necessidade de ajustar ou modificar o valor de uma variável com base em determinadas condições.

Atribuição simples: Atribui o valor à direita do operador à variável à esquerda.

x = 5
print(x) 
5

Adição e atribuição: Adiciona o valor à direita do operador à variável à esquerda e, em seguida, atribui o resultado à variável à esquerda.

x += 3
print(x) 
8

Subtração e atribuição: Adiciona o valor à direita do operador à variável à esquerda e, em seguida, atribui o resultado à variável à esquerda.

x -= 3
print(x) 
5

Multiplicação e atribuição: Multiplica a variável à esquerda pelo valor à direita do operador e, em seguida, atribui o resultado à variável à esquerda.

x = 4
x*=3
print(x)
12

Divisão e atribuição: Divide a variável à esquerda pelo valor à direita do operador e, em seguida, atribui o resultado (em ponto flutuante) à variável à esquerda.

x/=3
print(x) 
4.0

Divisão inteira e atribuição: Realiza a divisão inteira da variável à esquerda pelo valor à direita do operador e, em seguida, atribui o resultado à variável à esquerda.

x = 14
x//=3
print(x) 
4

Módulo e atribuição: Calcula o resto da divisão da variável à esquerda pelo valor à direita do operador e, em seguida, atribui o resultado à variável à esquerda.

x = 14
x%=5
print(x) 
4

Exponenciação e atribuição: Eleva a variável à esquerda ao valor à direita do operador e, em seguida, atribui o resultado à variável à esquerda.

x = 3
x**=2
print(x)
9

O comando if

O comando if em Python é uma estrutura condicional que permite testar uma condição e, com base no resultado dessa avaliação (verdadeiro ou falso), executar um bloco de código específico. Essencialmente, ele permite que o programa “decida” qual caminho seguir baseado em determinados critérios.

No campo do geoprocessamento, o comando if pode ser utilizado em diversas situações para controlar fluxos de trabalho, tomar decisões com base em atributos espaciais, filtrar dados geográficos, entre outras tarefas. Aqui estão alguns exemplos:

  • Filtragem de Dados com base em atributos.

Exemplos:

Suponha que você tenha um conjunto de dados com dados de declividade e queira categorizar o relevo, quando ele for plano.

declividade = 2
if declividade < 3:
    print("Plano.")
Plano.

Imagine que estamos mapeando zonas climáticas de uma região. Se a temperatura média anual de uma área estiver entre 20°C e 30°C, podemos categorizá-la como “zona climática tropical”.

temperatura_media = 25  # temperatura média anual em graus Celsius
if 20 <= temperatura_media <= 30:
    print('A área é categorizada como zona climática tropical.')
A área é categorizada como zona climática tropical.

O comando else

O comando else em Python é usado em conjunção com a instrução if. Enquanto if avalia se uma condição é verdadeira para executar um bloco de código, o else captura os casos em que a condição do if não é atendida, ou seja, é falso. Em outras palavras, o bloco de código sob else será executado quando a condição do if anterior for não-verdadeira.

Imagine que estamos trabalhando com um sistema de informação geográfica e queremos determinar se uma determinada área é adequada para agricultura com base na sua umidade. Vamos supor que uma umidade acima de 50% é considerada adequada para agricultura, enquanto valores abaixo disso são considerados inadequados.

umidade = 45  # umidade em percentagem
if umidade > 50:
    print('A área é adequada para agricultura.')
else:
    print('A área não é adequada para agricultura.')
A área não é adequada para agricultura.

Neste exemplo, como a umidade (45%) é menor que o limite de 50%, o código sob o comando else é executado, levando à saída que indica que a área não é adequada para agricultura.

No próximo exemplo, queremos encontrar um local ideal para construir uma instalação de observação da vida selvagem. Este local deve estar a uma altitude entre 1000 e 2000 metros e a pelo menos 5 km de distância de qualquer área urbana para garantir um ambiente tranquilo.

altitude = 1500  # altitude em metros
distancia_cidade = 7  # distância da cidade mais próxima em km
if (1000 <= altitude <= 2000) and (distancia_cidade > 5):
    print('O local é ideal para uma instalação de observação da vida selvagem.')
else:
    print('O local não é adequado.')
O local é ideal para uma instalação de observação da vida selvagem.

Neste exemplo, estamos usando o operador lógico and para combinar duas condições: altitude e distância da cidade. Ambas as condições devem ser verdadeiras para que o bloco de código sob o if seja executado. Se qualquer uma (ou ambas) das condições for falsa, o código sob o else será executado.

O comando elif

O comando elif é uma abreviação de “else if” e serve como uma ponte entre um if e um else. Permite que você teste múltiplas condições em uma sequência, sem ter que aninhar várias instruções if dentro umas das outras. É especialmente útil quando você tem várias condições distintas que precisam ser avaliadas em ordem.

Se a condição na instrução if for falsa, o programa verificará a condição na próxima instrução elif. Se essa condição também for falsa, o programa passará para a próxima instrução elif e assim por diante. Se nenhuma das condições if ou elif for verdadeira, o bloco de código sob a instrução else (se presente) será executado.

Exemplo: Imagine que estamos avaliando a qualidade do solo para diferentes tipos de cultivo. Se a umidade do solo estiver abaixo de 40%, pode ser considerada baixa. Se estiver entre 40% e 60%, é média. Se estiver acima de 60%, é alta.

umidade = 50  # umidade do solo em percentagem
if umidade < 40:
    print('A umidade do solo é baixa.')
elif umidade <= 60:
    print('A umidade do solo é média.')
else:
    print('A umidade do solo é alta.')
A umidade do solo é média.

Neste exemplo, o programa primeiro verifica se a umidade é menor que 40%. Como não é, ele passa para a condição do elif e verifica se a umidade é menor ou igual a 60%. Como é, ele executa o bloco de código associado a esse elif. Se a umidade fosse maior que 60%, ele teria passado para o bloco else.

Vamos voltar ao exemplo dos dados de declividade apresentado anteriormente. Utilizando o elif e o else, podemos utilizar diversas classes de declividade para classificar o relevo de uma determinada área:

# Definindo um valor de declividade
declividade = 10  # Exemplo de valor de declividade
# Usando o comando if para classificar a declividade
if declividade >= 0 and declividade < 3:
    print("Plano")
elif declividade >= 3 and declividade < 8:
    print("Suave-ondulado")
elif declividade >= 8 and declividade < 20:
    print("Ondulado")
elif declividade >= 20 and declividade < 45:
    print("Forte-ondulado")
elif declividade >= 45 and declividade <= 75:
    print("Montanhoso")
else:
    print("Forte-montanhoso")
Ondulado

Aninhamento de declarações condicionais

O aninhamento de declarações condicionais ocorre quando você tem uma instrução condicional (if, elif ou else) dentro de outra instrução condicional. Isso é útil quando você quer fazer uma decisão com base em um critério e, depois, tomar outra decisão dentro dessa primeira condição.

Imagine uma árvore de decisões, onde cada ramo leva a mais ramos. Em cada etapa da avaliação, você toma uma decisão e essa decisão pode levar a mais avaliações e decisões.

Ao aninhar declarações condicionais, é essencial manter o código organizado e legível para evitar confusões. Indentações adequadas são cruciais nesse processo, pois determinam o escopo de cada declaração.

Suponhamos que, em um contexto de geoprocessamento, você queira avaliar se uma área é adequada para construção. Primeiro, você verificará se a área é uma zona protegida. Se não for, você verificará se o terreno é estável.

zona_protegida = False
terreno_estavel = True

if zona_protegida:
    print('A área é uma zona protegida e não pode ser construída.')
else:
    if terreno_estavel:
        print('Área não protegida e terreno estável. Construção permitida.')
    else:
        print('Área não protegida e terreno instável. Construção não recomendada.')
Área não protegida e terreno estável. Construção permitida.

Neste exemplo, a primeira condição verifica se a área é uma zona protegida. Se não for, o código entra no bloco else e uma nova verificação condicional é feita para verificar a estabilidade do terreno.

É possível aninhar várias condições desta forma, mas é importante ser cauteloso para que o código não se torne excessivamente complicado e difícil de seguir. Em muitos casos, combinar condições com operadores lógicos (como and, or) pode ser uma alternativa mais elegante.

Cálculos com base em condições

Vamos considerar um exemplo em que você deseja calcular a área de um terreno. No entanto, dependendo do tipo de terreno, você pode precisar fazer cálculos diferentes. Existem três geometrias possíveis para esse terreno: retangular, triangular e circular. O cálculo da área depende do tipo de geometria do terreno.

Se o terreno for retangular, a área é definida por \(Area = comprimento x largura\). Se o terreno for triangular, a área é calculada por \(Area = (base x altura)/2\), e se o terreno for circular, \(Area = \pi r^2\).

Dadas as dimensões do terreno e a sua geometria, podemos usar estruturas condicionais para calcular a área:

tipo_terreno = 'triangular'
base = 10
altura = 5
comprimento = 20
largura = 15
raio = 7
pi = 3.14159
if tipo_terreno == 'retangular':
    area = comprimento * largura
    print(f'A área do terreno retangular é {area} m².')

elif tipo_terreno == 'triangular':
    area = (base * altura) / 2
    print(f'A área do terreno triangular é {area} m².')

elif tipo_terreno == 'circular':
    area = pi * (raio**2)
    print(f'A área do terreno circular é {area} m².')

else:
    print('Tipo de terreno desconhecido.')
A área do terreno triangular é 25.0 m².

Neste exemplo, as condições avaliam o tipo de geometria do terreno e executam o cálculo apropriado com base nesse tipo. A saída resultante fornece a área calculada de acordo com o tipo de terreno especificado.

2.4.2 Estruturas de repetição (Loops)#

O fluxo padrão de um programa é sequencial. Cada instrução é executada, uma após a outra, de cima para baixo. No entanto, em muitos casos, é necessário executar um conjunto de instruções repetidamente, seja um número específico de vezes ou até que uma certa condição seja satisfeita. Isso é realizado por meio de estruturas de repetição, também conhecidas como “loops”. Python oferece duas principais estruturas de loop: for e while.

No contexto do geoprocessamento, os loops oferecem uma solução eficaz para gerenciar tarefas repetitivas, especialmente quando lidamos com extensos conjuntos de dados espaciais. Por exemplo, ao trabalhar com séries temporais de imagens de satélite, um loop pode automatizar e padronizar o processamento de cada imagem, economizando tempo e reduzindo possíveis erros manuais.

Além disso, os loops são instrumentais ao se tratar de datasets geoespaciais. Eles proporcionam a capacidade de iterar sobre múltiplas geometrias, o que é essencial para realizar cálculos, análises e verificar conformidades em grandes volumes de dados. Em situações em que se tem um conjunto de pontos, linhas ou polígonos, um loop pode agilizar análises que seriam extenuantes se feitas manualmente. No contexto de modelagem e simulação, os loops também são de suma importância. Eles permitem a execução iterativa de modelos em diferentes cenários ou condições, facilitando análises preditivas e a otimização de soluções para problemas espaciais complexos.

Loop for

O loop for é uma das estruturas de repetição mais versáteis e frequentemente usadas em Python. Ele permite iterar sobre os itens de qualquer sequência (uma lista, uma string, uma tupla, um dicionário, um conjunto ou até mesmo um intervalo), e executar um bloco de código para cada item. A sintaxe básica do loop for é:

for variavel in sequencia:
    # bloco de código para cada item da sequência

Onde “variavel” é uma variável temporária que assume o valor de cada item da sequência durante cada iteração do loop.

Exemplos:

Suponhamos que você tenha uma lista de cidades e queira imprimir cada cidade na lista.

cidades = ['Natal', 'Recife', 'Fortaleza', 'João Pessoa']
for cidade in cidades:
    print(cidade)
Natal
Recife
Fortaleza
João Pessoa

Inicialmente, criamos uma lista chamada cidades contendo quatro strings, que representam nomes de cidades.

Utilizamos a estrutura de repetição for, que percorrerá cada elemento da lista cidades, um de cada vez. A cada iteração do loop, a variável cidade assume o valor do próximo elemento da lista. Por exemplo, na primeira iteração, cidade terá o valor ‘Natal’; na segunda iteração, terá o valor ‘Recife’; e assim por diante.

Dentro do loop, para cada cidade da lista, a função print() é chamada para exibir o nome da cidade atualmente referenciada pela variável cidade. A saída será:

Calcular a soma das áreas de diferentes polígonos que estão registradas em uma lista:

areas_poligonos = [100, 200, 150, 300, 250]
total_area = 0
for area in areas_poligonos:
    total_area += area
print('Área total:', total_area)
Área total: 1000

Inicialmente criamos uma lista chamada areas_poligonos que contém cinco números inteiros. Estes números representam, por exemplo, as áreas (em uma unidade hipotética, como metros quadrados) de cinco polígonos diferentes.

Em seguida, uma variável chamada total_area é inicializada com o valor 0. Esta variável será usada para acumular a soma das áreas de todos os polígonos da lista.

Uma estrutura de repetição for percorre cada elemento da lista areas_poligonos, um de cada vez. A cada iteração do loop, a variável area assume o valor do próximo elemento da lista.

Durante cada iteração do loop, a área atual (valor da variável area) é somada ao valor acumulado em total_area. O operador “+=” é um atalho para total_area = total_area + area.

Após o término do loop, a função print() é chamada para exibir a área total acumulada na variável total_area.

O loop for também pode ser usado com a função range(), que é uma função incorporada em Python que gera uma sequência de números e frequentemente é usada em loops for para repetir uma determinada ação um número específico de vezes. Essa função é muito útil quando se deseja iterar sobre uma sequência de números, sem a necessidade de criar uma lista ou outro container para armazenar esses números.

A função range() pode aceitar entre um a três argumentos:

  • Um único argumento: range(stop).

Produz uma sequência de números de “0” a stop “- 1”.

for i in range(5):
    print(i)
0
1
2
3
4
  • Dois argumentos: range(start, stop). Produz uma sequência de números de start a stop “- 1”.

for i in range(2, 5):
    print(i)
2
3
4
  • Três argumentos: range(start, stop, step).

Produz uma sequência de números, começando em start, terminando antes de stop e incrementando em step.

for i in range(0, 10, 2):
    print(i)
0
2
4
6
8

Outra função embutida em Python que permite iterar sobre os itens de uma sequência e, simultaneamente, ter acesso ao índice (ou posição) de cada item dentro dessa sequência é o enumerate(). É particularmente útil quando você quer não apenas o valor do item, mas também saber a sua posição.

Vamos supor que você tenha uma lista de rios e queira imprimir cada rio junto com sua posição na lista:

rios = ['Amazonas', 'São Francisco', 'Paraná', 'Tocantins']
for indice, rio in enumerate(rios):
    print(f'Rio {indice + 1}: {rio}')
Rio 1: Amazonas
Rio 2: São Francisco
Rio 3: Paraná
Rio 4: Tocantins

No exemplo acima, para cada iteração do loop for, o enumerate() retorna uma tupla onde o primeiro valor é o índice e o segundo valor é o item da sequência. No contexto do geoprocessamento, isso pode ser útil, por exemplo, ao associar cada rio a um índice específico em uma base de dados ou ao lidar com matrizes de dados onde a posição do item é relevante.

Esses são apenas alguns exemplos básicos. O real potencial do loop for é visto quando usado em combinação com outras estruturas de controle e operações mais complexas.

List comprehension

A list comprehension é uma forma concisa de usar um loop for para criar listas em Python. Em essência, ela é uma maneira de representar um loop for de uma forma mais compacta, especificamente quando o objetivo principal desse loop é gerar uma nova lista.

A ideia da list comprehension é aplicar uma expressão a cada item em uma sequência (ou em múltiplas sequências) e retornar os resultados em uma nova lista.

Sintaxe básica: nova_expressão for item in iteravel]

Vamos comparar o loop for tradicional a list comprehension:

Usando um loop for tradicional:

numeros = [1, 2, 3, 4, 5]
quadrados = []

for numero in numeros:
    quadrado = numero ** 2
    quadrados.append(quadrado)
print(quadrados)
[1, 4, 9, 16, 25]

Usando list comprehension:

numeros = [1, 2, 3, 4, 5]
quadrados = [n ** 2 for n in numeros]
print(quadrados)
[1, 4, 9, 16, 25]

Ambos os códigos produzem o mesmo resultado: uma lista dos quadrados dos números: [1, 4, 9, 16, 25. No entanto, a versão com list comprehension é mais concisa e muitas vezes é considerada mais Pythonic quando usada apropriadamente.

É válido ressaltar que list comprehensions também podem incorporar condições. Por exemplo, se quiséssemos apenas os quadrados dos números ímpares:

quadrados_impares = [n ** 2 for n in numeros if n % 2 != 0]
print(quadrados_impares)
[1, 9, 25]

Em resumo, a aplicação de list comprehension torna o código mais conciso e legível (quando não são excessivamente complexas). Além disso, frequentemente são mais rápidas do que o loop for tradicional ao criar listas, devido a otimizações internas. No entanto, é importante não sacrificar a legibilidade. Se uma list comprehension se tornar muito complicada ou difícil de entender, pode ser melhor optar por uma abordagem mais tradicional usando loops.

Dictionary comprehension

Assim como a list comprehension, o dictionary comprehension também se baseia em um loop for interno, mas, ao invés de gerar listas, ela é utilizada para criar dicionários de forma concisa. Enquanto a list comprehension retorna uma lista, o dictionary comprehension retorna um dicionário.

Sintaxe Básica:

{chave: valor for item in iterável}

Suponha que você queira criar um dicionário que associe um número à sua potência quadrada para números de 1 a 5. Vamos comparar o loop for tradicional ao dictionary comprehension:

  • Usando um loop for tradicional

quadrados = {}  
for x in range(1, 6):
    quadrados[x] = x ** 2
print(quadrados)
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
  • Usando dictionary comprehension

quadrados = {x: x ** 2 for x in range(1, 6)}
print(quadrados)
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

Assim como na list comprehension, você também pode incluir condições no dictionary comprehension. Por exemplo, se você quisesse os quadrados apenas dos números ímpares de 1 a 5:

quadrados_impares = {x: x ** 2 for x in range(1, 6) if x % 2 != 0}
print(quadrados_impares)
{1: 1, 3: 9, 5: 25}

Cabe ressaltar que é fundamental garantir que o código permaneça legível. Se uma dictionary comprehension se tornar muito complexa, pode ser preferível usar uma abordagem mais tradicional.

Loop While

O loop while é uma estrutura de repetição que permite executar um bloco de código enquanto uma condição específica for verdadeira. A lógica é: enquanto a condição estabelecida se mantiver True, o bloco de código dentro do while continuará sendo executado. É crucial garantir que, em algum momento, essa condição se torne False, caso contrário, o loop continuará infinitamente, resultando em um loop infinito.

A estrutura básica do loop while é relativamente simples. Ela começa com a palavra-chave while seguida de uma condição (expressão booleana) e um bloco de código que será repetido enquanto a condição for verdadeira (True). A estrutura básica é:

while condição:
    # bloco de código a ser executado enquanto a condição for verdadeira

É importante garantir que a condição do while eventualmente se torne False, caso contrário, o código entrará em um loop infinito. Suponha que queremos imprimir os números de 1 a 5:

contador = 1
while contador <= 5:
    print(contador)
    contador += 1
1
2
3
4
5

Neste exemplo, inicializamos o contador com o valor 1. O loop while verifica se o contador é menor ou igual a 5. Se for verdadeiro, ele entra no bloco de código, imprime o valor do contador e depois aumenta o contador em 1. Esse processo é repetido até que o contador exceda 5, momento em que a condição do while se torna False, e o loop é interrompido.

Imagine agora que você esteja trabalhando com um sensor que coleta dados de temperatura do solo a cada minuto e que deseja parar a coleta quando a temperatura exceder um certo limite.

temperatura_solo = 25  # valor inicial
limite_temperatura = 35
while temperatura_solo < limite_temperatura:
# Suponha que a cada iteração, a temperatura do solo aumente em 1°C.
    temperatura_solo += 1
    print(f'Coletando dados... Temperatura atual: {temperatura_solo}°C')

print('Limite de temperatura atingido! Parando a coleta de dados.')
Coletando dados... Temperatura atual: 26°C
Coletando dados... Temperatura atual: 27°C
Coletando dados... Temperatura atual: 28°C
Coletando dados... Temperatura atual: 29°C
Coletando dados... Temperatura atual: 30°C
Coletando dados... Temperatura atual: 31°C
Coletando dados... Temperatura atual: 32°C
Coletando dados... Temperatura atual: 33°C
Coletando dados... Temperatura atual: 34°C
Coletando dados... Temperatura atual: 35°C
Limite de temperatura atingido! Parando a coleta de dados.

Neste exemplo, o loop while continuará coletando dados até que temperatura_solo alcance ou exceda 35°C. Após isso, ele sairá do loop e imprimirá a mensagem final.

Controle de loop com breake e continue

Os comandos break e continue são ferramentas poderosas em Python, oferecendo controle adicional sobre como os loops são executados.

  • Break:

O comando break é usado para sair completamente de um loop, encerrando sua execução. É geralmente utilizado em situações em que uma condição externa à condição principal do loop é satisfeita ou quando se deseja sair do loop antes que ele complete sua execução natural.

Imaginemos um cenário de geoprocessamento onde temos uma lista de coordenadas, e queremos parar de processar quando encontrarmos uma coordenada específica.

coordenadas = [(4, 5), (7, 8), (10, 12), (15, 15), (20, 25)]

for coord in coordenadas:
    if coord == (15, 15):
        print('Coordenada alvo encontrada!')
        break
    print('Processando a coordenada:', coord)
Processando a coordenada: (4, 5)
Processando a coordenada: (7, 8)
Processando a coordenada: (10, 12)
Coordenada alvo encontrada!
  • Continue:

O comando continue é utilizado para pular a iteração atual e continuar com a próxima iteração do loop. É útil em situações em que, sob certas condições, queremos evitar a execução de uma parte do loop e continuar com a próxima iteração.

Considere um cenário em que estamos processando dados de altitude e queremos ignorar os valores negativos, pois podem ser erros nos dados.

altitudes = [345, 432, -1, 540, -2, 600]
for registro in altitudes:
    if registro < 0:
        print('Altitude inválida encontrada. Continuando...')
        continue
    print('Processando altitude:', registro)
Processando altitude: 345
Processando altitude: 432
Altitude inválida encontrada. Continuando...
Processando altitude: 540
Altitude inválida encontrada. Continuando...
Processando altitude: 600

Neste exemplo, o loop for irá ignorar os valores negativos e não executará o comando print(“Processando altitude:”, registro) para esses valores, mas continuará processando os próximos valores na lista.

Ambos, break e continue, são úteis para gerenciar a execução dos loops com mais precisão e são amplamente utilizados em diversos cenários de programação.

Aninhamento de loops

O aninhamento de loops ocorre quando um loop é colocado dentro de outro loop. Isso é útil em várias situações, especialmente quando se trabalha com estruturas de dados multidimensionais, como listas de listas ou matrizes. No aninhamento, cada vez que o loop externo é executado, o loop interno será executado em sua totalidade.

Considere o cenário em geoprocessamento onde você quer avaliar todos os pontos de uma matriz de elevações para identificar aqueles que estão acima de um certo nível. Imagine uma matriz simplificada de altitudes. Para processar cada ponto desta matriz, você usaria um loop aninhado:

altitudes = [[5, 10, 15], [20, 25, 30], [35, 40, 45]]

for linha in altitudes:
    for ponto in linha:
        if ponto > 25:
            print(f'Ponto {ponto} está acima do nível desejado.')
Ponto 30 está acima do nível desejado.
Ponto 35 está acima do nível desejado.
Ponto 40 está acima do nível desejado.
Ponto 45 está acima do nível desejado.

Neste exemplo, o loop externo percorre cada linha da matriz, e o loop interno percorre cada ponto dentro de uma linha específica. A combinação dos dois loops permite que você processe cada ponto individual da matriz.

O aninhamento de loops é uma ferramenta poderosa, mas é importante estar ciente de que pode aumentar significativamente o tempo de processamento, especialmente se as estruturas de dados forem grandes. A complexidade computacional de loops aninhados é frequentemente um produto das iterações de cada loop, tornando-se potencialmente ineficiente para grandes conjuntos de dados se não for manejado corretamente.

2.5 Funções#

Em programação, uma função é um bloco de código organizado e reutilizável que realiza uma única ação relacionada. As funções fornecem uma maneira de modularizar seu código, tornando-o mais eficiente, legível e reutilizável. Além disso, elas permitem definir interfaces de código que podem ser usadas de forma consistente.

No contexto do geoprocessamento, que envolve a coleta, armazenamento e análise de dados geoespaciais, a modularização do código através de funções não só permite eficiência e clareza, mas também facilita a reutilização do código em diferentes projetos. Desta maneira, assegura consistência em tarefas frequentemente repetidas, como interpolação de dados e transformação de sistemas de coordenadas. Por outro lado, o uso de funções ajuda a abstrair a complexidade dos algoritmos, tornando o código mais legível. Isso é essencial, especialmente quando se trabalha em colaboração, pois funções bem definidas podem ser compartilhadas, promovendo consistência e economia de tempo. Ao adotar funções em geoprocessamento, os profissionais garantem não apenas a organização, mas também a robustez e eficiência do seu trabalho.

Definição da Função

Em Python, uma função é definida usando a palavra-chave “def”, seguida pelo nome da função, parênteses e dois pontos. O código da função começa após os dois pontos e é indentado.

def nome_da_funcao():
    # corpo da função

Parâmetros e Argumentos

As funções podem receber dados, conhecidos como parâmetros, entre seus parênteses.

def saudacao(nome):
    print(f'Olá, {nome}!')

Ao chamar a função, você passa dados para ela, conhecidos como argumentos:

saudacao('Julia')
Olá, Julia!

Valor de Retorno

Uma função pode processar dados e retornar um valor usando a palavra-chave return:

def soma(a, b):
    return a + b

Se uma função não possui um return, ela retorna None por padrão.

Funções Anônimas (Lambda)

Python suporta a criação de funções anônimas (isto é, funções que não têm nome) usando a palavra-chave lambda. Elas são úteis quando você precisa de uma função pequena por um curto período e não quer formalmente defini-la usando def.

quadrado = lambda x: x ** 2
print(quadrado(5))
25

Funções Como Objetos

Em Python, tudo é um objeto, incluindo funções. No geoprocessamento, as funções são frequentemente usadas para realizar operações específicas em dados geoespaciais, como calcular distâncias, transformar projeções ou processar dados matriciais. Usando o conceito de funções como objetos, podemos criar uma abordagem modular para realizar diferentes operações de geoprocessamento. Vamos ilustrar isso com um exemplo.

Imagine que temos duas funções, uma que calcula a área de um polígono e outra que calcula o perímetro.

def calcular_area(poligono):
    # Suponha que esta função compute a área de um polígono
    # Neste exemplo, vamos simplificar e retornar um valor fixo
    return 100.0
def calcular_perimetro(poligono):
    # Suponha que esta função compute o perímetro de um polígono
    # Novamente, vamos simplificar e retornar um valor fixo
    return 40.0

Agora, vamos criar uma função que recebe outra função como argumento e aplica essa função a um conjunto de polígonos:

def processar_geometrias(geometrias, operacao):
    resultados = []
    for geometria in geometrias:
        resultados.append(operacao(geometria))
    return resultados

Podemos usar esta função processar_geometrias para calcular a área ou o perímetro de uma lista de polígonos:

poligonos = ['poligono1', 'poligono2', 'poligono3']  
# Esta é uma simplificação. Em uma aplicação real, teríamos objetos polígono.
areas = processar_geometrias(poligonos, calcular_area)
print(areas)
[100.0, 100.0, 100.0]
perimetros = processar_geometrias(poligonos, calcular_perimetro)
print(perimetros)
[40.0, 40.0, 40.0]

Este exemplo ilustra a flexibilidade e o poder de tratar funções como objetos em Python, permitindo que criemos abordagens modulares para realizar diversas operações em dados geoespaciais. Ao mesmo tempo, evitamos a repetição de código e mantemos nosso código mais organizado e legível.

Funções com Número Variável de Argumentos

Em alguns casos, pode ser necessário definir uma função capaz de aceitar qualquer número de argumentos. Em Python, *args e **kwargs são convenções utilizadas para permitir que funções aceitem um número variável de argumentos. O termo *args refere-se a argumentos posicionais que são coletados em uma tupla, permitindo que você passe qualquer número de argumentos posicionais para a função. Por outro lado, **kwargs refere-se a argumentos nomeados que são coletados em um dicionário, possibilitando a inclusão de múltiplos argumentos com nomes específicos. Juntos, esses mecanismos oferecem grande flexibilidade, permitindo a criação de funções mais genéricas e adaptáveis a diferentes necessidades.

def varios_args(*args, **kwargs):
    print(args)    # Tupla dos argumentos posicionais
    print(kwargs)  # Dicionário dos argumentos nomeados

Este é um exemplo que demonstra como uma função em Python pode aceitar um número arbitrário de argumentos, tanto posicionais quanto nomeados. Inicialmente, a função varios_args é definida para aceitar um número variável de argumentos. Na sequência:

  • *args: Coleta os argumentos posicionais passados para a função em uma tupla. O nome args é uma convenção, e o importante aqui é o prefixo (*).

  • **kwargs: Coleta os argumentos nomeados (ou argumentos-chave) passados para a função em um dicionário. kwargs é abreviação de keyword arguments, e o importante é o prefixo **.

Dentro da função, simplesmente imprimimos os argumentos posicionais e nomeados coletados.

Considere o exemplo:

varios_args(1, 2, 3, a=4, b=5)
(1, 2, 3)
{'a': 4, 'b': 5}

Ao chamar a função, passamos três argumentos posicionais (1, 2 e 3) e dois argumentos nomeados (a=4 e b=5). Como esperado, args coletou os argumentos posicionais em uma tupla, e kwargs coletou os argumentos nomeados em um dicionário.

2.6 Manipulação de arquivos de texto e .CSV#

A manipulação de arquivos é uma tarefa comum em programação. Ela permite que você leia, escreva, crie ou delete arquivos a partir do seu programa. A maioria das linguagens de programação oferece bibliotecas ou módulos nativos para facilitar essas operações.

Abrindo um arquivo

Antes de ler ou escrever em um arquivo, você precisa abri-lo. Durante a abertura, você especifica o modo de abertura, que determina se você está lendo, escrevendo, anexando, etc.

Os modos de abertura comumente utilizados são:

  • Leitura (r): Abre um arquivo para leitura. Se o arquivo não existir, retorna um erro.

  • Escrita (w): Abre um arquivo para escrita. Cria um novo arquivo se ele não existir ou sobrescreve um arquivo existente.

  • Anexar (a): Abre um arquivo para anexar dados. Cria um novo arquivo se ele não existir ou move o ponteiro para o final de um arquivo existente para adicionar novos dados.

  • Leitura e escrita (r+): Abre um arquivo tanto para leitura quanto para escrita.

Exemplo:

# file1 = open('~/geopythonbook/files/exemplo.txt', 'r')

Nesse caso, não abrirá o arquivo, pois ele não existe. Vamos rodar o código abaixo, que criará o arquivo.

import os

# Obter o diretório pessoal do usuário
home_dir = os.path.expanduser("~")

# Caminho absoluto completo para o arquivo exemplo.txt:
file_path = os.path.join(home_dir, "geopythonbook", "files", "exemplo.txt")

with open(file_path, "w") as file:
    pass

O argumento ‘w’ especifica o modo em que o arquivo será aberto. O modo ‘w’ significa “escrever”. Se o arquivo já existir, seu conteúdo será sobrescrito. Se o arquivo não existir, ele será criado.

as file: Este é um alias para o arquivo aberto. Isso significa que, dentro do bloco with, você pode se referir ao arquivo aberto usando a variável file.

pass: A palavra-chave pass em Python é uma operação nula — nada acontece quando é executada. É usada como um espaço reservado quando uma declaração é requerida sintaticamente, mas você não quer executar nenhum comando ou código.

Escrevendo em um arquivo

Se você abrir um arquivo em modo de escrita ou anexação, pode escrever nele.

import os
home_dir = os.path.expanduser("~")
file_path = os.path.join(home_dir, 'geopythonbook/files/exemplo.txt')

with open(file_path, 'w') as file:
    file.write('Olá, Mundo!')

Lendo um arquivo

Depois de abrir ou criar um arquivo em modo de leitura, você pode ler seu conteúdo.

with open(file_path, 'r') as file:
    conteudo = file.read()
    print(conteudo)
Olá, Mundo!

Fechando um arquivo

Após a leitura, escrita ou ambas, é essencial fechar o arquivo.

file2 = open(file_path, 'r')
file2.close()

2.7 Módulos e pacotes#

No contexto de geoprocessamento, a organização e reutilização de código são essenciais. Módulos e pacotes em Python desempenham um papel vital em garantir que o código seja estruturado, reutilizável e fácil de manter.

No contexto do geoprocessamento, em que a complexidade das tarefas e a precisão dos dados são cruciais, a estruturação do código desempenha um papel fundamental. A implementação estratégica de módulos e pacotes introduz uma organização meticulosa ao código, tornando-o mais intuitivo e acessível. Esta organização não só segmenta diferentes funções e classes de maneira clara, mas também simplifica a navegação e a compreensão das operações codificadas.

Além da organização, módulos e pacotes são importantes no que diz respeito à reutilização e manutenção do código. Uma das maiores vantagens é a capacidade de reutilizar funções, especialmente aquelas destinadas à manipulação de dados geoespaciais, em múltiplos projetos, eliminando a redundância e economizando tempo. Esta modularidade também se traduz em manutenção mais eficaz. Corrigir erros ou atualizar características em módulos específicos se torna uma tarefa direta, minimizando o risco de perturbar outras seções do código, garantindo assim soluções de geoprocessamento mais robustas e confiáveis.

2.7.1 Módulos#

Um módulo em Python é simplesmente um arquivo contendo definições e instruções Python. O nome do arquivo é o nome do módulo acrescido do sufixo .py. Módulos são usados para organizar as funcionalidades em unidades lógicas, ajudando a manter o código mais organizado e facilitando a reutilização de código.

Importação de Módulos

Você pode acessar as funcionalidades de um módulo importando-o em seu script ou console interativo Python. A palavra-chave import é usada para fazer isso. Por exemplo:

import math
print(math.sqrt(16))  
4.0
  • Importação Parcial:

Se você quiser importar apenas partes específicas de um módulo, pode usar a palavra-chave from juntamente com import:

from math import sqrt
print(sqrt(16))  
4.0
  • Alias:

Para evitar conflitos de nome ou simplesmente para criar um atalho, você pode dar um “apelido” (alias) ao módulo ou à função que está importando:

import math as m
print(m.sqrt(16))  
4.0

Módulos Personalizados

Além dos módulos da biblioteca padrão (como math, os, sys, etc.), você pode criar seus próprios módulos.

Atributos do Módulo

Os módulos também têm certos atributos embutidos, que podem ser úteis para diferentes propósitos. Por exemplo, o atributo name pode ajudar a determinar se um módulo está sendo executado como um script principal ou se foi importado como um módulo em outro script.

2.7.2 Pacotes#

Um pacote em Python é simplesmente uma maneira de organizar módulos relacionados em um único diretório. Esse diretório contém um arquivo especial chamado init.py (que pode estar vazio ou conter código de inicialização para o pacote) e pode também conter subdiretórios, que representam subpacotes.

A ideia principal por trás dos pacotes é proporcionar um espaço de nomes separado, o que permite organizar e encapsular múltiplos módulos de maneira lógica e hierárquica. Esta organização é especialmente útil em projetos maiores ou quando se deseja distribuir um conjunto de funcionalidades relacionadas.

Estrutura de um Pacote

Imagine que temos um pacote chamado geoprocessamento que contém dois módulos: analise.py e visualizacao.py. A estrutura seria algo assim:

geoprocessamento/

|-- __init__.py

|-- analise.py

|-- visualizacao.py

Importando de um Pacote

Para importar um módulo específico de um pacote, você pode usar a seguinte sintaxe:

from geoprocessamento import analise

Ou, se quiser acessar uma função ou classe específica dentro desse módulo:

from geoprocessamento.analise import funcao_especifica

Arquivo init.py

O arquivo init.py é o que distingue um diretório comum de um pacote em Python. Ele é executado sempre que o pacote é importado e pode conter qualquer código Python válido. Pode ser usado para inicializar variáveis do pacote, importar submódulos, verificar dependências, entre outras coisas. Se estiver vazio, serve apenas para indicar que o diretório deve ser considerado um pacote ou subpacote.

2.8 O módulo Datetime#

Datetime é um módulo em Python que fornece classes para manipular datas e horários. Ele não é um tipo de dado básico como int, float, ou str, mas sim um módulo que contém vários tipos de dados para tratar questões relacionadas a datas, horários, intervalos de tempo, entre outros.

O módulo datetime é extremamente útil quando precisamos lidar com operações de datas e horários em Python, sejam elas simples, como pegar a data atual, ou mais complexas, como calcular a diferença entre duas datas ou considerar fusos horários.

Os principais tipos de dados e classes do módulo datetime são:

  • datetime.date: Uma classe para trabalhar com datas (ano, mês e dia);

  • datetime.time: Uma classe para trabalhar com horários (hora, minuto, segundo, microsegundo);

  • datetime.datetime: Uma classe que combina date e time, representando uma data e hora;

  • datetime.timedelta: Uma classe para trabalhar com diferenças de tempo, o que permite subtrair duas datas ou adicionar uma quantidade específica de dias, horas, etc., a uma data;

  • datetime.tzinfo: Base para classes que fornecem informações de fuso horário;

  • datetime.timezone: Uma subclasse de tzinfo que representa um deslocamento fixo do UTC.

Exemplos de uso do módulo datetime:

from datetime import datetime, date, time, timedelta

Pegando a data e hora atual:

agora = datetime.now()
print(agora)
2023-12-12 11:24:09.689567

Trabalhando apenas com datas:

hoje = date.today()
print(hoje)
2023-12-12

Criando uma data específica:

data_especifica = date(2023, 9, 23)
print(data_especifica)
2023-09-23

Criando um horário específico:

horario = time(14, 47, 5)
print(horario)
14:47:05

Diferença entre datas (timedelta):

diferenca = hoje - data_especifica
print(diferenca.days)  
80

Adicionando 5 dias à data atual:

daqui_a_cinco_dias = hoje + timedelta(days=5)
print(daqui_a_cinco_dias)  
2023-12-17

2.9 Gerenciamento e Tratamento de Exceções em Python#

Na utilização do Python no contexto do geoprocessamento, a ocorrência de erros é comum. Entender a diferença entre erros de sintaxe e exceções, e saber como lidar com cada um, é fundamental.

  • Erros de Sintaxe:

Erros de sintaxe, também conhecidos como erros de análise, ocorrem quando você escreve algo que o Python não reconhece como um código válido. Em outras palavras, você quebrou as “regras gramaticais” da linguagem. Exemplo:

if 5 > 2
    print('5 é maior que 2')

SyntaxError: expected ':'

Neste exemplo, falta o “:” após a condição do if, o que é um erro de sintaxe. Ao tentar executar esse código, o Python indicará a linha e a posição aproximada onde o erro foi detectado.

  • Exceções:

Mesmo que seu código esteja sintaticamente correto, pode acontecer de ele tentar fazer algo inválido ou impossível durante a execução. Isso levanta uma “exceção”. Se não tratadas, as exceções resultarão em um erro e a interrupção do programa. Exemplo:

print(5/0)

ZeroDivisionError: division by zero

Este código não tem erros de sintaxe, mas tentar dividir por zero é um erro matemático, e o Python lançará uma exceção, especificamente ZeroDivisionError.

2.9.1 Uso de blocos try e except para lidar com exceções#

Quando ocorrem erros durante a execução do programa, eles geralmente levantam “exceções”. Se essas exceções não forem tratadas, o programa será interrompido. Para evitar isso e possibilitar a execução de ações específicas em resposta a determinadas exceções, Python fornece uma maneira de capturar e responder a elas usando os blocos try e except.

Estrutura Básica

A estrutura básica de tratamento de exceções em Python é a seguinte:

try:
    # Bloco que pode gerar uma exceção
except TipoDaExcecao:
    # Bloco executado se a exceção em questão for detectada

Exemplos:

Divisão por Zero:

try:
    resultado = 10 / 0
except ZeroDivisionError:
    print('Tentativa de divisão por zero!')
Tentativa de divisão por zero!

Tentativa de acesso a um índice inexistente em uma lista:

lista = [1, 2, 3]
try:
    valor = lista[5]
except IndexError:
    print('Índice não encontrado na lista!')
Índice não encontrado na lista!

Tratamento de Múltiplas Exceções

Um bloco try pode ter múltiplos blocos except para tratar diferentes tipos de exceções:

try:
    # Código que pode disparar diferentes tipos de exceções
except (TypeError, ValueError):
    print('Um TypeError ou ValueError foi detectado!')
except ZeroDivisionError:
    print('Tentativa de divisão por zero!')

Capturando Todas as Exceções

Para capturar todas as exceções que não foram capturadas por except específicos, você pode usar um except sem especificar o tipo da exceção:

try:
    # Algum trecho de código
except:
    print('Uma exceção foi detectada!')

No entanto, usar este tipo de captura genérica pode não ser recomendado em muitos casos, pois pode mascarar erros inesperados e tornar a depuração mais difícil.

Usando a Exceção Capturada

Você pode usar a exceção capturada para obter mais informações sobre o erro:

try:
    resultado = 10 / 0
except ZeroDivisionError as e:
    print(f'Erro detectado: {e}')
Erro detectado: division by zero

Blocos else e finally

else: O bloco de código dentro de else é executado se nenhum erro ocorrer dentro do bloco try.

try:
    print('Tudo correu como esperado!')
except:
    print('Este trecho não será mostrado!')
else:
    print('Nenhum erro foi identificado no bloco try!')
Tudo correu como esperado!
Nenhum erro foi identificado no bloco try!

finally: Este bloco é executado não importa o que aconteça, e é geralmente usado para liberar recursos.

try:
    # Algum trecho de código
except:
    print('Esta mensagem será exibida se ocorrer uma exceção.')
finally:
    print('Esta mensagem sempre será exibida, ocorra ou não uma exceção.')

O uso adequado dos blocos try e except permite que você crie programas mais robustos e amigáveis ao usuário, fornecendo mensagens úteis ou tomando medidas corretivas. Porém, é importante usá-los de forma ponderada para não mascarar problemas no código que devem ser corrigidos.

2.9.2 Levantando exceções com raise#

Em Python, o comando raise é empregado para disparar intencionalmente uma exceção. Tal recurso é especialmente relevante quando desejamos sinalizar uma circunstância que não é tolerável ou quando precisamos relançar uma exceção após executar determinadas operações.

Veja a seguir algumas formas de utilizar o comando raise:

  • Disparando uma exceção básica:

Se em determinada situação do seu programa você identificar uma condição que seja inadmissível, é possível utilizar uma exceção para alertar sobre este problema.

'''
idade = -3

if idade < 0:
    raise ValueError('A idade inserida é inválida!')
'''
"\nidade = -3\n\nif idade < 0:\n    raise ValueError('A idade inserida é inválida!')\n"

Neste exemplo, caso idade receba um valor negativo, o sistema dispara uma exceção ValueError com a mensagem informando sobre a invalidade da idade.

  • Disparando uma exceção com dados extras:

Em certos contextos, ao disparar uma exceção, pode ser útil agregar informações adicionais, fornecendo maior clareza a quem estiver interpretando ou corrigindo o erro.

def divisao(a, b):
    if b == 0:
        raise ZeroDivisionError(f'Erro ao tentar dividir {a} por zero!')
    return a / b
  • Relançando uma exceção capturada:

Há momentos em que pode ser necessário capturar uma exceção, processar algo específico e, posteriormente, relançar essa mesma exceção. Isso pode ser realizado usando somente o comando raise dentro de um bloco except.

'''
try:
    resultado = 10 / 0
except ZeroDivisionError:
    print('Ocorreu uma tentativa de divisão por zero!')
    raise
    '''
"\ntry:\n    resultado = 10 / 0\nexcept ZeroDivisionError:\n    print('Ocorreu uma tentativa de divisão por zero!')\n    raise\n    "

Aqui, quando tentamos dividir por zero, a exceção ZeroDivisionError é identificada e tratada. Uma mensagem é exibida e, logo após, a exceção é relançada, pausando o programa.

  • Disparando exceções personalizadas:

É possível criar exceções personalizadas, úteis para sinalizar erros específicos de determinada aplicação ou contexto.

class ExcecaoEspecifica(Exception):
    pass

def funcao_exemplo(x):
    if x < 0:
        raise ExcecaoEspecifica('O valor inserido é proibido!')

Neste exemplo, criamos a exceção customizada ExcecaoEspecifica e a disparamos dentro da função funcao_exemplo quando identificamos um valor negativo.

2.10 A abordagem Pythonic para codificação#

Pythonic é um termo usado na comunidade Python para descrever um estilo de escrita de código que está em harmonia com a filosofia e as peculiaridades da linguagem Python. Escrever código de uma maneira Pythonic significa que você está aproveitando ao máximo os recursos e construções da linguagem, produzindo código que é legível, conciso e eficiente.

Importância do modo Pythonic:

  • Legibilidade: Uma das principais filosofias por trás do Python, conforme expresso em “The Zen of Python” (PEP 20), é que “A legibilidade conta”. O código Pythonic é mais fácil de ler e compreender;

  • Eficiência: Ao seguir as práticas Pythonic, muitas vezes você utilizará construções da linguagem otimizadas, resultando em código mais eficiente;

  • Manutenção: Código escrito de forma Pythonic é geralmente mais fácil de manter e estender;

  • Comunidade: Aderir a um estilo Pythonic torna mais fácil para outros desenvolvedores Python entenderem e contribuírem para o seu código. Isso é particularmente importante em projetos open-source.

Características do modo “Pythonic”:

  • Expressividade: Utilizar construções da linguagem que permitem expressar ideias complexas de maneira concisa;

  • Clareza: O código deve ser escrito de forma a ser autoexplicativo, valorizando a clareza sobre a complexidade;

  • Utilização de idioms: Usar padrões e estruturas de código que são naturalmente associados ao Python;

  • Adesão a PEP 8: Seguir a PEP 8, que é o guia de estilo para a codificação Python, ajudará na consistência e legibilidade do código.

Exemplos de código Pythonic vs. não-Pythonic:

Inicialmente vamos criar duas lista para aplicar os exemplos. A lista2 é uma lista vazia:

lista = [1, 2, 3]
lista2 = []

a) Iterar sobre uma sequência:

Não Pythonic

for i in range(len(lista)):
    print(lista[i])
1
2
3

Pythonic

for item in lista:
    print(item)
1
2
3

b) Verificar se uma lista está vazia:

Não Pythonic

if len(lista) == 0:
    print("A lista está vazia.")
else:
    print("A lista não está vazia")
A lista não está vazia
if len(lista2) == 0:
    print("A lista está vazia.")
else:
    print("A lista não está vazia")
A lista está vazia.

Pythonic

if not lista:
    print("A lista está vazia.")
else:
    print("A lista não está vazia")
A lista não está vazia
if not lista2:
    print("A lista está vazia.")
else:
    print("A lista não está vazia")
A lista está vazia.

c) List Comprehensions:

Não Pythonic

resultado = []
for i in range(10):
    resultado.append(i * 2)
print(resultado)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Pythonic

resultado = [i * 2 for i in range(10)]
print(resultado)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

d) Acessar elementos de um dicionário com um valor padrão

Inicialmente, vamos criar um dicionário:

dicionario = {'a': 1, 'b': 2, 'c': 3}

Vamos usar uma abordagem explícita para verificar se a chave ‘d’ está no dicionário e, em caso negativo, atribuir um valor padrão.

Não Pythonic

chave = 'd'

if chave in dicionario:
    valor = dicionario[chave]
else:
    valor = 'padrao'

print(valor)
padrao

Como a chave ‘d’ não existe, atribuímos o valor ‘padrao’ à variável valor.

Pythonic

chave = 'd'

valor = dicionario.get(chave, 'padrao')

print(valor)
padrao

A segunda abordagem (Pythonic) é mais concisa e recomendada, pois evita a necessidade de uma verificação explícita da existência da chave e torna o código mais limpo e fácil de entender.

Em síntese, escrever código de maneira Pythonic é abraçar a filosofia do Python e seus ideais de clareza, simplicidade e elegância. Isso não apenas torna o código mais agradável de ler e escrever, mas também promove boas práticas que são valorizadas em toda a comunidade Python.