Executando verificação de segurança...
2

Complementando:

Sobre o escopo de variáveis, existe uma "brecha": acessar uma variável que foi declarada fora da função (desde que não haja uma atribuição a ela) é permitido. Exemplo:

def f():
    print('dentro da função', x)

x = 10
f()
print('fora da função', x)

A saída deste código é:

dentro da função 10
fora da função 10

Ou seja, o x dentro da função refere-se à variável que foi criada fora dela. Isso é possível porque dentro da função não é feita nenhuma atribuição à variável x.

Agora se eu mudar para:

def f():
    x = 2
    print('dentro da função', x)

x = 10
f()
print('fora da função', x)

Agora a saída é:

dentro da função 2
fora da função 10

No caso, a atribuição x = 2 feita dentro da função poderia ser interpretada como a criação de uma variável local, ou uma alteração da variável x criada fora da função. Mas de acordo com as regras da linguagem, optaram pela primeira opção (ou seja, o x dentro da função não é o mesmo que está fora dela).

Se eu quisesse que a variável x externa fosse alterada dentro da função, teria que usar a declaração global:

def f():
    global x # quero usar a variável x que está definida fora da função
    x = 2
    print('dentro da função', x)

x = 10
f()
print('fora da função', x)

Agora a saída será:

dentro da função 2
fora da função 2

Sobre o parâmetro com valor default, tem uma pegadinha. Se usar tipos mutáveis, como listas ou dicionários, podem ocorrer situações inesperadas. Por exemplo:

# função recebe uma lista, e se não for passada, usa uma lista vazia
def f(lista=[]):
    lista.append(1)
    print(lista)

# chama a função 3 vezes
f()
f()
f()

O resultado será:

[1]
[1, 1]
[1, 1, 1]

Isso porque o valor default só é criado uma vez, quando a função é definida. Ou seja, cada vez que a função é chamada, ela acaba adicionando um elemento na mesma lista. Por isso que a cada chamada ela aumenta. A solução, neste caso, seria mudar para:

def f(lista=None):
    if lista is None:
        lista = []
    lista.append(1)
    print(lista)

Assim, a cada chamada sem parâmetros, uma nova lista é criada.


Por fim, sobre o *args e **kwargs, vale lembrar que também é possível usar ambos:

def f(*args, **kwargs):
    print('args:', args)
    print('kwargs:', kwargs)

print('somente posicionais:')
f(1, 2)

print('somente nomeados:')
f(a=3, b=4)

print('misturado:')
f(1, 2, a=3, b=4)

Assim, se eu chamar a função somente com argumentos posicionais, apenas args será preenchido e kwargs ficará vazio. E se chamar somente com argumentos nomeados, ocorre o contrário. E se chamar com ambos, tanto args quanto kwargs estarão preenchidos. A saída do código acima é:

somente posicionais:
args: (1, 2)
kwargs: {}
somente nomeados:
args: ()
kwargs: {'a': 3, 'b': 4}
misturado:
args: (1, 2)
kwargs: {'a': 3, 'b': 4}

Lembrando que os argumentos nomeados devem vir no final, ou seja, se fizer algo como f(a=1, b=2, 3, 4) ou ainda f(1, b=2, 3), dará erro.

Carregando publicação patrocinada...