theme |
---|
solarized |
Leonardo Rochael Almeida
23-Outubro-2022
Note:
Intro: 5 min.
Lançar IPython em %doctest_mode
Lançar x11vnc
Lançar Remote Desktop Viewer
Note:
Trabalho com Python há 21 anos.
Meu primeiro emprego com Python foi tendo o Luciano Ramalho como chefe.
E tive a honra de revisar tanto a 1ª quanto a 2ª ed. do Fluent Python.
E fui vítima da maldição do conhecimento.
- Métodos normais:
- Como declarar:
def metodo(self):
- Como usar:
objeto.metodo()
- Como declarar:
- Métodos especiais
- Como declarar:
def __str__(self):
- Como usar:
print(objeto)
- Como declarar:
Note:
Método normal é o que você acessa com "pontinho".
Método especial é um que quem normalmente acessa é o Python para fazer algo especial com uma instância da sua classe.
Note:
Fonte: Fluent Python Second Edition
Por exemplo, esses são todos os métodos especiais que sua classe pode implementar, e permitem que a instância da classe participe de operações com operadores matemáticos, mais, vezes, etc.
Note:
Fonte: Fluent Python Second Edition
Já esses métodos especiais são todos os outros que não tem a ver com participar de operações.
Tem métodos especiais para sua classe ser chamada como se fosse uma função,
indexada como se fosse uma lista ou dicionário, fornecer um comprimento com
len()
, especificar sua representação no console etc.
Note:
Mostrar slides/code/slide0_methods.py
from slide0_methods import *
m1 = MinhaClasse()
m2 = MinhaSubClasse()
m1.dobrar()
m1.x = 7
m1.dobrar()
m2.dobrar()
m1
m2
def __call__(self, other):
return self.x + other
MinhaClasse.__call__ = __call__
m2(7)
(e atributos normais)
- instância
- classe
- superclasses
(métodos "dunder": __...__
)
instânciaNÃO!- classe
- superclasses
Em Python declarações de função e de classe "acontecem" em tempo de execução.
Note:
Classes são criadas em tempo de execução,
Mas imports só "rodam" o módulo uma vez.
Demonstrar com prints por todos os lados:
slides/code/slide1_runtime.py
Imagem © Luciano Ramalho, usada com permissão
Classes são valores também!
Note:
Em Python, todas as coisas declaradas tem variáveis atribuídas, inclusive funções e classes!
Classes (e funções) podem ser atribuídas a variáveis, listas e dicionários.
Demonstrar sobrescrever as variáveis nas quais as classes foram declaradas, e instanciar as classes através das variáveis nas quais foramm salvas.
a = [1, 2, 3]
b = a
b.append(4)
b
a
Posso atribuir classes a outras variáveis
MinhaClasse2 = MinhaClasse
instancia2 = MinhaClasse2()
MinhaClasse = None
instancia = MinhaClasse()
Posso colocar classes em listas, ou colocá-las em dicionários
Inclusive, o conteúdo de módulos importados fica em um dicionário:
slide1_runtime.__dict__.keys()
{key: value for key, value in slide1_runtime.__dict__.items() if not key.startswith('__')}
Assim como o conteúdo de classes e instâncias:
m1.__dict__
MinhaSubClasse.__dict__
Como meu amigo Lalo Martins diria:
Python é feito apenas de dicionários e toneladas de açúcar sintático
class Pato:
...
- Gerar uma classe
- Atribuir à classe uma variável
- Com mesmo nome da classe
Note:
class
não é uma "declaração". É um comando estruturado.
As mesmas duas responsabilidades valem para def
e funções.
O que significa que dá pra criar classes dentro de funções.
E também é possível criar funções dentro de funções.
Todos os valores têm uma classe
- inclusive classes!
Note:
Demonstrar obj.__class__
, type(obj)
e isinstance(obj, class)
pato.__class__
type(pato)
pato.__class__ is type(pato)
pato.__class__ is slide1_runtime.Pato
- Criando classes dinamicamente
Note:
demonstrar slides/code/slide5_dynamic_class.py
from slide5_dynamic_class import *
m3 = MinhaSubClasse()
m3.dobrar()
m3.somar()
MinhaSubClasse.__class__
MinhaOutraSubClasse = meu_gerador_de_subclasse(27)
m4 = MinhaOutraSubClasse()
m4.dobrar()
m4.somar()
MinhaOutraSubClasse.__bases__
MinhaOutraSubClasse.__name__
MinhaOutraSubClasse.__class__
MinhaSubClasseMaisDinamica = type(
'MinhaSubClasseMaisDinamica', # o nome da classe
(MinhaClasse, MeuMixin), # superclasses
{'x': 27}, # "namespace" da classe
)
# Inclusive com métodos
def __init__(self, x):
self.x = x
MinhaSubClasseRealmenteDinamica = type(
'MinhaSubClasseRealmenteDinamica',
(MinhaClasse, MeuMixin),
{'__init__': __init__},
)
Para criar uma instância, eu invoco a classe.
Para criar dinamicamente uma classe, eu invoco a classe da classe.
Note:
type
: a classe das classes por padrão- 1 parâmetro: retorna a classe de um objeto
- 3 parâmetros: cria uma nova classe
Note:
Metaclasse é o nome que damos à classe de uma classe
E type
é a metaclasse padrão de todas as classes
uma relação peculiar
Note:
Mas se type
é uma (meta)classe, de quem ela é subclasse?
E se object
, que é uma classe, também é uma instância, quem é a classe de
object
?
>>> type(object)
<class 'type'>
>>> type(type)
<class 'type'>
>>> type.__class__
<class 'type'>
>>> type.__bases__
(<class 'object'>,)
>>> object.__bases__
()
A relação entre object
e type
não pode ser construída em Python.
Faz parte da definição da linguagem.
- Herdando de
type
class better_repr_type(type):
...
Note:
Se type
é uma classe, posso herdar de type
?
slides/code/slide9_better_repr.py
from slide9_better_repr import *
MinhaSubClasseComRepr = better_repr_type(
'MinhaSubClasseComRepr', # nome
(MinhaClasse, MeuMixin), # bases
{'__init__': __init__}, # atributos / métodos
)
Note:
class MinhaClasse(Super, ..., metaclass=MinhaMetaClasse):
...
Note:
class MinhaSubClasseComRepr2(MinhaClasse, metaclass=better_repr_type):
def __init__(self, x):
self.x = x
- Dar métodos especiais às classes
__repr__
__getitem__
__(...)__
- Preparar o
namespace
(.__dict__
) de uma classe - Interceptar/registrar/customizar criação de classes
- Manipular métodos e atributos da classe durante criação
- Interceptar/customizar criação de instâncias
Note:
slides/code/slide12_walkthru.py
Walkthru completo do processo de declaração de uma classe
Debugar passo a passo no vs.code
Sobrescrever o __call__
da metaclasse pra retornar None
.
- Interceptar/customizar criação de instâncias
__call__
- Redundante com
__new__
da classe
- Redundante com
- Influenciar instâncias depois de criadas
- Fornecer atributos ou métodos normais às classes
- apenas métodos especiais!
Note:
MRO de atributos normais nunca passa pela metaclasse.
SuperClass.__init_subclass__()
- Invocado a cada subclasse declarada
- Mesmo nas subclasses indiretas
- Mas não na classe onde é declarada
- Invocado a cada subclasse declarada
Decoradores:
@decorador
class MinhaClasse:
...
- Um bom exemplo:
@dataclasses.dataclass
Note:
Um decorador recebe a classe já pronta, e têm a oportunidade de modificá-la, e até substituí-la, antes de retorná-la.
Um bom exemplo existente é @dataclass
, que cria métodos nas suas classes.
__class_getitem__
- Usado pelo Python para type hints
def print_steps(steps: list[str]): ...
Note:
Mostrar slides/code/slide20_meta_alternatives.py
from slide20_meta_alternatives import *
Anseriforme['Pato']
@verifica_anseriforme
class Cachorro(Pato):
def quack(self):
print("au, au!")
class Gato(Pato):
def quack(self):
print("miau!")
Anseriforme['Cachorro']
class MinhaSubClasse(SuperCls, palavra='Chave', numero=42):
...
- Mas é necessário consumi-las:
- Onde:
MetaClass.__new__()
SuperClass.__init_subclass__()
- Pois
object.__init_subclass__()
não as aceita.
- Onde:
Note:
E já que estamos falando de customização de classes, uma coisa interessante é
que classes aceitam keyword arguments além de metaclasse=
Devem ser consumidas no __new__
da metaclasse, ou no __init_subclass__
de
uma classe mãe.
Abrir slides/code/slide12_walkthru.py
ao lado de
slides/code/slide23_keywords.py
e debugar.
from sqlmodel import Field, SQLModel
class Hero(SQLModel, table=True):
id: int = Field(primary_key=True)
name: str
secret_name: str
age: int
Note:
A ausência do table=
indica que o ORM não deve criar
uma tabela para registros desta classe.
Mas subclasses de uma tal classe podem declarar table
.
- Tudo tem classes, inclusive as classes
- Metaclasses fornecem métodos especiais para classes
- E apenas métodos especiais
- Metaclasses não têm nenhuma influência sobre instâncias da classe
- busca de métodos/atributos não flui pra metaclasse
- Você pode criar (meta)classes pras suas classes
- Mas provavelmente não precisa
Note:
Tem gente que acha que Python é uma linguagem objetificante... Tudo é objeto!
Eu prefiro pensar que Python é uma linguagem muito classuda! Tudo tem muita classe!
Metaclasses ajudam a linguagem a evoluir (__init_subclass__
, __class_getitem__
).
Metaclasse é pra quem está fazendo frameworks, como SQLAlchemy ou Pydantic.
Se você se pergunta se precisa usar metaclasses, certeza que não precisa ;-)
Quem precisa sabe exatamente porque precisa.
from autostring import AutoString
class SaborDeSorvete(AutoString):
creme
morango
chocolate
GH: leorochael/2022-10-23-Talk-PythonBrasil-Metaclasses
https://www.linkedin.com/in/leorochael/
Telegram: @LeoRochael
email: [email protected]