Objetos e tipos
Traduzido de http://blog.thedigitalcatonline.com/blog/2014/08/20/python-3-oop-part-1-objects-and-types/ publicado por Leonardo Giordani em 20/08/2014Sobre esta série
A programação orientada a objetos (OOP) tem sido o principal paradigma de programação há várias décadas, desde as tentativas iniciais nos anos 60 até chegar em algumas das linguagens mais importantes usadas atualmente. Sendo um conjunto de conceitos de programação e metodologias de design, não dá para dizer que exista uma linguagem que a implementou "corretamente" ou "perfeitamente", na realidade existe tantas implementações diferentes, como existem linguagens diferentes.Portanto, um dos aspectos mais interessantes das linguagens OOP é o entendimento de como elas implementam esses conceitos. Nesta publicação, vou tentar dar início à analise da implementação OOP da linguagem Python. Devido à riqueza do tópico, considero essa tentativa como um conjunto de ideias para os iniciantes de Python que estão tentando encontrar o caminho para essa linguagem bonita (e às vezes peculiar).
Esta série de postagens pretende apresentar o leitor à implementação do Python 3 dos conceitos de programação orientada a objetos. O conteúdo desta e das próximas postagens não serão completamente diferentes da série anterior de "OOP Concepts in Python 2.x", no entanto. O ponto é que, embora algumas das estruturas internas mudaram muito, a filosofia global não se alterou, sendo Python 3 uma evolução do Python 2 e não um novo idioma.
Então escolhi dividir a série anterior e adaptar seu conteúdo ao Python 3 em vez de publicar uma mera lista de correções. Acho que esta maneira deve ser mais útil para novos leitores, que, de outra forma, seriam forçados a ler a série anterior.
print na função print(). Esta é, de fato, uma mudança muito pequena, em comparação com outras modificações feitas nas estruturas internas, mas é a mais impressionante e será a fonte de 80% de seus erros de sintaxe quando você começar a escrever o código Python 3.Lembre-se de que a saída de tela é agora uma função e escreva
print(a) e não print a. Vale observar que Python 2.7 aceita o uso de print(a) então esta parte do código em Python 3, funciona também em Python 2.7.De volta para o objeto
A ciência da computação lida com dados e com procedimentos para manipular esses dados. Tudo, desde os primeiros programas Fortran até as últimas aplicações móveis, é sobre dados e suas manipulações.Então, se os dados são os ingredientes e os procedimentos são as receitas, parece (e pode ser) razoável mantê-las separadas.
Vamos fazer alguma programação procedutal em Python
In [1]:
# Alguns dados
data = (13, 63, 5, 378, 58, 40)
# Um procedimento que calcula a média
def avg(d):
return sum(d)/len(d)
avg(data)
Out[1]:
Como você pode ver, o código é simples: o procedimento (função) atua numa seqüência de dados e retorna a média dos itens de lista. Até agora, tudo certo: calcular a média dos números deixando-os intocados e criar novos dados.
A observação do mundo cotidiano, no entanto, mostra que dados complexos mudam: um dispositivo elétrico está ligado ou desligado, uma porta está aberta ou fechada, o conteúdo de uma estante de livros em seu quarto muda à medida que você compra novos livros.
Você ainda pode gerenciá-lo mantendo os dados e procedimentos separados, por exemplo:
A observação do mundo cotidiano, no entanto, mostra que dados complexos mudam: um dispositivo elétrico está ligado ou desligado, uma porta está aberta ou fechada, o conteúdo de uma estante de livros em seu quarto muda à medida que você compra novos livros.
Você ainda pode gerenciá-lo mantendo os dados e procedimentos separados, por exemplo:
In [2]:
# Há duas portas numeradas, inicialmente fechadas
porta1 = [1, 'fechada']
porta2 = [2, 'fechada']
# Procedimento para abrir a porta
def abre_porta(porta):
porta[1] = 'aberta'
In [3]:
abre_porta(porta1)
porta1
Out[3]:
In [4]:
abre_porta(porta2)
porta2
Out[4]:
Descrevi uma porta como uma estrutura contendo um número e o status da porta (como faria em linguagens como LISP, por exemplo). O procedimento sabe como essa estrutura é feita e pode alterá-la.
Isso também funciona corretamente. Surge, contudo, alguns problemas, quando começo a criar tipos especializados de dados. O que acontece, por exemplo, se eu introduzo um tipo de dados "porta com fechadura", que só pode ser aberta quando não está trancada? Vamos ver:
Isso também funciona corretamente. Surge, contudo, alguns problemas, quando começo a criar tipos especializados de dados. O que acontece, por exemplo, se eu introduzo um tipo de dados "porta com fechadura", que só pode ser aberta quando não está trancada? Vamos ver:
In [5]:
# Há duas portas numeradas, inicialmente fechadas
porta1 = [1, 'fechada']
porta2 = [2, 'fechada']
# O procedimento para abrir a porta
def abre_porta(porta):
porta[1] = 'aberta'
# Esta é uma porta com fechadura, inicialmente destrancada
fporta1 = [1, 'fechada', 'destrancada']
# Procedimento para abrir uma porta padrão
def abre_porta(porta):
porta[1] = 'aberta'
# Procedimento para abrir uma porta com fechadura
def abre_fporta(porta):
if porta[2] == 'destrancada':
porta[1] = 'aberta'
In [6]:
abre_porta(porta1)
porta1
Out[6]:
In [7]:
abre_fporta(fporta1)
fporta1
Out[7]:
Embora tudo ainda funcione sem surpresas neste código. Como você pode ver, tive que encontrar um nome diferente para o procedimento que abre uma porta com fechadura, uma vez que sua implementação difere do procedimento que abre uma porta padrão. Mas, espere... ainda estou abrindo uma porta, a ação é a mesma, e isso muda o status da própria porta. Então, por que tenho que lembro que uma porta fechada deve ser aberta com, em abre_fporta()vez de abre_porta() se o verbo, a ação, é o mesmo?
Há chances de que esta separação entre dados e procedimentos não se encaixem perfeitamente em algumas situações. O problema principal é que a ação "aberta" não está realmente usando a porta; antes está mudando seu estado. Assim como ocorre com os botões de controle de volume do seu telefone, que estão no próprio telefone, o procedimento "aberto" deve se referir à "porta".
Isso é exatamente o que leva ao conceito de objeto: um objeto, no contexto OOP, é uma estrutura que contém dados e procedimentos que operam neles.
O significado comportamental representa o fato de que você sabe o que é descrevendo como ele age. Esta é a base do chamado "duck typing", numa tradução literal: "tipagem de pato": se algo age como um pato, é um pato (https://osantana.me/duck-typing/).
O significado estrutural identifica o tipo da coisa, observando sua estrutura interna. Portanto, duas coisas que atuam da mesma maneira, mas são internamente diferentes, são de tipos diferentes.
Ambos os pontos de vista podem ser válidos e diferentes linguagens podem implementar e enfatizar um significado ou outro, e até mesmo ambos.
Em Python, o tipo de objeto é representado pela classe utilizada para construir o objeto: isto é, em Python o tipo de palavra tem o mesmo significado da classe de palavras .
Por exemplo, uma das classes internas de Python é int, que representa um número inteiro
Há chances de que esta separação entre dados e procedimentos não se encaixem perfeitamente em algumas situações. O problema principal é que a ação "aberta" não está realmente usando a porta; antes está mudando seu estado. Assim como ocorre com os botões de controle de volume do seu telefone, que estão no próprio telefone, o procedimento "aberto" deve se referir à "porta".
Isso é exatamente o que leva ao conceito de objeto: um objeto, no contexto OOP, é uma estrutura que contém dados e procedimentos que operam neles.
Nosso querido tipo?
Quando se fala sobre dados, precisamos imediatamente apresentar o conceito de tipo. Este conceito pode ter dois significados na ciência da computação que merecem ser mencionados: o comportamental e o estrutural.O significado comportamental representa o fato de que você sabe o que é descrevendo como ele age. Esta é a base do chamado "duck typing", numa tradução literal: "tipagem de pato": se algo age como um pato, é um pato (https://osantana.me/duck-typing/).
O significado estrutural identifica o tipo da coisa, observando sua estrutura interna. Portanto, duas coisas que atuam da mesma maneira, mas são internamente diferentes, são de tipos diferentes.
Ambos os pontos de vista podem ser válidos e diferentes linguagens podem implementar e enfatizar um significado ou outro, e até mesmo ambos.
Jogos de Classes
Os objetos em Python podem ser construídos descrevendo sua estrutura através de uma classe . Uma classe é a representação de programação de um objeto genérico, como "um livro", "um carro", "uma porta": quando falo sobre "uma porta", todos podem entender o que estou dizendo, sem a necessidade de referir para uma porta específica na sala.Em Python, o tipo de objeto é representado pela classe utilizada para construir o objeto: isto é, em Python o tipo de palavra tem o mesmo significado da classe de palavras .
Por exemplo, uma das classes internas de Python é int, que representa um número inteiro
In [8]:
a = 6
a
Out[8]:
In [9]:
print(type(a))
In [10]:
print(a.__class__)
Como você pode ver, a função incorporada
Depois de ter uma classe, você pode instanciá-la para obter um objeto concreto (uma instância) desse tipo, ou seja, um objeto construído de acordo com a estrutura dessa classe. A sintaxe Python para instanciar uma classe é a mesma de uma chamada de função.
type() retorna o conteúdo do atributo mágico class (a magia aqui significa que seu valor é gerenciado internamente pelo próprio Python). O tipo da variável a, ou sua classe, é int. (Esta é uma descrição muito imprecisa de um tópico bastante complexo, então lembre-se de que, no momento, estamos apenas arranhando a superfície).Depois de ter uma classe, você pode instanciá-la para obter um objeto concreto (uma instância) desse tipo, ou seja, um objeto construído de acordo com a estrutura dessa classe. A sintaxe Python para instanciar uma classe é a mesma de uma chamada de função.
In [11]:
b = int()
print(type(b))
Ao criar uma instância, você pode passar, de acordo com a definição da classe, alguns valores para inicializá-la.
In [12]:
b = int()
b
Out[12]:
In [13]:
c = int(7)
c
Out[13]:
Neste exemplo, a classe
Vamos escrever uma classe que represente uma porta, para combinar os exemplos procedurais da primeira seção.
int cria um número inteiro com o valor 0 quando é chamada sem argumentos, caso contrário, usa o argumento dado para inicializar o objeto recém-criado.Vamos escrever uma classe que represente uma porta, para combinar os exemplos procedurais da primeira seção.
In [14]:
class Porta:
def __init__(self, numero, status):
self.numero = numero
self.status = status
def abrir(self):
self.status = 'aberta'
def fechar(self):
self.status = 'fechada'
A palavra chave
Os métodos de uma classe devem aceitar como primeiro argumento um valor especial chamado
A classe pode receber um método especial chamado
As variáveis
Como você pode ver, o método
A classe pode ser usada para criar um objeto concreto.
class define uma nova classe chamada Porta; todo o código indentado abaixo de class faz parte desta classe. As funções que você escreve dentro do objeto são chamadas de métodos e não diferem de uma função padrão; a nomenclatura muda apenas para destacar o fato de que essas funções agora fazem parte de um objeto.Os métodos de uma classe devem aceitar como primeiro argumento um valor especial chamado
self(o nome é uma convenção, mas sempre deve ser usado).A classe pode receber um método especial chamado
__init__() que é executado quando a classe é instanciada, recebendo os argumentos passados quando esta é chamada; é o que normalmente, no contexto OOP, chama-se de construtor. Vale observar que o método __init__() não é a única parte desse mecanismo em Python.As variáveis
self.numero e self.status são chamadas de atributos do objeto. Em Python, os métodos e os atributos são ambos membros do objeto e são acessíveis com a sintaxe ponto (porta.numero); a diferença entre atributos e métodos é que este último pode ser chamado (porta.abrir()).Como você pode ver, o método
__init__() deve criar e inicializar os atributos, uma vez que eles não são declarados em nenhum outro lugar. Isso é muito importante em Python e está estritamente ligado à forma como a linguagem lida com os tipos de variáveis. Eu detalharei esses conceitos ao lidar com o polimorfismo em uma postagem posterior.A classe pode ser usada para criar um objeto concreto.
In [15]:
porta1 = Porta(1, 'fechada')
print(type(porta1))
In [16]:
porta1.numero
Out[16]:
In [17]:
porta1.status
Out[17]:
Agora
Para chamar um método de um objeto, isto é, executar uma das suas funções internas, você apenas acessa como um atributo com a sintaxe ponto e o chama como qualquer função.
porta1 uma instância da classe Porta; type() retorna a classe __main__.Porta já que a classe foi definida diretamente no shell interativo, que está no módulo principal atual.Para chamar um método de um objeto, isto é, executar uma das suas funções internas, você apenas acessa como um atributo com a sintaxe ponto e o chama como qualquer função.
In [18]:
porta1.abrir()
porta1.numero
Out[18]:
In [19]:
porta1.status
Out[19]:
Nesse caso, o método
Continua...
abrir() da instância porta1 foi chamado. Nenhum argumento foi passado para ele, mas se você rever a declaração da classe, pode verificar que foi configurado para receber um argumento (self). Quando você chama um método de uma instância, o Python passa automaticamente a instância para o método como primeiro argumento.Mais sobre self explícito em Python: http://wiki.python.org.br/SignificadoDoSelf.Você pode criar quantas instâncias forem necessárias e elas não estarão completamente relacionadas. Ou seja, as alterações que você faz em uma instância não refletem em outra instância da mesma classe.
Recapitulando
Os objetos são descritos por uma classe, que pode gerar uma ou mais instâncias não relacionadas entre si. Uma classe contém métodos, que são funções, e eles aceitam pelo menos um argumento chamadoself, que é a instância atual na qual o método foi chamado. Um método especial, init() lida com a inicialização do objeto, definindo o valor inicial dos atributos.Curiosidades dos títulos
Os títulos desta seção provêm dos seguintes filmes: De volta para o Futuro (1985), Nosso querido Bob? (1991), Jogos de Guera (1983) .Fontes
Você encontrará uma grande quantidade de documentação nesta postagem Reddit. A maioria das informações contidas nesta série provêm dessas fontes.Jupyter Notebook
Jupyter Notebook contendo o código utilizado neste post: https://github.com/FranciscoACLima/python3_oop.Continua...
Francisco AC Lima.

