Introdução à programação
com Python em um contexto visual


Primeiros passos de orientação a objetos: usando a classe Slider

No começo do curso os principais exemplos de código que vimos se valem, em geral, de estratégias de programação sem “Orientação a Objetos”. Agora veremos como Python, assim como diversas outras linguagens, permite usar esta maneira de programar, pomposamente chamada de “paradigma de programação”: a Orientação a objetos(_object Orientation, por vezes abreviada OO). Python permite misturar elementos de diversos paradigmas.

Vamos começar apresentando os primeiros elementos e vocabulários da orientação a objetos.

Ideias principais e vocabulário

Classe (class), tipo (type) ou uma “categoria de objetos”

Tratando como equivalentes os termos classe e tipo, quando falamos sobre os valores manipulados pelo nosso programa é comum mencionarmos a categoria a que pertencem, isto é, de que tipo ou classe são. Os valores mais fundamentais, ditos primitivos, como os números que manipulamos, são em geral do tipo float(ponto flutuante) ou int(abreviação de integer, inteiros), já os textos são da classe str(abreviação de string, uma cadeia de caracteres). Estruturas como listas são objetos do tipo list e assim por diante. Você pode não ter visto mas o Processing nos entrega os dados de imagens carregadas do disco na forma de um objeto Py5Image. Cada tipo de objeto pode ter propriedades e funcionalidades específicas(atributos e métodos) que os tornam mais úteis em determinados contextos.

Note que fora os tipos embutidos(acima mencionamos int, float, str e list, mas há vários outros), as classes normalmente seguem a convenção de ter a primeira letra maiúscula no nome, com Slider que veremos mais à frente, e é especialmente recomendável seguir essa convençao para as classes que você criar.

Atributos (propriedades ou campos)

Objetos tem “valores ou propriedades” chamadas de atributos, que podem ser consultados usando a “sintaxe do ponto” (objeto.atributo). Por exemplo, quando carregamos uma imagem no Processing podemos consultar as dimensões dela nos atributos .width e .height:

img = load_image('a.png')  # uma imagem PNG na pasta /data/
w = img.width  # largura em pixels
h = img.height  # altura em pixels

Métodos (ou funções associadas aos objetos)

Objetos tem funções associadas, conhecidas como métodos, que podem ser invocadas com a “sintaxe do ponto” (objeto.metodo()). Uma lista em Python, por exemplo, possui diversos métodos e já vimos pelo menos um deles, o .append() que é chamado para incluir elementos na lista.

frutas = ['uva', 'banana']
frutas.append('kiwi')
print(frutas)  # ['uva', 'banana', 'kiwi']

Instanciar (criar uma nova instância de um objeto)

Fora casos especiais em que podemos criar objetos diretammente no código (como a lista de frutas que acabamos de ver) ou com uma função ajudante, no caso de load_image(nome_arquivo) que cria um objeto Py5Image, costumamos criar novos objetos chamando o nome da classe (usando os parênteses, como em uma chamada de funç, e isso pode ou não demandar argumentos. No exemplo que veremos a seguir vamos criar um slider.

s1 = Slider(0, 90, 50, 'tamanho')  # mínimo, máximo, valor_inicial, etiqueta

O que ficou de fora

Não vamos ver ainda neste momento em detalhes de como funciona a definição ou criação da classe(a parte que segue class Slider: ), que codifica como ela produz e inicializa os objetos ou como são definidos os métodos, nem trataremos do assunto mais avançado “herança” em que uma classe é baseada em outra, recebendo desta parte das suas características.

Exemplo de uso da classe Slider

Veja agora um exemplo comentado de como instanciar e usar objetos da classe Slider que vão servir de interface gráfica para modificar um desenho de uma àrvore.

Note que os objetos slider tem os métodos .position() para locá-los na tela depois de terem sido criados, e o método .update(), que chamaremos dentro da função draw() para fazer o duplo trabalho de desenhar o slider na tela e obter o valor indicado pelo slider naquele momento.



def setup():
    global s1, s2, s3
    global seed
    seed = int(random(1000))
    print(seed)
    size(500, 500)
    s1 = Slider(0, 90, 50, 'tamanho')   # instanciando o slider s1
    s1.position(20, 30)                 # posicionando s1 em x:20 y:30
    s2 = Slider(0, 180, 45, 'ângulo')
    s2.position(190, 30)
    s3 = Slider(0, 10, 0, 'variação aleatória')
    s3.position(360, 30)


def draw():
    global angulo, rndvar
    random_seed(seed)
    background(240, 240, 200)
    translate(250, 440)
    tamanho = s1.update()          # atualizando, desenha s1 na tela e obtem um valor
    angulo = radians(s2.update())
    rndvar = s3.update() / 10
    galho(tamanho)


def galho(tamanho):
    reducao = 0.75
    sw = tamanho / 10
    stroke_weight(sw)
    line(0, 0, 0, -tamanho)
    if tamanho > 5:
        push_matrix()
        translate(0, -tamanho)
        rotate(angulo)
        galho(tamanho * reducao - random(-sw, sw) * rndvar)
        rotate(-angulo * 2)
        galho(tamanho * reducao - random(-sw, sw) * rndvar)
        pop_matrix()

 # ...
 # Atenção: precisa colar aqui a definição da classe Slider que está mais abaixo nesta página.
 # ou colar em uma nova aba chamada slider.py e acrescentar `from slider
 # import Slider` no início do sketch

slider

Como é a definição da classe Slider? (a classe por dentro)

Atenção: o código abaixo faz parte do exemplo acima. Em projetos grandes, mais complexos, pode ser interessante separar as classes em outro arquivo para facilitar a manipulação e leitura do código. Para isso, crie um novo arquivo, que pode se chamar, por exemplo slider.py, vizinho ao seu arquivo principal. O comentário # PY5 IMPORTED MODE CODE é necessário neste caso, ele permite que as funções do py5 neste arquivo sejam reconhecidas pela ferramenta run_sketch ou pelo plug-in do THonny. Já no arquivo principal é preciso então usar a instrução from slider import Slider no começo do seu código. Se não quiser fazer nada disso, simplesmente cole o código com a definição da classe no final do seu arquivo principal, depois do código inicial com setup() e draw().

Veja uma primeira versão da classe Slider

# PY5 IMPORTED MODE CODE

class Slider:

    def __init__(self, low, high, default, label=''):
        self.low , self.high = low, high
        self.value = default
        self.label = label
        self.w, self.h = 120, 20
        self.position(25, 25)  # default position

    def position(self, x, y):
        self.x, self.y = x, y
        self.rectx = self.x + remap(self.value, self.low, self.high, 0, self.w)

    def update(self):
        if is_mouse_pressed and dist(mouse_x, mouse_y, self.rectx, self.y) < self.h:
            self.rectx = mouse_x
        self.rectx = constrain(self.rectx, self.x, self.x + self.w)
        self.value = remap(self.rectx, self.x, self.x + self.w, self.low, self.high)
        self.display()
        return self.value
        
    def display(self):
        push()  # combina pushMatrix() and pushStyle()
        reset_matrix()
#         camera()  # acrescente caso o sketch seja com P3D!
        rect_mode(CENTER)
        stroke_weight(4)
        stroke(200)
        line(self.x, self.y, self.x + self.w, self.y)
        stroke_weight(1)
        stroke(0)
        line(self.x + self.w / 24, self.y, self.x + self.w - self.w / 24, self.y)
        fill(255)
        stroke(0)
        rect(self.rectx, self.y, self.w / 12, self.h)
        fill(0)
        text_align(CENTER, CENTER)
        text("{:.1f}".format(self.value), self.rectx, self.y + self.h)
        text(self.label, self.x + self.w / 2, self.y - self.h)
        pop()  # popStyle() and popMatrix

Páginas relacionadas

Extra: Uma segunda versão da classe Slider

Acrescentando algumas funcionalidades extra e comentários à classe Slider.

# PY5 IMPORTED MODE CODE

class Slider:

    template = "{:.1f}"  # para formatar como mostra o valor
    label_align = CENTER

    def __init__(self, low, high, default, label=''):
        """
        Slider needs range from low to high
        and and a default value. Label is optional.
        """
        self.low = low
        self.high = high
        self.value = default
        self.label = label
        self.w, self.h = 120, 20
        self.position(25, 25)  # Pos default

    def position(self, x, y):
        """Define as coordenadas na tela, e calcula rectx, pos. do 'handle'"""
        self.x = x
        self.y = y
        # the position of the rect you slide:
        self.rectx = self.x + remap(self.value, self.low, self.high, 0, self.w)

    def update(self):
        """Atualiza o slider e devolve o valor (self.value). Chama display()"""
        # is_mouse_pressed moves slider
        if is_mouse_pressed and dist(mouse_x, mouse_y, self.rectx, self.y) < self.h:
            self.rectx = mouse_x
        # constrain rectangle
        self.rectx = constrain(self.rectx, self.x, self.x + self.w)
        self.value = remap(self.rectx,
                         self.x, self.x + self.w,
                         self.low, self.high)
        self.display()
        return self.value
        
    def display(self):
        """Desenha o slider na tela, usando coordenadas sem transformar"""
        push()         # Combina pushMatrix() e pushStyle()
        reset_matrix()  # push(), seguido de resetMatrix() e camera() permitem...
        camera()       # .maldino@fediscience.org.. desenhar o slider no sistema de coordenadas original
        rect_mode(CENTER)
        # Linha cinza sob o slider
        stroke_weight(4)
        stroke(200)
        line(self.x, self.y, self.x + self.w, self.y)
        # O retângulo, elemento principal da interface do slider
        stroke_weight(1)
        stroke(0)
        fill(255)
        translate(0, 0, 1)
        rect(self.rectx, self.y, self.w / 12, self.h)
        # Mostra o valor (value) atual
        fill(0)
        text_size(10)
        text_align(CENTER, CENTER)
        text(self.template.format(self.value), self.rectx, self.y + self.h)
        # draw label
        if self.label_align == LEFT:
            text_align(self.label_align)
            text(self.label, self.x, self.y - self.h)
        else:
            text(self.label, self.x + self.w / 2, self.y - self.h)
        pop()  # equivale a popStyle() and popMatrix()