"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Funções são como liquidificadores! Inserimos bananas e um copo de leite, ajustamos alguns parâmetros como o tempo de processamento e por fim recebemos uma deliciosa vitamina! E o melhor é que não precisamos entender os mínimos detalhes sobre como os mecanismos internos do aparelho funcionam para fazer nossa vitamina. Tampouco precisamos ter construído o liquidificador para poder saboreá-la. Além disso, qualquer que fosse o liquidificador, esperaríamos obter algo não muito diferente de uma vitamina, desde que utilizássemos os mesmos ingredientes e regulagens.\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Agora de forma mais sofisticada, funções são construções que **encapsulam** um determinado comportamento que se espera executar múltiplas vezes durante a execução de um programa. São rotinas, que podem ter sido definidas pelo próprio programador ou por outros programadores. No caso do liquidificador, a rotina foi definida pelo próprio fabricante. O usuário só precisa saber o que precisa saber como operá-lo: que tipos de coisas deve fornecer como **entrada** (*input*) e o que deve esperar receber como **saída** (*output*).\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Funções facilitam sua vida por dois motivos principais. \n",
"\n",
"1. Permitem ao programador **abstrair** computações, sem precisar se preocupar a todo momento sobre os mínimos detalhes de como elas são de fato realizadas. Imagine se você tivesse que se preocupar com os detalhes sobre como um texto é imprimido na tela de seu computador toda vez que você precisasse desta funcionalidade... Felizmente a função `print`, que utilizamos no nosso programa \"Hello World\", permite que esta rotina seja abstraída para você, o que facilita bastante seu aprendizado! \n",
"\n",
"2. Permitem **compartilhar** e **reutilizar** código. Se você construir uma função que possa ajudar outras pessoas também, por que não compartilhar? Isso acontece bastante na comunidade de programadores, e os ajuda a não ficar \"reinventando a roda\" quando precisam de alguma funcionalidade que já foi implementada por alguém. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para começar a entender como trabalhar com funções, precisamos conhecer seus três componentes principais: *(i)* **nome**, *(ii)* **parâmetros** e *(iii)* **corpo**.\n",
"\n",
"Dar um **nome** às funções é uma forma simples de mantermos uma referência a elas. Embora possamos nomear funções conforme nossa vontade, é recomendável escolhermos nomes que nos digam algo sobre seu funcionamento. É fácil lembrar que a função `print`, por exemplo, serve para imprimir algo na tela.\n",
"\n",
"Os **parâmetros** fornecem um meio para \"afinarmos\" o comportamento de uma função para nossas necessidades. Por exemplo, no caso do liquidificador, alguns parâmetros relevantes seriam o tempo de processamento, ou a velocidade de rotação do motor. Os dados de entrada (*inputs*) são também passados para as funções através de parâmetros. No jargão da programação, nos referimos aos valores que passamos no lugar de cada um dos parâmetros como **argumentos**. Pense em um parâmetro como um *placeholder* para um valor, enquanto o argumento é o valor em si, passado para dentro da função através de um parâmetro.\n",
"\n",
"Por fim, no **corpo** da função especificamos todas as etapas que devem ser realizadas por ela. Estas etapas incluem o processamento dos dados de entrada (*inputs*) e a construção do resultado que ela produzirá como saída (*output*). No fim das contas, a ideia é que as computações descritas no corpo da função sejam abstraídas para o usuário da função."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Objetivos.\n",
"\n",
"Após esta aula você deverá ser capaz de:\n",
"* **Definir** e **executar** funções;\n",
"* Delimitar o **escopo** de uma função;\n",
"* Reutilizar funcionalidades distribuídas em **pacotes** pela comunidade *Python*."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Definindo uma nova função"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para definir uma nova função precisamos obedecer à seguinte sintaxe:\n",
"* a palavra `def` indica que uma nova função está sendo definida;\n",
"* após `def`, deve ser escrito o **nome** da função;\n",
"* após o nome da função, entre parênteses, os **parâmetros** são separados por vírgulas;\n",
"* Os dois pontos `:` após os parênteses indica que o **corpo** da função vem a seguir, no bloco de código abaixo;\n",
"* O bloco de código deve ser escrito com uma **indentação**, o distanciamento em relação à margem esquerda da célula;\n",
"* No fim do corpo da função, o valor de saída (*output*) é indicado após a palavra `return`."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Para ilustrar, vamos construir uma nova função, batizada com o nome `foo` (poderia ser qualquer outro nome, tipo `dinossauro`). Ela simplesmente deve imprimir no console os valores que passamos em cada um dos parâmetros e, no fim, retornar como *output* o valor $1$."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"def foo(par1, par2):\n",
" print(par1)\n",
" print(par2)\n",
" return 1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Uma vez definida, a função fica guardada na memória, mas não é automaticamente executada. Para de fato executá-la, precisamos **chamá-la**, escrevendo seu nome e passando argumentos no lugar dos parâmetros, entre parênteses."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Santos\n",
"Dumont\n"
]
},
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"foo(\"Santos\", \"Dumont\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos definir agora uma função chamada `funcaoSoma`, que simplesmente soma dois números e retorna o resultado. Ela possui os parâmetros `n1` e `n2`, que esperam receber como valores (ou argumentos) dois valores numéricos. "
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def funcaoSoma(n1,n2):\n",
" res = n1 + n2\n",
" return res"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"8"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"funcaoSoma(3,5)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"20"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"funcaoSoma(-3,23)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"42"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"funcaoSoma(42,0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos também armazenar o resultado de funções em variáveis! Vamos declarar as variáveis `a`, `b`, `c` e `d`, com quaisquer valores numéricos. Em seguida, usaremos a função `funcaoSoma` para somar `a` e `b` e armazene o resultado em uma variável `a_b`. Depois, usaremos novamente a função `funcaoSoma` para somar `c` e `d` e armazene o resultado em uma variável `c_d`. Finalmente, somaremos os valores em `a_b` e `c_d` usando a mesma `funcaoSoma`, armazenando o resultado final em uma variável `resFinal`."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"28"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a = 3\n",
"b = 5\n",
"c = 9\n",
"d = 11\n",
"\n",
"a_b = funcaoSoma(a,b)\n",
"c_d = funcaoSoma(c,d)\n",
"\n",
"resFinal = funcaoSoma( a_b, c_d )\n",
"\n",
"resFinal"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Podemos também passar resultados de funções como argumentos para outras funções, sem precisar utilizar variáveis intermediárias! Podemos, por exemplo, realizar as mesmas computações da célula acima sem precisar declarar `a_b` e `c_d`."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"28"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a = 3\n",
"b = 5\n",
"c = 9\n",
"d = 11\n",
"\n",
"resFinal = funcaoSoma( funcaoSoma(a,b), funcaoSoma(c,d) )\n",
"\n",
"resFinal"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Escopo de funções"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Funções operam em um \"contexto\" próprio. \n",
"É como se criássemos uma \"bolha\" toda vez que executamos uma função. Tudo o que está dentro desta bolha não é visível de fora dela, mas todo o conteúdo que está fora dela é visível de dentro.\n",
"Nos referimos ao contexto de \"dentro da bolha\" como o **escopo local** da função, e tudo o que está fora como o **escopo global**.\n",
"\n",
"Sendo assim, uma funçao \"enxerga\" todas as variáveis em escopo local, ou seja, aquelas que são definidas dentro dela própria (incluindo os parâmetros); e as variáveis em escopo global, definidas fora dela.\n",
"No entanto, variáveis locais, definidas dentro de funções, não são visíveis de fora delas!"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Variável a ANTES da execução da função: 3\n",
"Variável b ANTES da execução da função: 5\n",
"---\n",
"Variável a DENTRO da função foo: 7\n",
"Variável b DENTRO da função foo: 5\n",
"Variável m DENTRO da função foo: 11\n",
"---\n",
"Variável a APÓS a execução da função 3\n",
"Variável b APÓS da execução da função: 5\n"
]
},
{
"ename": "NameError",
"evalue": "name 'm' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Variável a APÓS a execução da função\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Variável b APÓS da execução da função:\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Variável m\"\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mNameError\u001b[0m: name 'm' is not defined"
]
}
],
"source": [
"a = 3\n",
"print(\"Variável a ANTES da execução da função:\",a)\n",
"\n",
"b = 5\n",
"print(\"Variável b ANTES da execução da função:\",b)\n",
"\n",
"def foo(): # A função é apenas definida aqui\n",
" a = 7\n",
" print(\"Variável a DENTRO da função foo:\",a)\n",
" print(\"Variável b DENTRO da função foo:\",b)\n",
" m = 11\n",
" print(\"Variável m DENTRO da função foo:\",m)\n",
" \n",
"print('---')\n",
"foo() # A função apenas é de fato executada aqui\n",
"print('---')\n",
"\n",
"print(\"Variável a APÓS a execução da função\",a)\n",
"print(\"Variável b APÓS da execução da função:\",b)\n",
"print(\"Variável m\",m)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Obs:** Um mesmo nome pode ser dado para variáveis em escopos diferentes (que podem conter valores diferentes)! Nestes casos, a variável que foi definida em escopo local prevalece sobre a global."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Reutilizando funcionalidades"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O segredo para um bom desempenho como programador é jamais reinventar a roda! Sempre que possível, devemos buscar reutilizar código que já foi escrito por outros programadores. \n",
"**Pacotes** são uma forma eficiente usada pelos programadores para estruturar e compartilhar seu código com a comunidade, sendo criados com o intuito de entregar aos usuários um conjunto de funcionalidades. \n",
"O código dentro de um pacote é organizado em **módulos**, cada qual contendo um conjunto de funções e variáveis que implementam funcionalidades mais específicas.\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Veja uma lista de pacotes potencialmente interessantes para um biólogo:\n",
"* [Biopython](http://biopython.org/) com funcionalidades específicas para aplicações em biologia molecular computacional, na **bioinformática**;\n",
"* [Numpy](http://www.numpy.org/) para computação científica, com representações e operações otimizadas sobre objetos matemáticos;\n",
"* [Pandas](http://pandas.pydata.org/) para representação de *Data frames* e análise de dados;\n",
"* [Matplotlib](https://matplotlib.org/) para construção de figuras (visualização de dados);\n",
"* [Statsmodels](http://www.statsmodels.org/stable/index.html) para trabalhar com modelos estatísticos;\n",
"* [ScikitLearn](http://scikit-learn.org/stable/) para trabalhar com algoritmos de aprendizado de máquina;\n",
"* [Networkx](https://networkx.github.io/) para construir modelos em redes complexas;\n",
"* [ArcPy](http://desktop.arcgis.com/en/arcmap/10.3/analyze/arcpy/what-is-arcpy-.htm) é um pacote para automação de tarefas de análise e geração de mapas em ArcGis;\n",
"* [Matplotlib Basemap](https://matplotlib.org/basemap/) é um conjunto de ferramentas para plotar mapas em python, utilizando como base a *Matplotlib*;\n",
"* [Pygbif](http://pygbif.readthedocs.io/en/latest/) é um cliente *Python* que facilita o acesso a dados de ocorrência de espécies no [GBIF](https://www.gbif.org/) (Global Biodiversity Information Facility)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Em *Python*, existem algumas funções que são carregadas com a inicialização do interpretador, por serem utilizadas de forma mais ampla pelos programadores. Alguns exemplos são as funções `print`, `type`, `range` e `open`. \n",
"Outras, no entanto, são mais específicas e portanto não são carregadas automaticamente.\n",
"Usamos a palavra `import` para carregar um pacote ou módulo."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"O módulo `math`, por exemplo, reúne um conjunto de funções e constantes que facilitam muito trabalhar com elementos da matemática. Estas funções não são carregadas automaticamente, e portanto devemos **importar o módulo** para utilizá-las.\n",
"Vamos carregar o módulo `math` e, em seguida, executar a função `factorial` provida por ele. Conforme podemos consultar na documentação, esta função espera um número como *input* e retorna seu fatorial, como *output*."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Fatorial de 1: 1\n",
"Fatorial de 2: 2\n",
"Fatorial de 3: 6\n",
"Fatorial de 10: 3628800\n"
]
}
],
"source": [
"import math\n",
"\n",
"print(\"Fatorial de 1:\",math.factorial(1))\n",
"print(\"Fatorial de 2:\",math.factorial(2))\n",
"print(\"Fatorial de 3:\",math.factorial(3))\n",
"print(\"Fatorial de 10:\",math.factorial(10))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exercícios"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Ex 1.** Defina uma função `calculaMedia`, que calcula a média entre **três** números quaisquer. Em seguida, execute sua função com valores diferentes. O que acontece se você definir a função com três parâmetros mas passar um número diferente de argumentos?"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"def calculaMedia(n1,n2,n3):\n",
" res = (n1+n2+n3)/3\n",
" return res"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3.0"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"calculaMedia(1,3,5)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2.6666666666666665"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"calculaMedia(1,2,5)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "calculaMedia() missing 1 required positional argument: 'n3'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# Este código vai gerar um erro: A função esperava três argumentos mas nós só passamos 2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mcalculaMedia\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: calculaMedia() missing 1 required positional argument: 'n3'"
]
}
],
"source": [
"# Este código vai gerar um erro: A função esperava três argumentos mas nós só passamos 2\n",
"calculaMedia(1,5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Ex 2.** Defina uma função `multiplica`, que multiplica dois números quaisquer. Em seguida, declare as variáveis `a`, `b` e `c` com quaisquer valores numéricos. Multiplique `a` e `b` utilizando a função `multiplica` e, em seguida, execute novamente a função `multiplica` para multiplicar o resultado do passo anterior pelo número em `c`."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"def multiplica(n1,n2):\n",
" return n1*n2"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"105"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a=3\n",
"b=5\n",
"c=7\n",
"\n",
"multiplica( multiplica(a,b), c)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Ex 3.** A função `exponencia` abaixo deve realizar uma exponenciação do número `n1` à potência `n2`. Mas ao executar a célula para definir a função recebemos uma mensagem de erro. Corrija o código e experimente executar a função com alguns pares de números."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"def exponencia(n1,n2):\n",
" return n1**n2"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"8"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"exponencia(2,3)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"25"
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"exponencia(5,2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Ex 4.** A função `mensagemMeuNome` abaixo deveria receber como argumento o seu nome, imprimir uma mensagem com ele e, por fim, retornar o seu nome como *output*. No entanto, existe um *bug*. Você consegue identificá-lo e corrigí-lo? "
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Olá! Me chamo Pedro\n"
]
},
{
"data": {
"text/plain": [
"'Pedro'"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nome = \"Luke Skywalker\"\n",
"\n",
"def mensagemMeuNome( meuNome ):\n",
" print(\"Olá! Me chamo\", meuNome)\n",
" return meuNome\n",
"\n",
"mensagemMeuNome(\"Pedro\") # insira seu nome entre parênteses"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Ex 5.** A função `type` é automaticamente carregada com a inicialização do interpretador *Python*. Você consegue descobrir o que ela faz? Experimente passar diferentes tipos de dados."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tipo de 'Algum texto': \n",
"Tipo de 43: \n",
"Tipo de True: \n",
"Tipo de 32.21: \n"
]
}
],
"source": [
"# A função type retorna o tipo do valor passado como argumento\n",
"print(\"Tipo de 'Algum texto':\", type(\"Algum texto\") )\n",
"print( \"Tipo de 43:\",type(43) )\n",
"print( \"Tipo de True:\", type(True) )\n",
"print( \"Tipo de 32.21:\", type(32.21) )"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"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.5"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}