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 classbloco 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 Examples. 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 datae armazena o valor inteiro 42. O outro é nomeado methode 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 datae methodnão são realmente parte do objeto, mas ainda podemos procurá-los por meio xde 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 selfparâmetro, o que não pode acontecer se olharmos Example.methoddiretamente).
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 namequando criamos um Example, e cada instância tem a sua própria name. Python irá ignorar o atributo class Example.namesempre que procurarmos o .namede 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 adeixa de ser um nome para 'hi ', e passa a ser um nome para 'hi mom'. Fizemos bum nome para 'hi 'também, e depois de reaplicar o anome, bainda é 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 bum nome para a mesma coisa que o anomeou e então mudamos essa coisa. Não criamos uma nova lista para anomear, 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.