Se tens programado num estilo funcional, declarativo ou imperativo, passar o foco para a programação orientada a objetos (POO) pode parecer um pouco estranho.
Uma abordagem de POO pede ao programador que pense em modelar um problema como um ou mais objects que interagem entre si, mantêm estado e atuam sobre os dados.
Os objetos podem representar entidades do mundo real (como carros ou gatos) ou conceitos mais abstratos (como números inteiros, veículos ou mamíferos).
Cada objeto torna-se uma instância única na memória do computador e representa uma parte do modelo global.
As Classes são as definições de novos tipos de objetos, a partir das quais se criam novas instances de objetos.
Frequentemente juntam dados com código ou funções que operam sobre esses dados.
Neste sentido, as classes são esquemas ou conjuntos de instruções a partir dos quais se podem construir e usar muitos objetos de um tipo semelhante.
Um programa complexo pode ter muitas classes, cada uma a construir muitos tipos diferentes de objetos.
O processo de construir um objeto a partir de uma classe chama-se instantiation (ou criar uma instância da classe).
Uma definição de classe em Python é simples:
class MyClass:
# Class body goes here
Os campos de uma classe (também conhecidos como properties, attributes, data members ou variables) podem ser adicionados ao corpo da classe:
class MyClass:
number = 5
string = "Hello!"
Uma instância (objeto) de MyClass pode ser criada e associada a um nome ao chamar a classe (da mesma forma que se chama uma função):
>>> new_object = MyClass()
# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>
Os Class attributes são partilhados por todos os objetos (ou instâncias) criados a partir de uma classe e podem ser acedidos através da dot notation - um . colocado depois do nome do objeto e antes do nome do atributo:
>>> new_object = MyClass()
# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5
# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'
# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()
>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>
# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True
Os atributos de classe são definidos no corpo da própria classe, antes de quaisquer outros métodos. Pertencem à classe, o que lhes permite ser partilhados por todas as instâncias da classe. Uma vez que estes atributos são partilhados, o seu valor pode ser acedido e manipulado a partir da classe diretamente. Alterar o valor dos atributos de classe altera o valor para todos os objetos instanciados a partir da classe:
>>> obj_one = MyClass()
>>> obj_two = MyClass()
# Accessing a class attribute from an object.
>>> obj_two.number
5
# Accessing the class attribute from the class itself.
>>> MyClass.number
5
# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27
# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27
>>> obj_two.number
27
Ter um conjunto de objetos com dados sincronizados em todos os momentos não é particularmente útil.
Felizmente, os objetos criados a partir de uma classe podem ser personalizados com os seus próprios instance attributes (ou propriedades, variáveis ou campos de instância).
Como o nome sugere, os atributos de instância são exclusivos de cada objeto e podem ser modificados independentemente.
__init__()
O especial "dunder method" (abreviação de "double underscore method") __init__() é usado para personalizar instâncias de classe e pode ser usado para inicializar atributos ou propriedades de instância dos objetos.
Pelo seu papel na inicialização de atributos de instância, __init__() também é designado por class constructor ou initializer.
__init__() recebe um parâmetro obrigatório chamado self, que se refere ao objeto recém-inicializado ou criado.
Os dados para os atributos ou propriedades de instância podem depois ser passados como argumentos de __init__(), a seguir ao parâmetro self.
Abaixo, MyClass tem agora atributos de instância chamados location_x e location_y.
Como podes ver, os dois atributos foram atribuídos ao primeiro e ao segundo índices do argumento location (um tuplo) que foi passado a __init__().
Os atributos location_x e location_y de uma instância de classe serão agora inicializados quando instancias a classe e é criado um objeto:
class MyClass:
# These are class attributes, variables, or fields.
number = 5
string = "Hello!"
# This is the class "constructor", with a "location" parameter that is a tuple.
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
# Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))
# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))
# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1
>>> new_object_two.location_x
-8
Repara que só precisas de passar um argumento ao inicializar o MyClass acima. O Python trata de passar o self quando a classe é chamada.
Um method é uma function associada à própria classe (conhecida como método de classe, que será abordada num exercício posterior) ou a uma instância da classe (objeto).
Os métodos que operam sobre um objeto (instância) têm de ser definidos com self como primeiro parâmetro.
Podes depois definir os restantes parâmetros como farias para uma função "normal" ou não associada:
class MyClass:
number = 5
string = "Hello!"
# Class constructor.
def __init__(self, location):
# Instance properties
self.location_x = location[0]
self.location_y = location[1]
# Instance method. Note "self" as first parameter.
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
Tal como no acesso a atributos, chamar um método requer apenas colocar um . depois do nome do objeto e antes do nome do método.
O método chamado não precisa de uma cópia do objeto como primeiro parâmetro. O Python preenche o self automaticamente:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)
# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)
Podes aceder aos atributos de classe a partir de métodos de instância da mesma forma que o fazes fora da classe:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances from within an instance.
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5
>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6
Em exercícios de conceitos anteriores e em stubs de exercícios de prática, já viste a palavra-chave pass usada dentro do corpo das funções em vez do código real.
A palavra-chave pass é um marcador sintaticamente válido: evita que o Python lance um erro de sintaxe por causa de uma definição de função ou classe vazia.
Essencialmente, é uma forma de dizer ao intérprete de Python: "Não te preocupes! Eu vou pôr código aqui, só que ainda não o fiz."
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
# Stubbing or placholding the body of this method.
pass
A Ellen está a fazer um jogo em que o jogador tem de combater alienígenas.
Acabou de aprender Programação Orientada a Objetos (POO) e está ansiosa por tirar partido do que a utilização de classes pode oferecer ao seu programa.
Para grande satisfação da Ellen, ofereceste-te para ajudar e ela deu-te a tarefa de programar os alienígenas que o jogador tem de combater.
Define a classe Alien com um construtor que aceita dois parâmetros <x_coordinate> e <y_coordinate>, colocando-os nas variáveis de instância x_coordinate e y_coordinate.
Cada alienígena também começa com um nível de saúde de 3, por isso a variável health também deve ser inicializada.
>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3
Agora, cada alienígena deve conseguir acompanhar internamente a sua própria posição e saúde.
hit
A Ellen gostaria que a class Alien tivesse um método hit que diminui a saúde de um objeto alienígena em 1 sempre que é chamado.
Assim, pode simplesmente chamar <alien>.hit() em vez de ter de alterar manualmente a saúde de um alienígena.
Cabe-te a ti decidir se hit() leva os pontos de saúde até ou abaixo de zero.
>>> alien = Alien(0, 0)
# Initialized health value.
>>> alien.health
3
# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2
is_alive
Percebes que, se a saúde continuar a diminuir, em algum momento provavelmente chegará a 0 (ou até menos!).
Seria uma boa ideia acrescentar um método is_alive que a Ellen possa chamar rapidamente para verificar se o alienígena está... bem... vivo. 😉
<alien>.is_alive() deve devolver um Boolean.
>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False
teleport
No jogo da Ellen, os alienígenas têm a capacidade de se teletransportar!
Terás de escrever um método teleport que recebe novos valores x_coordinate e y_coordinate e altera as coordenadas do alienígena em conformidade.
>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4
collision_detection
Obviamente, se os alienígenas podem ser atingidos por algo, então precisam de conseguir detetar quando ocorreu uma colisão dessas.
No entanto, os algoritmos de deteção de colisões podem ser complicados, e ainda não sabes como implementar um.
A Ellen disse que o fará mais tarde, mas mesmo assim gostaria que o método collision_detection aparecesse na classe como um lembrete para desenvolver a funcionalidade.
Terá de receber uma variável de algum tipo (provavelmente outro objeto), mas isso é realmente tudo o que sabes.
Terás de garantir que colocar a definição do método na classe não causa erros quando é chamado:
>>> alien.collision_detection(other_object)
>>>
A Ellen voltou com um novo pedido para ti. Ela quer acompanhar quantos alienígenas foram criados ao longo do tempo de vida do jogo. Ela diz que tem algo a ver com o sistema de pontuação.
Por exemplo:
>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2
A Ellen adora o que fizeste até agora, mas tem mais um favor para te pedir.
Ela gostaria de uma função autónoma (fora da classe Alien()) que crie uma list de objetos Alien(), dada uma lista de posições (como tuples).
Por exemplo:
>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
... print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Inscreve-te no Exercism para aprenderes e dominares Python com 17 conceitos146 exercícios, e mentoria humana real, tudo grátis.