Atualizado: 04/11/2024

Classes no Pattern Matching - Python

O Python permite usar objetos de classes como padrões em pattern matching. Vamos considerar um exemplo:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age


def print_person(person):
    match person:
        case Person(name="Tom", age=37):
            print("Default Person")
        case Person(name=name, age=37):
            print(f"Name: {name}")
        case Person(name="Tom", age=age):
            print(f"Age: {age}")
        case Person(name=name, age=age):
            print(f"Name: {name}  Age: {age}")


print_person(Person("Tom", 37))  # Default Person
print_person(Person("Tom", 22))  # Age: 22
print_person(Person("Sam", 37))  # Name: Sam
print_person(Person("Bob", 41))  # Name: Bob  Age: 41

Aqui, definimos a classe Person, que através do construtor recebe valores para os atributos self.name e self.age.

A função print_person recebe o parâmetro person, que se presume ser um objeto da classe Person. Dentro da função, a estrutura match compara o valor do parâmetro person com uma série de padrões. Cada padrão é uma definição de Person, onde cada atributo é associado a um determinado valor. Por exemplo, o primeiro padrão define estritamente os valores de ambos os atributos:

case Person(name="Tom", age=37):
    print("Default Person")

Este padrão corresponde a um objeto Person se o atributo name desse objeto tiver o valor "Tom" e o atributo age tiver o valor 37.

É importante notar que este padrão não é uma chamada ao construtor Person. O padrão simplesmente estabelece como os atributos são correspondidos a valores.

O segundo padrão define estritamente o valor apenas para o atributo age:

case Person(name=name, age=37):
    print(f"Name: {name}")

Para corresponder a este padrão, o atributo age deve ser igual a 37. O atributo name pode ter qualquer valor, e esse valor é atribuído à variável name. A expressão name=name significa atributo_do_objeto=variável. Na chamada print(f"Name: {name}"), é impresso o valor da variável name, que recebeu o valor do atributo name.

Neste caso, tanto o atributo quanto a variável têm o mesmo nome, mas isso não é obrigatório, e poderíamos ter usado outro nome para a variável. Por exemplo:

case Person(name=person_name, age=37):  # O valor do atributo 'name' é atribuído à variável 'person_name'
    print(f"Name: {person_name}")

O terceiro padrão corresponde a um objeto Person cujo atributo name é igual a "Tom", e o valor do atributo age é atribuído à variável age:

case Person(name="Tom", age=age):
    print(f"Age: {age}")

No último padrão, os atributos name e age podem ter quaisquer valores, e esses valores são atribuídos a variáveis com os mesmos nomes:

case Person(name=name, age=age):
    print(f"Name: {name}  Age: {age}")

Não é obrigatório utilizar todos os atributos do objeto Person. Podemos também usar o padrão _ se precisarmos tratar casos que não correspondam a nenhum padrão:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age


def print_person(person):
    match person:
        case Person(name="Tom"):
            print("Default Person")
        case Person(name=person_name):  # Obtém apenas o atributo 'name'
            print(f"Name: {person_name}")
        case _:
            print("Not a Person")


print_person(Person("Tom", 37))  # Default Person
print_person(Person("Sam", 37))  # Name: Sam
print_person("Tom")              # Not a Person

Neste caso, o segundo padrão Person(name=person_name) corresponde a qualquer objeto Person, atribuindo o valor do atributo name à variável person_name.

O último padrão trata casos em que é passado um valor que não representa um objeto Person.

Uso de Múltiplos Valores

Podemos também definir um conjunto de valores que um atributo pode ter usando o operador | (pipe):

def print_person(person):
    match person:
        case Person(name="Tom" | "Tomas" | "Tommy"):
            print("Default Person")
        case Person(name=person_name):  # Obtém apenas o atributo 'name'
            print(f"Name: {person_name}")
        case _:
            print("Not a Person")


print_person(Person("Tom", 37))     # Default Person
print_person(Person("Tomas", 37))   # Default Person

Neste caso, o primeiro padrão corresponde a um objeto Person cujo atributo name tem um dos três valores: "Tom", "Tomas" ou "Tommy".

Também podemos definir valores alternativos para todo o padrão, incluindo objetos de outras classes:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age


class Student:
    def __init__(self, name):
        self.name = name


def print_person(person):
    match person:
        case Person(name="Tom") | Student(name="Tomas"):
            print("Default Person/Student")
        case Person(name=name) | Student(name=name):  # Obtém apenas o atributo 'name'
            print(f"Name: {name}")
        case _:
            print("Not a Person or Student")


print_person(Person("Tom", 37))     # Default Person/Student
print_person(Student("Tomas"))      # Default Person/Student

print_person(Person("Bob", 41))     # Name: Bob
print_person(Student("Mike"))       # Name: Mike

print_person("Tom")                 # Not a Person or Student

Aqui, o primeiro padrão case Person(name="Tom") | Student(name="Tomas") corresponde a qualquer objeto Person cujo atributo name é "Tom", ou a qualquer objeto Student cujo atributo name é "Tomas".

O segundo padrão case Person(name=name) | Student(name=name) corresponde a qualquer objeto Person ou Student, atribuindo o valor do atributo name à variável name.

Parâmetros Posicionais

Nos exemplos anteriores, os atributos foram especificados pelo nome, como em case Person(name="Tom", age=37). No entanto, se usarmos muitos padrões e precisarmos associar os atributos do objeto a determinados valores ou variáveis em cada um, mencionar constantemente os nomes dos atributos pode tornar o código extenso. O Python permite também usar parâmetros posicionais:

class Person:
    __match_args__ = ("name", "age")
    def __init__(self, name, age):
        self.name = name
        self.age = age


def print_person(person):
    match person:
        case Person("Tom", 37):
            print("Default Person")
        case Person(person_name, 37):
            print(f"Name: {person_name}")
        case Person("Tom", person_age):
            print(f"Age: {person_age}")
        case Person(person_name, person_age):
            print(f"Name: {person_name}  Age: {person_name}")


print_person(Person("Tom", 37))  # Default Person
print_person(Person("Tom", 22))  # Age: 22
print_person(Person("Sam", 37))  # Name: Sam
print_person(Person("Bob", 41))  # Name: Bob  Age: 41

Note no código da classe Person a definição:

__match_args__ = ("name", "age")

Isso informa ao Python que, ao especificar os atributos, o atributo name vem primeiro e o atributo age vem em segundo. Assim, nos padrões, não precisamos indicar o nome do atributo: case Person("Tom", 37): o Python irá associar os atributos aos valores ou variáveis com base em suas posições.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com