Programação Orientada a Objetos
Em todos os programas que escrevemos até agora, criamos nosso programa em torno de funções, ou seja, blocos de instruções que manipulam dados. Isso é chamado de orientado a procedimentos modo de programação. Existe outra maneira de organizar seu programa, que é combinar dados e funcionalidades e envolvê-lo em algo chamado objeto. Isso é chamado de objeto orientado paradigma de programação. Na maioria das vezes, você pode usar a programação processual, mas ao escrever grandes programas ou ter um problema mais adequado a este método, você pode usar técnicas de programação orientadas a objetos.
Classes e objetos são os dois aspectos principais da programação orientada a objetos. Uma classe cria um novo tipo onde objetos são instancias da classe. Uma analogia é que você pode ter variáveis de tipo int
que se traduz em dizer que as variáveis que armazenam inteiros são variáveis que são instâncias (objetos) do int
classe.
Nota para programadores de linguagem estática
Note que mesmo inteiros são tratados como objetos (do
int
classe). Isso é diferente de C ++ e Java (antes da versão 1.5) onde os números inteiros são tipos nativos primitivos.Veja o
help(int)
para mais detalhes na classe.Os programadores C # e Java 1.5 encontrarão isso semelhante ao boxe e unboxing conceito.
Os objetos podem armazenar dados usando variáveis comuns que pertencem para o objeto. As variáveis que pertencem a um objeto ou classe são referidas como fields. Os objetos também podem ter funcionalidade usando funções que pertencem para uma aula. Tais funções são chamadas métodos da classe. Esta terminologia é importante porque nos ajuda a diferenciar entre funções e variáveis independentes e aquelas que pertencem a uma classe ou objeto. Coletivamente, os fields e métodos podem ser referidos como os atributos da classe.
Os fields são de dois tipos: eles podem pertencer a cada instância / objeto da classe ou podem pertencer à própria classe. Eles são chamados variáveis de instância e variáveis de classe respectivamente.
Uma classe é criada usando a palavra-chave class
. Os fields e métodos da classe estão listados em um bloco recuado.
A self
Os métodos de classe têm apenas uma diferença específica das funções comuns - eles devem ter um primeiro nome extra que deve ser adicionado ao início da lista de parâmetros, mas você não Dê um valor para este parâmetro quando você chama o método, o Python irá fornecê-lo. Esta variável particular refere-se ao objeto em si, e, por convenção, é dado o nome self
.
Embora, você pode dar qualquer nome para este parâmetro, é fortemente recomendado que você use o nome self
- qualquer outro nome é definitivamente desaprovado. Existem muitas vantagens em usar um nome padrão - qualquer leitor do seu programa irá imediatamente reconhecê-lo e até IDEs especializados (Ambientes de Desenvolvimento Integrados) podem ajudá-lo se você usar self
.
Observação para programadores C ++ / Java / C #
A
self
em Python é equivalente aothis
ponteiro em C ++ ethis
referência em Java e C #.
Você deve estar se perguntando como Python dá o valor para self
e por que você não precisa dar um valor para isso. Um exemplo deixará isso claro. Diga que você tenha uma classe chamada MyClass
e uma instância desta classe chamou myobject
. Quando você chama um método desse objeto como myobject.method(arg1, arg2)
, isso é convertido automaticamente pela Python em MyClass.method(myobject, arg1, arg2)
- isso é tudo especial self
é sobre.
Isso também significa que se você possui um método que não leva argumentos, então você ainda precisa ter um argumento - o self
.
Classes
A classe mais simples possível é mostrada no exemplo a seguir (salvar como oop_simplestclass.py
).
class Person:
pass # An empty block
p = Person()
print(p)
Saída:
$ python oop_simplestclass.py
<__main__.Person instance at 0x10171f518>
Como funciona
Criamos uma nova classe usando o class
declaração e o nome da classe. Isto é seguido por um bloco recuado de declarações que formam o corpo da classe. Nesse caso, temos um bloco vazio que é indicado usando o pass
declaração.
Em seguida, criamos um objeto / instância dessa classe usando o nome da classe seguido por um par de parênteses. (Nós aprenderemos mais sobre instanciação na próxima seção). Para a nossa verificação, confirmamos o tipo da variável simplesmente imprimindo. Nos diz que temos uma instância da Person
classe no __main__
módulo.
Observe que o endereço da memória do computador onde seu objeto está armazenado também é impresso. O endereço terá um valor diferente no seu computador, uma vez que o Python pode armazenar o objeto onde quer que encontre espaço.
Métodos
Já discutimos que as classes / objetos podem ter métodos como funções, exceto que temos um extra self
variável. Agora vamos ver um exemplo (salvar como oop_method.py
).
class Person:
def say_hi(self):
print('Hello, how are you?')
p = Person()
p.say_hi()
# The previous 2 lines can also be written as
# Person().say_hi()
Saída:
$ python oop_method.py
Hello, how are you?
Como funciona
Aqui vemos o self
em ação. Observe que o say_hi
método não possui parâmetros, mas ainda possui self
na definição da função.
O __init__
método
Existem muitos nomes de métodos que têm significado especial nas classes Python. Veremos o significado da __init__
método agora.
O __init__
método é executado assim que um objeto de uma classe é instanciado (ou seja, criado). O método é útil para fazer qualquer inicialização (ou seja, passando valores iniciais para o seu objeto) que você deseja fazer com seu objeto. Observe os sublinhados duplos tanto no início quanto no final do nome.
Exemplo (salvar como oop_init.py
):
class Person:
def __init__(self, name):
self.name = name
def say_hi(self):
print('Hello, my name is', self.name)
p = Person('Swaroop')
p.say_hi()
# The previous 2 lines can also be written as
# Person().say_hi()
Saída:
$ python oop_init.py
Hello, my name is Swaroop
Como funciona
Aqui, definimos o __init__
Método como tomar um parâmetro name
(juntamente com o habitual self
). Aqui, nós apenas criamos um novo campo também chamado name
. Observe que estas são duas variáveis diferentes, embora ambas sejam chamadas de 'nome'. Não há problema porque a notação pontilhada self.name
significa que há algo chamado "nome" que é parte do objeto chamado "auto" e o outro name
é uma variável local. Como nós explicitamente indicamos qual nome nos referimos, não há confusão.
Ao criar nova instância p
, da classe Person
, nós fazemos isso usando o nome da classe, seguido pelos argumentos entre parênteses: p = Person ('Swaroop').
Nós não chamamos explicitamente o __init__
método.
Este é o significado especial deste método.
Agora, podemos usar o self.name
campo em nossos métodos que é demonstrado no say_hi
método.
Variáveis de Classe e Objeto
Nós já discutimos a funcionalidade parte das classes e objetos (ou seja, métodos), agora vamos aprender sobre a parte de dados. A parte de dados, ou seja, fields, são apenas variáveis comuns que são bound ao namespaces das aulas e dos objetos. Isso significa que esses nomes são válidos somente no contexto dessas classes e objetos. É por isso que eles são chamados name spaces.
Existem dois tipos de fields - variáveis de classe e variáveis de objeto que são classificadas de acordo com a classe ou o objeto owns as variáveis, respectivamente.
Variáveis de classe são compartilhados - eles podem ser acessados por todas as instâncias dessa classe. Existe apenas uma cópia da variável de classe e quando qualquer objeto faz uma alteração para uma variável de classe, essa alteração será vista por todas as outras instâncias.
Variáveis de objeto são de propriedade de cada objeto / instância individual da classe. Nesse caso, cada objeto tem sua própria cópia do campo, ou seja, eles não são compartilhados e não estão relacionados de forma alguma ao campo pelo mesmo nome em uma instância diferente. Um exemplo tornará isso fácil de entender (salvo como oop_objvar.py
):
class Robot:
"""Represents a robot, with a name."""
# A class variable, counting the number of robots
population = 0
def __init__(self, name):
"""Initializes the data."""
self.name = name
print("(Initializing {})".format(self.name))
# When this person is created, the robot
# adds to the population
Robot.population += 1
def die(self):
"""I am dying."""
print("{} is being destroyed!".format(self.name))
Robot.population -= 1
if Robot.population == 0:
print("{} was the last one.".format(self.name))
else:
print("There are still {:d} robots working.".format(
Robot.population))
def say_hi(self):
"""Greeting by the robot.
Yeah, they can do that."""
print("Greetings, my masters call me {}.".format(self.name))
@classmethod
def how_many(cls):
"""Prints the current population."""
print("We have {:d} robots.".format(cls.population))
droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()
droid2 = Robot("C-3PO")
droid2.say_hi()
Robot.how_many()
print("\nRobots can do some work here.\n")
print("Robots have finished their work. So let's destroy them.")
droid1.die()
droid2.die()
Robot.how_many()
Saída:
$ python oop_objvar.py
(Initializing R2-D2)
Greetings, my masters call me R2-D2.
We have 1 robots.
(Initializing C-3PO)
Greetings, my masters call me C-3PO.
We have 2 robots.
Robots can do some work here.
Robots have finished their work. So let's destroy them.
R2-D2 is being destroyed!
There are still 1 robots working.
C-3PO is being destroyed!
C-3PO was the last one.
We have 0 robots.
Como funciona
Este é um exemplo longo, mas ajuda a demonstrar a natureza das variáveis de classe e objeto. Aqui, population
pertence aos Robot
classe e, portanto, é uma variável de classe. O name
variável pertence ao objeto (é atribuído usando self
) e, portanto, é uma variável de objeto.
Assim, nos referimos à population
variável de classe como Robot.population
e não como self.population
. Referimo-nos à variável do objeto name
usando self.name
notação nos métodos desse objeto. Lembre-se desta diferença simples entre variáveis de classe e objeto. Observe também que uma variável de objeto com o mesmo nome de uma variável de classe esconde a variável de classe!
Ao invés de Robot.population
, nós também poderíamos ter usado self.__class__.population
porque cada objeto se refere à sua classe através do self.__class__
atributo.
A how_many
é realmente um método que pertence à classe e não ao objeto. Isso significa que podemos defini-lo como um classmethod
ou uma staticmethod
dependendo se precisamos saber de qual classe fazemos parte. Como nos referimos a uma variável de classe, vamos usar classmethod
.
Marcamos o how_many
método como método de classe usando um decorador.
Os decoradores podem ser imaginados como um atalho para chamar uma função de wrapper (ou seja, uma função que "envolve" em torno de outra função para que possa fazer algo antes ou depois da função interna), aplicando assim a @classmethod
decorador é o mesmo que chamar:
how_many = classmethod(how_many)
Observe que o __init__
método é usado para inicializar o Robot
instância com um nome. Neste método, aumentamos a population
contagem por 1 desde que temos um robô mais sendo adicionado. Observe também que os valores de self.name
é específico para cada objeto que indica a natureza das variáveis de objeto.
Lembre-se de que você deve se referir às variáveis e métodos do mesmo objeto usando o self
somente. Isso é chamado de referência de atributo.
Neste programa, também vemos o uso de docstrings para aulas, bem como métodos. Podemos acessar a classe docstring em tempo de execução usando Robot.__doc__
e o método docstring como Robot.say_hi.__doc__
No die
método, simplesmente diminuímos o Robot.population
contagem por 1.
Todos os alunos são públicos. Uma exceção: se você usar membros de dados com nomes usando o prefixo de sublinhado duplo assim como __privatevar
, Python usa nome-mangling para efetivamente torná-lo uma variável privada.
Assim, a convenção seguida é que qualquer variável a ser usada somente dentro da classe ou objeto deve começar com um sublinhado e todos os outros nomes são públicos e podem ser usados por outras classes / objetos. Lembre-se que esta é apenas uma convenção e não é aplicada pela Python (exceto pelo prefixo de sublinhado duplo).
Observação para programadores C ++ / Java / C #
Todos os membros da turma (incluindo os membros dos dados) são publicos e todos os métodos são virtuais em Python.
Herança
Um dos principais benefícios da programação orientada a objetos é reuso de código e uma das formas como isso é alcançado através da herança mecanismo. A herança pode ser melhor imaginada como implementação de um tipo e subtipo relação entre as aulas.
Suponha que você queira escrever um programa que tenha que acompanhar os professores e alunos de uma faculdade. Eles têm algumas características comuns, como nome, idade e endereço. Eles também têm características específicas, como salário, cursos e folhas para professores e marcas e taxas para estudantes.
Você pode criar duas classes independentes para cada tipo e processá-las, mas adicionar uma nova característica comum significaria adicionar a ambas as classes independentes. Isso rapidamente se torna pesado.
Uma maneira melhor seria criar uma classe comum chamada SchoolMember
e depois ter as aulas de professor e aluno herdar desta classe, ou seja, eles se tornarão sub-tipos desse tipo (classe) e então podemos adicionar características específicas a esses subtipos.
Existem muitas vantagens para essa abordagem. Se adicionarmos / alterar qualquer funcionalidade em SchoolMember
, Isso também é refletido nos subtipos. Por exemplo, você pode adicionar um novo campo de cartão de identificação para professores e alunos simplesmente adicionando-o à classe SchoolMember. No entanto, as alterações nos subtipos não afetam outros subtipos. Outra vantagem é que você pode se referir a um objeto de professor ou aluno como um SchoolMember
objeto que pode ser útil em algumas situações, como a contagem do número de membros da escola. Isso é chamado polimorfismo onde um subtipo pode ser substituído em qualquer situação em que um tipo pai seja esperado, isto é, o objeto pode ser tratado como uma instância da classe pai.
Observe também que reutilizamos o código da classe dos pais e não precisamos repeti-lo nas diferentes classes, como teríamos que acontecer no caso de termos usado classes independentes.
A SchoolMember
classe nesta situação é conhecida como a base class ou a superclass. A Teacher
e Student
as classes são chamadas de classes derivadas ou subclasses.
Agora vamos ver este exemplo como um programa (salvar como oop_subclass.py
):
class SchoolMember:
'''Represents any school member.'''
def __init__(self, name, age):
self.name = name
self.age = age
print('(Initialized SchoolMember: {})'.format(self.name))
def tell(self):
'''Tell my details.'''
print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")
class Teacher(SchoolMember):
'''Represents a teacher.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print('(Initialized Teacher: {})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Salary: "{:d}"'.format(self.salary))
class Student(SchoolMember):
'''Represents a student.'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print('(Initialized Student: {})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Marks: "{:d}"'.format(self.marks))
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)
# prints a blank line
print()
members = [t, s]
for member in members:
# Works for both Teachers and Students
member.tell()
Output:
$ python oop_subclass.py
(Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)
Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
Name:"Swaroop" Age:"25" Marks: "75"
Como funciona
Para usar a herança, especificamos os nomes das classes base em uma tupla seguindo o nome da classe na definição da classe (por exemplo, class Teacher(SchoolMember)
). Em seguida, observamos que o __init__
método da classe base é explicitamente chamado usando o self
variável para que possamos inicializar a parte da classe base de uma instância na subclasse. Isto é muito importante para lembrar - Como estamos definindo um __init__
método em Teacher
e Student
subclasses, Python não liga automaticamente o construtor da classe base SchoolMember
, você precisa chamá-lo explicitamente você mesmo.
Em contraste, se não definimos um __init__
método em uma subclasse, o Python chamará o construtor da classe base automaticamente.
Embora pudéssemos tratar casos de Teacher
ou Student
como gostaríamos de uma instância de SchoolMember
e acessar o tell
método de SchoolMember
simplesmente digitando Teacher.tell
ou Student.tell
, em vez disso, definimos outro tell
método em cada subclasse (usando o tell
método de SchoolMember
para parte dela) para adaptar-se a essa subclasse. Porque fizemos isso, quando escrevemos Teacher.tell
Python usa o tell
método para essa subclasse versus a superclasse. No entanto, se não tivéssemos tell
método na subclasse, o Python usaria o tell
método na superclasse. Python sempre começa a procurar métodos no tipo de subclasse real primeiro e, se não encontrar nada, ele começa a olhar para os métodos nas classes base da subclasse, um por um na ordem em que são especificados na tupla (aqui só nós tem 1 classe base, mas você pode ter várias classes base) na definição da classe.
Uma nota sobre terminologia - se mais de uma classe estiver listada na tupla de herança, então ela é chamada herança múltipla.
O end
parâmetro é usado no print
Funciona na superclasse tell()
método para imprimir uma linha e permitir que a próxima impressão continue na mesma linha. Este é um truque para fazer print
não imprima um \n
(newline) símbolo no final da impressão.
Resumo
Nós já exploramos os vários aspectos das classes e objetos, bem como as várias terminologias associadas a ele. Nós também vimos os benefícios e as armadilhas da programação orientada a objetos. O Python é altamente orientado a objetos e a compreensão desses conceitos com cuidado irá ajudá-lo muito a longo prazo.
Em seguida, aprenderemos como lidar com a entrada / saída e como acessar arquivos em Python.