Classes no Pattern Matching em 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.
Documentação oficial: