{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Funções e módulos\n", "=====================\n", "\n", "Introdução\n", "------------\n", "\n", "As funções nos permitem agrupar várias declarações em um bloco lógico. Comunicamo-nos com uma função através de uma interface claramente definida, fornecendo certos parâmetros para a função e recebendo algumas informações de volta. Além dessa interface, geralmente não sabemos como, exatamente, uma função faz o trabalho dela para obter o valor que retorna.\n", "\n", "Por exemplo, a função `math.sqrt`: não sabemos, na totalidade, como ela calcula a raiz quadrada, mas sabemos sobre a interface: se passarmos `x` para a função, ela retornará (uma aproximação de) $\\sqrt{x}$.\n", "\n", "Esta abstração é uma coisa útil: é uma técnica comum na engenharia para quebrar um sistema em componentes menores (caixa preta) que trabalham juntos através de interfaces bem definidas, mas que não precisam saber sobre as realizações internas da funcionalidade uns dos outros. Na verdade, não ter que se preocupar com esses detalhes de implementação pode nos ajudar a ter uma visão mais clara do sistema composto por muitas dessas componentes.\n", "\n", "As funções fornecem os blocos básicos de funcionalidade em programas maiores (e simulações computacionais) e ajudam a controlar a complexidade inerente ao processo.\n", "\n", "Podemos agrupar funções em um módulo Python e, assim, criar nossas próprias bibliotecas de funcionalidades.\n", "\n", "Usando funções\n", "---------------\n", "\n", "A palavra \"função\" tem diferentes significados em matemática e programação. Na programação, ela se refere a uma sequência de operações que executam uma computação. Por exemplo, a função `sqrt()`, definida no módulo de matemática `math`, calcula a raiz quadrada de um determinado valor:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2.0" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from math import sqrt\n", "sqrt(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O valor que passamos para a função `sqrt` é 4 neste exemplo. Esse valor é chamado de *argumento* da função. Uma função pode ter mais de um argumento.\n", "\n", "A função retorna o valor 2.0 (o resultado de sua computação) para o \"contexto de chamada\". Esse valor é chamado de *valor de retorno* da função.\n", "\n", "É comum dizer que uma função \"leva\" um argumento e \"retorna\"\" um resultado, o chamado *valor de retorno*.\n", "\n", "#### Confusão comum sobre a impressão e os valores de retorno \n", "\n", "É um erro comum do principiante confundir a *impressão* de valores com *valores de retorno*. No exemplo a seguir, é difícil ver se a função `math.sin` retorna um valor ou se ela imprime o valor:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0.9092974268256817" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import math\n", "math.sin(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nós importamos o módulo `math` e chamamos a função `math.sin` com o argumento `2`. A chamada `math.sin(2)` realmente *retornará* o valor `0.909...`, não o imprimirá. No entanto, como não foi atribuído o valor de retorno a uma variável, o prompt do Python imprimirá o objeto retornado.\n", "\n", "A seguinte sequência alternativa funciona apenas se o valor for retornado:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.9092974268256817\n" ] } ], "source": [ "x = math.sin(2)\n", "print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O valor de retorno da chamada da função `math.sin(2)` é atribuído à variável `x` e `x` é impresso na linha seguinte.\n", "\n", "Geralmente, as funções devem executar \"silenciosamente\" (ou seja, não imprimir nada) e reportar o resultado de sua computação através do valor de retorno.\n", "\n", "Parte da confusão sobre valores impressos versus valores de retorno no prompt do Python vem da impressão rápida do Python (uma representação) dos objetos retornados *se* os objetos retornados não são atribuídos. Geralmente, ver os objetos retornados é exatamente o que queremos (como normalmente nos preocupamos com o objeto retornado), apenas ao aprender Python. Isso pode causar confusão leve sobre funções que retornam valores ou valores de impressão.\n", "\n", "##### Informação adicional\n", "\n", "- [Pense em Python](https://penseallen.github.io/PensePython2e/) apresenta uma introdução gentil às funções (em que se baseia o parágrafo anterior) nos Capítulo 3 e 6." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Definindo funções\n", "------------------\n", "\n", "Formato genérico de definição de uma função:\n", "\n", "```python\n", "def minha_funcao(arg1, arg2, ..., argn):\n", " \"\"\"Docstring opcional\"\"\"\n", "\n", " # Implementação da função\n", "\n", " return resultado # opcional\n", "\n", "# isto nao é parte da função\n", "algum_comando\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A terminologia de Allen Downey (em seu livro [Pense em Python](https://penseallen.github.io/PensePython2e/)) de funções frutíferas e infrutíferas distingue funções que retornam um valor daquelas que não retornam valor algum. A distinção refere-se à característica de uma função fornecer um valor de retorno (=frutífera) ou não retornar explicitamente um valor (=infrutífera). Se as funções não usam a instrução `return`, tendemos a dizer que a função não retorna nada (enquanto que, na realidade, sempre retornará o objeto `None` quando ele termina - mesmo que nela falte a instrução `return`).\n", "\n", "Por exemplo, a função `cumprimento` imprimirá \"Olá, mundo!\" quando chamada (e é infrutífero, pois não retorna um valor)." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def cumprimento():\n", " print(\"Olá, mundo!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Quando chamamos essa função:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Olá, mundo!\n" ] } ], "source": [ "cumprimento()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ela imprime “Olá, Mundo!” para `stdout`, como esperado. Se atribuírmos o valor de retorno da função para a variável `x`, poderemos inspecioná-lo:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Olá, mundo!\n" ] } ], "source": [ "x = cumprimento()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] } ], "source": [ "print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "e encontrarmos que a função `cumprimento`, de fato, retornou, o objeto `None`.\n", "\n", "Outro exemplo para uma função que não retorna valor algum (isso significa que não há palavra-chave `return` na função) seria:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def printpluses(n): \n", " print(n * \"+\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Geralmente, funções que retornam valores são mais úteis, pois podem ser usadas para montar código (talvez como outra função) combinando-as de maneira \"esperta\". Vejamos alguns exemplos de funções que retornam um valor.\n", "\n", "Suponhamos que precisemos definir uma função que calcule o quadrado de uma determinada variável. O código-fonte da função poderia ser:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "def quadrado(x):\n", " return x * x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A palavra-chave `def` diz ao Python que estamos *definindo* uma função naquele ponto. A função leva um argumento, a saber `x`. A função retorna `x*x`, que é, claramente, $x^2$. Aqui está um exemplo que mostra como a função pode ser definida e usada:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "attributes": { "classes": [ "numberLines" ], "id": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0 * 0 = 0\n", "1 * 1 = 1\n", "2 * 2 = 4\n", "3 * 3 = 9\n", "4 * 4 = 16\n" ] } ], "source": [ "def quadrado(x):\n", " return x * x\n", "\n", "for i in range(5):\n", " i_quadrado = quadrado(i)\n", " print(i, '*', i, '=', i_quadrado)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Vale mencionar que as linhas 1 e 2 definem a função `quadrado`, ao passo que as de linhas 4 a 6 são o programa principal." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos definir funções que levam mais do que um argumento:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import math\n", "\n", "def hipotenusa(x, y):\n", " return math.sqrt(x * x + y * y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Também é possível retornar mais de um argumento. Aqui está um exemplo de uma função que converte uma determinada *string* retornando-a em duas versões: com todos os caracteres em letras maiúsculas e todos os caracteres em letras minúsculas. Incluímos o programa principal para mostrar como esta função pode ser chamada:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Banana em minúsculas: banana e em maiúsculas BANANA\n" ] } ], "source": [ "def maiusculasEMinusculas(string):\n", " return string.upper(), string.lower()\n", "\n", "palavra = 'Banana'\n", "\n", "uppercase, lowercase = maiusculasEMinusculas(palavra)\n", "\n", "print(palavra, 'em minúsculas:', lowercase,\n", " 'e em maiúsculas', uppercase)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Podemos definir múltiplas funções em Python em um arquivo. Aqui está um exemplo com duas funções:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "*****************Olá, Mundo!*****************\n" ] } ], "source": [ "def retorna_estrelas( n ):\n", " return n * '*'\n", "\n", "def imprime_centrado_estrelas( string ):\n", " linelength = 46 \n", " starstring = retorna_estrelas((linelength - len(string)) // 2)\n", "\n", " print(starstring + string + starstring)\n", "\n", "imprime_centrado_estrelas('Olá, Mundo!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Leitura complementar\n", "\n", "- [Python Tutorial: Seção 4.6 Definindo Funções](http://docs.python.org/tutorial/controlflow.html#defining-functions)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Módulos\n", "-------\n", "\n", "- Agrupam funcionalidade;\n", "\n", "- Fornecem espaço de nomes;\n", "\n", "- A biblioteca padrão do Python contém uma vasta coleção de módulos (tente `help('modules')`);\n", "\n", "- Extensão da linguagem Python.\n", "\n", "### Importando módulos" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import math" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Isto introduzirá o nome `math` no espaço de nomes no qual o comando de importação foi lançado. Os nomes dentro do módulo `math` não aparecerão diretamente: eles devem ser acessados através do nome `math`. Por exemplo: `math.sin`." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import math, cmath" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mais de um módulo pode ser importado na mesma declaração, embora o [Python Style Guide](http://www.python.org/dev/peps/pep-0008/) recomende não fazer isso. Em vez disso, devemos escrever" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import math\n", "import cmath\n", "\n", "import math as matematica" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "O nome pelo qual o módulo é conhecido localmente pode ser diferente do seu nome \"oficial\". Os usos típicos deste são:\n", "\n", "- evitar conflitos de nomes com nomes existentes, e\n", "\n", "- mudar o nome para algo mais gerenciável. Por exemplo, `import SimpleHTTPServer as shs`. Isto é desencorajado para código de produção (visto que nomes longos e mais significativos tornam os programas muito mais compreensíveis do que os curtos e crípticos), mas para testar experiências de forma interativa, ser capaz de usar um sinônimo curto pode tornar sua vida muito mais fácil. Dado que os módulos (importados) são objetos de primeira classe, você pode, claro, simplesmente fazer `shs = SimpleHTTPServer` a fim de obter o identificador mais facilmente identificável no módulo." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from math import sin" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Esta declaração importará a função `sin` do módulo `math`, mas não irá introduzir o nome `math` no espaço de nomes corrente, senãp apenas o nome `sin`. É possível extrair mais de um nome do módulo de uma só vez:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from math import sin, cos" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finalmente, vejamos esta notação:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "from math import *" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Mais uma vez, isso não introduz o nome `math` no espaço de nomes corrente, mas *todos os nomes públicos* do módulo `math`. Em termos gerais, é uma má idéia fazer isso:\n", "\n", "- Muitos nomes novos serão despejados no espaço de nomes corrente.\n", "\n", "- Você tem certeza de que eles não sobrescreverão nenhum nome já presente?\n", "\n", "- Será muito difícil rastrear de onde esses nomes vieram\n", "\n", "- Dito isto, alguns módulos (incluindo os da biblioteca padrão, recomendam que sejam importados dessa maneira). Use com cuidado!\n", "\n", "- Isso é bom para testes rápidos interativos ou pequenos cálculos." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Criando módulos\n", "\n", "Um módulo é, em princípio, nada mais do que um arquivo Python. Aqui está um exemplo de um arquivo de módulo que é salvo em `modulo1.py`:\n", "\n", "```python\n", "def alguma_funcao_util():\n", " pass\n", "\n", "print(\"Meu nome é\", __name__)\n", "```\n", "\n", "Podemos executar este arquivo (módulo) como um programa em Python normal (por exemplo: `python modulo1.py`):" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/Users/gustavo/courses/calculo-numerico/lecture-ipynb/static/data\n" ] } ], "source": [ "cd static/data" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Meu nome eh', '__main__')\r\n" ] } ], "source": [ "!python modulo1.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Observamos que a variável mágica `__name__` leva o valor `__main__` se o arquivo de programa `modulo1.py` for executado.\n", "\n", "Por outro lado, podemos *importar* `modulo1.py` em outro arquivo (que poderia ter o nome `prog.py`), por exemplo, como este:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Meu nome eh modulo1\n" ] } ], "source": [ "import modulo1 # em um certo arquivo prog.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Quando o Python depara-se com a instrução `import modulo1` em `prog.py`, ele procura o arquivo `modulo1.py` no diretório de trabalho atual (e se ele não conseguir encontrá-lo em todos os diretórios em `sys.path`) e abre o arquivo `modulo1.py`. Ao fazer o *parsing* do arquivo `modulo1.py` de cima para baixo, ele adicionará quaisquer definições de função neste arquivo no espaço de nome `modulo1` no contexto de chamada (esse é o programa principal em `prog.py`). Neste exemplo, existe apenas a função `alguma_funcao_util`. Uma vez que o processo de importação esteja completo, podemos fazer uso de `modulo1.algum_funcao_util` em `prog.py`. Se o Python encontrar instruções diferentes das definições de função (e classe) durante a importação de `modulo1.py`, ele executará isso imediatamente. Neste caso, ele se deparará com a declaração `print(Meu nome eh, __name __)`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Observe a diferença na saída se importarmos `modulo1.py` em vez de executá-lo por conta própria: `__name__` dentro de um módulo leva o valor do nome do módulo se o arquivo for importado.\n", "\n", "### Uso de \\_\\_name\\_\\_\n", "\n", "Em suma,\n", "\n", "- `__name__` é `__main__` se o arquivo do módulo for executado sozinho;\n", "\n", "- `__name__` é o nome do módulo (ou seja, o nome do arquivo do módulo sem o sufixo `.py`) se o arquivo do módulo for importado.\n", "\n", "Podemos, portanto, usar a seguinte instrução `if` em `modulo1.py` para escrever o código que é *apenas executado* quando o módulo é executado por conta própria: isto é útil para manter programas de teste ou demonstrações das habilidades de um módulo neste programa principal \"condicional\". É prática comum para qualquer arquivo de módulo ter um programa principal condicional que demonstre suas capacidades." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exemplo 1\n", "\n", "O próximo exemplo mostra um programa principal para outro arquivo (`vetorial.py`) que é usado para demonstrar as habilidades das funções definidas naquele arquivo:\n", "\n", "```python\n", "from __future__ import division\n", "import math\n", "import numpy as N\n", "\n", "def norma(x):\n", " \"\"\"retorna a magnitude de um vetor x\"\"\"\n", " return math.sqrt(sum(x ** 2))\n", "\n", "\n", "def vetor_unitario(x):\n", " \"\"\"retorna um vetor unitario x/|x|. x requer um tipo numpy array.\"\"\"\n", " xnorm = norma(x)\n", " if xnorm == 0:\n", " raise ValueError(\"Vetor nulo nao pode ser normalizado\")\n", " return x / norma(x)\n", "\n", "\n", "if __name__ == \"__main__\":\n", " # uma pequena demonstracao de como as funcoes neste modulo podem ser usadas:\n", " x1 = N.array([0, 1, 2])\n", " print(\"A norma de \" + str(x1) + \" eh \" + str(norma(x1)) + \".\")\n", " print(\"O vetor unitario na direcao de \" + str(x1) + \" eh \" \\\n", " + str(vetor_unitario(x1)) + \".\")\n", "```\n", "\n", "Se este arquivo for executado usando `python vetorial.py`, entao `__name__==__main__` é verdadeiro, e a saída será" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "A norma de [0 1 2] eh 2.2360679775.\r\n", "O vetor unitario na direcao de [0 1 2] eh [ 0. 0.4472136 0.89442719].\r\n" ] } ], "source": [ "!python vetorial.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Se este arquivo for importado (ou seja, usado como um módulo) em outro arquivo Python, então `__name __ == __ main__` é falso, e esse bloco de declaração não será executado (e nenhuma saída será produzida).\n", "\n", "Esta é uma forma bastante comum de executar o código condicionalmente em arquivos que fornecem funções semelhantes às da biblioteca. O código que é executado se o arquivo for executado por conta própria geralmente consiste em uma série de testes (para verificar se as funções do arquivo realizam as operações certas - *testes de regressão* ou *testes de unidade*), ou alguns exemplos de como as funções da biblioteca no arquivo podem ser usadas.\n", "\n", "### Exemplo 2\n", "\n", "Mesmo que um programa Python não se destine a ser usado como um arquivo de módulo, é uma boa prática usar sempre um programa principal condicional:\n", "\n", "- muitas vezes, verifica-se, mais tarde, que as funções no arquivo podem ser reutilizadas (assim, economiza-se trabalho)\n", "\n", "- isso é conveniente para testes de regressão.\n", "\n", "Suponha que um determinado exercício seja escrever uma função que retorne os primeiros 5 números primos e além disso, os imprima. (Há, claro, uma solução trivial para isso, como sabemos, os números primos, e devemos imaginar que o cálculo necessário é mais complexo). Podemos ser tentados a escrever:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3 5 7 11\n" ] } ], "source": [ "def primos5():\n", " return (2, 3, 5, 7, 11)\n", "\n", "for p in primos5():\n", " print(\"%d\" % p), " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "É um estilo melhor usar uma função principal condicional, i.e.:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3 5 7 11\n" ] } ], "source": [ "def primos5():\n", " return (2, 3, 5, 7, 11)\n", "\n", "if __name__==\"__main__\":\n", " for p in primos5():\n", " print(\"%d\" % p)," ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Um purista pode argumentar que o seguinte ainda é mais \"limpo\":" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 3 5 7 11\n" ] } ], "source": [ "def primos5():\n", " return (2, 3, 5, 7, 11)\n", "\n", "def main():\n", " for p in primos5():\n", " print(\"%d\" % p),\n", "\n", "if __name__==\"__main__\":\n", " main()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "mas qualquer uma das duas opções é boa.\n", "\n", "#### Leitura complementar\n", "\n", "- [Python Tutorial Seção 6](http://docs.python.org/tutorial/modules.html#modules)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 1 }