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 ao this ponteiro em C ++ e this 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.