Ok, as primeiras coisas primeiro.
Não existe "declaração de variável" ou "inicialização de variável" em Python.
Existe simplesmente o que chamamos de "atribuição", mas provavelmente deveríamos chamar apenas de "nomeação".
Atribuição significa "este nome do lado esquerdo agora se refere ao resultado da avaliação do lado direito, independentemente do que foi referido antes (se houver)".
foo = 'bar'
foo = 2 * 3
Como tal, os nomes do Python (um termo melhor do que "variáveis", sem dúvida) não têm tipos associados; os valores sim. Você pode reaplicar o mesmo nome a qualquer coisa, independentemente de seu tipo, mas a coisa ainda tem um comportamento que depende de seu tipo. O nome é simplesmente uma forma de se referir ao valor (objeto). Isso responde à sua segunda pergunta: você não cria variáveis para conter um tipo personalizado. Você não cria variáveis para conter nenhum tipo específico. Você não "cria" variáveis. Você dá nomes aos objetos.
Segundo ponto: Python segue uma regra muito simples quando se trata de classes, que na verdade é muito mais consistente do que linguagens como Java, C ++ e C #: tudo que é declarado dentro do class
bloco faz parte da classe . Portanto, functions ( def
) escritas aqui são métodos, ou seja, parte do objeto de classe (não armazenado por instância), assim como em Java, C ++ e C #; mas outros nomes aqui também fazem parte da classe. Novamente, os nomes são apenas nomes, não têm tipos associados e as funções também são objetos em Python. Portanto:
class Example:
data = 42
def method(self): pass
As classes também são objetos , em Python.
Portanto, agora criamos um objeto chamado Example
, que representa a classe de todas as coisas que são Example
s. Este objeto tem dois atributos fornecidos pelo usuário (em C ++, "membros"; em C #, "campos ou propriedades ou métodos"; em Java, "campos ou métodos"). Um deles é nomeado data
e armazena o valor inteiro 42
. O outro é nomeado method
e armazena um objeto de função. (Existem vários outros atributos que o Python adiciona automaticamente.)
Porém, esses atributos ainda não fazem parte do objeto. Fundamentalmente, um objeto é apenas um pacote de mais nomes (os nomes dos atributos), até que você descubra coisas que não podem mais ser divididas. Assim, os valores podem ser compartilhados entre diferentes instâncias de uma classe, ou mesmo entre objetos de diferentes classes, se você definir isso deliberadamente.
Vamos criar uma instância:
x = Example()
Agora temos um objeto separado chamado x
, que é uma instância de Example
. Os data
e method
não são realmente parte do objeto, mas ainda podemos procurá-los por meio x
de alguma mágica que o Python faz nos bastidores. Quando olhamos para cima method
, em particular, obteremos um "método vinculado" (quando o chamamos, x
é passado automaticamente como o self
parâmetro, o que não pode acontecer se olharmos Example.method
diretamente).
O que acontece quando tentamos usar x.data
?
Quando o examinamos, ele é pesquisado primeiro no objeto. Se não for encontrado no objeto, o Python procura na classe.
No entanto, quando atribuímos a x.data
, Python criará um atributo no objeto. Ele vai não substituir o atributo de classe.
Isso nos permite fazer a inicialização do objeto . O Python chamará automaticamente o __init__
método da classe em novas instâncias quando forem criadas, se houver. Neste método, podemos simplesmente atribuir a atributos para definir valores iniciais para esse atributo em cada objeto:
class Example:
name = "Ignored"
def __init__(self, name):
self.name = name
Agora devemos especificar a name
quando criamos um Example
, e cada instância tem a sua própria name
. Python irá ignorar o atributo class Example.name
sempre que procurarmos o .name
de uma instância, porque o atributo da instância será encontrado primeiro.
Uma última advertência: modificação (mutação) e atribuição são coisas diferentes!
Em Python, as strings são imutáveis. Eles não podem ser modificados. Quando você faz:
a = 'hi '
b = a
a += 'mom'
Você não altera a string 'hi' original. Isso é impossível em Python. Em vez disso, você cria uma nova string 'hi mom'
e a
deixa de ser um nome para 'hi '
, e passa a ser um nome para 'hi mom'
. Fizemos b
um nome para 'hi '
também, e depois de reaplicar o a
nome, b
ainda é um nome para 'hi '
, porque 'hi '
ainda existe e não foi alterado.
Mas as listas podem ser alteradas:
a = [1, 2, 3]
b = a
a += [4]
Agora b
é [1, 2, 3, 4] também, porque criamos b
um nome para a mesma coisa que o a
nomeou e então mudamos essa coisa. Não criamos uma nova lista para a
nomear, porque Python simplesmente trata +=
de listas de forma diferente.
Isso é importante para os objetos porque se você tivesse uma lista como um atributo de classe e usasse uma instância para modificar a lista, então a mudança seria "vista" em todas as outras instâncias. Isso ocorre porque (a) os dados são, na verdade, parte do objeto de classe e não qualquer objeto de instância; (b) porque você estava modificando a lista e não fazendo uma atribuição simples, você não criou um novo atributo de instância ocultando o atributo de classe.