WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
1/361/36
Códigos da apresentação:https://github.com/danilobellini/wta2017
WTA 2017Workshop de tecnologia adaptativa
WTA 2017Workshop de tecnologia adaptativa
Tutorial:Introspecção e
compilação tardia em Python
Tutorial:Introspecção e
compilação tardia em Python
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
2/362/36
Danilo J. S. BelliniDanilo J. S. Bellini
Engenheiro eletricista (Poli-USP)
Bacherel em música / compositor (ECA-USP)
Mestre em ciência da computação (IME-USP)
DesenvolvedorPython, shell script, …
Processamento de sinais
GIS (Geographic Information System)
FLOSS developerProjetos:
AudioLazy
Dose
…
Contribuições:IPython
watchdog
py.test
openModeller
…
https://github.com/danilobellini
https://bitbucket.org/danilobellini
https://br.linkedin.com/in/danilo-j-s-bellini-66a96310
https://ericstk.wordpress.com/2014/10/16
Vejam ta
mbém o
material
do tuto
rial
do WTA20
15!Veja
m também
o
material
do tuto
rial
do WTA20
15!
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
3/363/36
PythonPython
Linguagem multiparadigmaPossui recursos de metaprogramação
Científico, GUI, Web, games, GIS, scraping, “hackery”, …
Compilada e interpretada!
“Python is a programming language that lets you work quickly and integrate systems more effectively.”
https://www.python.orghttps://www.python.org
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
4/364/36
SumárioSumário
Dicionários
Objetos
Escopo / Closure
Compilação
AST
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
5/365/36
Parte 1Parte 1
DicionáriosDicionários
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
6/366/36
Dicionário / dictDicionário / dict
Pares chave-valor, mapeia cada chave em um valor
Permite aninhamento e reentrância
“Unhashable”, mas as chaves devem ser sempre “hashable”
In [1]: d = {"nome": "Danilo", ...: "twitter": "@danilobellini"}
In [2]: d["nome"]Out[2]: 'Danilo'
In [3]: del d["twitter"]
In [4]: len(d) # Tamanho (número de pares)Out[4]: 1
In [5]: d["sobrenome"] = "J. S. Bellini"
In [6]: d["sobrenome"] = d["sobrenome"].split()[-1]
In [7]: dOut[7]: {'nome': 'Danilo', 'sobrenome': 'Bellini'}
In [1]: d = {"nome": "Danilo", ...: "twitter": "@danilobellini"}
In [2]: d["nome"]Out[2]: 'Danilo'
In [3]: del d["twitter"]
In [4]: len(d) # Tamanho (número de pares)Out[4]: 1
In [5]: d["sobrenome"] = "J. S. Bellini"
In [6]: d["sobrenome"] = d["sobrenome"].split()[-1]
In [7]: dOut[7]: {'nome': 'Danilo', 'sobrenome': 'Bellini'}
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
7/367/36
Métodos e operadoresMétodos e operadores
O “dict” não possui ordenação!
collections.OrderedDict
Ordered != Sorted
As chaves são como um conjunto
Hash
Ordenação
Iteração nas chaveslist({1: 2}) == [1]
dict.items()
Itera por pares chave, valor
dict.keys()
Itera por chaves
dict.values()
Itera por valores
No Python 2, esses métodos devolvem listas ao invés de iteráveis tardios (mas há métodos equivalentes, com o prefixo “iter”)
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
8/368/36
Dunders que permitem criar objetos que se comportam como dicionários
“Operador []”
In [1]: class LogDict(dict): ...: ...: def __getitem__(self, key): ...: print("__getitem__: {}".format(locals())) ...: return super(LogDict, self).__getitem__(key) ...: ...: def __setitem__(self, key, value): ...: print("__setitem__: {}".format(locals())) ...: super(LogDict, self).__setitem__(key, value) ...: ...: def __delitem__(self, key): ...: print("__delitem__: {}".format(locals())) ...: super(LogDict, self).__delitem__(key)
In [2]: logd = LogDict({1: 1, 2: 2, 3: 3})
In [3]: del logd[1]__delitem__: {'self': {1: 1, 2: 2, 3: 3}, 'key': 1}
In [4]: logd[2] += logd[3]__getitem__: {'self': {2: 2, 3: 3}, 'key': 2}__getitem__: {'self': {2: 2, 3: 3}, 'key': 3}__setitem__: {'self': {2: 2, 3: 3}, 'key': 2, 'value': 5}
In [5]: logdOut[5]: __getitem__: {'self': {2: 5, 3: 3}, 'key': 2}__getitem__: {'self': {2: 5, 3: 3}, 'key': 3}{2: 5, 3: 3}
In [1]: class LogDict(dict): ...: ...: def __getitem__(self, key): ...: print("__getitem__: {}".format(locals())) ...: return super(LogDict, self).__getitem__(key) ...: ...: def __setitem__(self, key, value): ...: print("__setitem__: {}".format(locals())) ...: super(LogDict, self).__setitem__(key, value) ...: ...: def __delitem__(self, key): ...: print("__delitem__: {}".format(locals())) ...: super(LogDict, self).__delitem__(key)
In [2]: logd = LogDict({1: 1, 2: 2, 3: 3})
In [3]: del logd[1]__delitem__: {'self': {1: 1, 2: 2, 3: 3}, 'key': 1}
In [4]: logd[2] += logd[3]__getitem__: {'self': {2: 2, 3: 3}, 'key': 2}__getitem__: {'self': {2: 2, 3: 3}, 'key': 3}__setitem__: {'self': {2: 2, 3: 3}, 'key': 2, 'value': 5}
In [5]: logdOut[5]: __getitem__: {'self': {2: 5, 3: 3}, 'key': 2}__getitem__: {'self': {2: 5, 3: 3}, 'key': 3}{2: 5, 3: 3}
https://docs.python.org/reference/datamodel.html
__getitem____setitem____delitem__
__getitem____setitem____delitem__
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
9/369/36
__missing____missing__
Chaves que não pertencem ao dicionário
Fallback
Não altera o dicionário
In [1]: class FibDict(dict): ...: ...: def __missing__(self, key): ...: if key < 0: ...: raise KeyError(key) ...: return self[key - 1] + self[key - 2]
In [2]: fibd = FibDict({0: 0, 1: 1})
In [3]: fibd[2], fibd[3], fibd[4], fibd[5], fibd[6], fibd[7]Out[3]: (1, 2, 3, 5, 8, 13)
In [4]: fibdOut[4]: {0: 0, 1: 1}
In [5]: fibd[-1] [...]KeyError: -1
In [6]: 3 in fibdOut[6]: False
In [1]: class FibDict(dict): ...: ...: def __missing__(self, key): ...: if key < 0: ...: raise KeyError(key) ...: return self[key - 1] + self[key - 2]
In [2]: fibd = FibDict({0: 0, 1: 1})
In [3]: fibd[2], fibd[3], fibd[4], fibd[5], fibd[6], fibd[7]Out[3]: (1, 2, 3, 5, 8, 13)
In [4]: fibdOut[4]: {0: 0, 1: 1}
In [5]: fibd[-1] [...]KeyError: -1
In [6]: 3 in fibdOut[6]: False
Pode-se, dentro do __missing__:Armazenar o resultado (cache)
Modificar o próprio dicionário
Lançar exceção (e.g. KeyError)
…
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
10/3610/36
Parte 2Parte 2
ObjetosObjetos
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
11/3611/36
Dicionário da instânciaDicionário da instância
Em Python, tudo é objeto!
O namespace de atributos (dicionário da instância) pode ser coletado com o built-in “vars”
Acessa o conteúdo de obj.__dict__
Não aplicável em built-ins e objetos com __slots__
In [1]: class Usuário(object): ...: ...: def __init__(self, nome, twitter=None): ...: self.nome = nome ...: self.twitter = twitter
In [2]: obj = Usuário("Danilo", "@danilobellini")
In [3]: obj.nomeOut[3]: 'Danilo'
In [4]: vars(obj)Out[4]: {'nome': 'Danilo', 'twitter': '@danilobellini'}
In [5]: vars(obj) is obj.__dict__Out[5]: True
In [1]: class Usuário(object): ...: ...: def __init__(self, nome, twitter=None): ...: self.nome = nome ...: self.twitter = twitter
In [2]: obj = Usuário("Danilo", "@danilobellini")
In [3]: obj.nomeOut[3]: 'Danilo'
In [4]: vars(obj)Out[4]: {'nome': 'Danilo', 'twitter': '@danilobellini'}
In [5]: vars(obj) is obj.__dict__Out[5]: True
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
12/3612/36
In [6]: dir(obj)Out[6]:['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__',
In [6]: dir(obj)Out[6]:['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__',
In [7]: type(obj)Out[7]: __main__.Usuário
In [8]: vars(Usuário)Out[8]:mappingproxy({'__dict__': <attribute '__dict__' of 'Usuário' objects>, '__doc__': None, '__init__': <function __main__.Usuário.__init__>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Usuário' objects>})
In [9]: Usuário.mro()Out[9]: [__main__.Usuário, object]
In [7]: type(obj)Out[7]: __main__.Usuário
In [8]: vars(Usuário)Out[8]:mappingproxy({'__dict__': <attribute '__dict__' of 'Usuário' objects>, '__doc__': None, '__init__': <function __main__.Usuário.__init__>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Usuário' objects>})
In [9]: Usuário.mro()Out[9]: [__main__.Usuário, object]
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'nome', 'twitter']
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'nome', 'twitter']
Dicionário da classeDicionário da classeClasses também são objetos!
Métodos
A resolução de atributos incluem atributos das classes na MRO além dos que estão no dicionário da instância
Esse conjunto ampliado de nomes de atributos (incluindo métodos) pode ser obtido com o built-in “dir”
Não inclui os nomes dos atributos obtidos da metaclasse ou gerados via __getattr__/__getattribute__
O objeto lança AttributeError ao tentar acessar atributo inexistente
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
13/3613/36
__getattr__ é somente um fallback!
super() é super(__class__, self)
Python 3, apenas
In [1]: from IPython.lib.pretty import pprint
In [2]: class LogObj(object): ...: ...: def __getattr__(self, name): ...: print("__getattr__:"); pprint(locals()) ...: return name # Para não lançar AttributeError ...: ...: def __setattr__(self, name, value): ...: print("__setattr__:"); pprint(locals()) ...: super().__setattr__(name, value) ...: ...: def __delattr__(self, name): ...: print("__delattr__:"); pprint(locals()) ...: super().__delattr__(name)
In [3]: logobj = LogObj()
In [4]: logobj.a = 1__setattr__:{'__class__': __main__.LogObj, 'name': 'a', 'self': <__main__.LogObj at 0x...>, 'value': 1}
In [5]: logobj.b = 2__setattr__:{'__class__': __main__.LogObj, 'name': 'b', 'self': <__main__.LogObj at 0x...>, 'value': 2}
In [1]: from IPython.lib.pretty import pprint
In [2]: class LogObj(object): ...: ...: def __getattr__(self, name): ...: print("__getattr__:"); pprint(locals()) ...: return name # Para não lançar AttributeError ...: ...: def __setattr__(self, name, value): ...: print("__setattr__:"); pprint(locals()) ...: super().__setattr__(name, value) ...: ...: def __delattr__(self, name): ...: print("__delattr__:"); pprint(locals()) ...: super().__delattr__(name)
In [3]: logobj = LogObj()
In [4]: logobj.a = 1__setattr__:{'__class__': __main__.LogObj, 'name': 'a', 'self': <__main__.LogObj at 0x...>, 'value': 1}
In [5]: logobj.b = 2__setattr__:{'__class__': __main__.LogObj, 'name': 'b', 'self': <__main__.LogObj at 0x...>, 'value': 2}
__getattr____setattr____delattr__
__getattr____setattr____delattr__
In [6]: logobj.a += logobj.b__setattr__:{'__class__': __main__.LogObj, 'name': 'a', 'self': <__main__.LogObj at 0x...>, 'value': 3}
In [7]: del logobj.b__delattr__:{'__class__': __main__.LogObj, 'name': 'b', 'self': <__main__.LogObj at 0x...>}
In [8]: vars(logobj)Out[8]: {'a': 3}
In [6]: logobj.a += logobj.b__setattr__:{'__class__': __main__.LogObj, 'name': 'a', 'self': <__main__.LogObj at 0x...>, 'value': 3}
In [7]: del logobj.b__delattr__:{'__class__': __main__.LogObj, 'name': 'b', 'self': <__main__.LogObj at 0x...>}
In [8]: vars(logobj)Out[8]: {'a': 3}
In [9]: logobj.c__getattr__:{'name': 'c', 'self': <...>}Out[9]: 'c'
In [9]: logobj.c__getattr__:{'name': 'c', 'self': <...>}Out[9]: 'c'
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
14/3614/36
__getattribute____getattribute__In [1]: class GetAttributeLen(object): ...: ...: def __getattribute__(self, name): ...: print("__getattribute__ {}".format(name)) ...: return super().__getattribute__(name) ...: ...: def __len__(self): ...: return len(vars(self)) ...:
In [2]: obj = GetAttributeLen()
In [3]: obj.a = 1
In [4]: obj.b = 2
In [5]: obj.a += obj.b__getattribute__ a__getattribute__ b
In [6]: len(obj)__getattribute__ __dict__Out[6]: 2
In [1]: class GetAttributeLen(object): ...: ...: def __getattribute__(self, name): ...: print("__getattribute__ {}".format(name)) ...: return super().__getattribute__(name) ...: ...: def __len__(self): ...: return len(vars(self)) ...:
In [2]: obj = GetAttributeLen()
In [3]: obj.a = 1
In [4]: obj.b = 2
In [5]: obj.a += obj.b__getattribute__ a__getattribute__ b
In [6]: len(obj)__getattribute__ __dict__Out[6]: 2
__getattribute__
[Quase] incondicional
__getattr__
Fallback
Alguns built-ins (e.g. len) podem chamar um método dunder diretamente, sem passar pelo __getattribute__, outros não (e.g. vars)
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
15/3615/36
In [1]: class CountGets(object): ...: count = 0 ...: ...: def __getattr__(self, name): ...: self.count += 1 ...: return name ...:
In [2]: cg = CountGets()
In [3]: hasattr(cg, "count")Out[3]: True
In [4]: cg.countOut[4]: 0
In [5]: hasattr(cg, "a")Out[5]: True
In [6]: cg.countOut[6]: 1
In [7]: hasattr(CountGets, "a")Out[7]: False
In [8]: cg.countOut[8]: 1
In [1]: class CountGets(object): ...: count = 0 ...: ...: def __getattr__(self, name): ...: self.count += 1 ...: return name ...:
In [2]: cg = CountGets()
In [3]: hasattr(cg, "count")Out[3]: True
In [4]: cg.countOut[4]: 0
In [5]: hasattr(cg, "a")Out[5]: True
In [6]: cg.countOut[6]: 1
In [7]: hasattr(CountGets, "a")Out[7]: False
In [8]: cg.countOut[8]: 1
Built-ins hasattr, getattr e setattrBuilt-ins hasattr, getattr e setattr
Acesso aos atributos de um objetoFuncionam mesmo quando não existe dicionário da instância
Por conta do hasattr, recomenda-se que __getattr__ e __getattribute__ não tenham efeito colateral
Acesso via strings ao invés de identificadores
getattr(obj, “name”) → obj.name
setattr(obj, “name”, value) → obj.name = value
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
16/3616/36
Identificadores em Unicode!!!Identificadores em Unicode!!!
Notaram que havia um acento no identificador?
Python 3, apenas!
São normalizados (NFKC)… mas as strings não são… gotcha!
Built-in “type”Criação dinâmica de classe a partir do dicionário da classe (namespace)
In [1]: class Usuário(object): ...In [1]: class Usuário(object): ...
!!áá
>>> obj = type("SomeClass", # Nome da classe... (object,), # Bases (herança)... {c: i for i, c in... enumerate(" "Σ����� )} # Namespace... )()>>> obj.� == getattr(obj, ""� )False>>> obj.Σ == getattr(obj, " "Σ )True>>> dir(obj)[..., ' 'Σ , ''� , ''� , ''� , ''� , ''� ]
>>> obj = type("SomeClass", # Nome da classe... (object,), # Bases (herança)... {c: i for i, c in... enumerate(" "Σ����� )} # Namespace... )()>>> obj.� == getattr(obj, ""� )False>>> obj.Σ == getattr(obj, " "Σ )True>>> dir(obj)[..., ' 'Σ , ''� , ''� , ''� , ''� , ''� ]
Código q
ue envie
i
no dia 2
016-07-2
1
na maill
ist
"Python-
ideas"
Código q
ue envie
i
no dia 2
016-07-2
1
na maill
ist
"Python-
ideas"
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
17/3617/36
Parte 3Parte 3
Escopo / ClosureEscopo / Closure
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
18/3618/36
Escopo léxico / ClosureEscopo léxico / ClosureEscopo léxico
Pertinência do nome relativo à estrutura do código
Existência de um namespace “local”
Escopo “estático” (outras linguagens, linters)
e.g. pyflakes: detecção de variáveis não utilizadas pelo código
ClosureBloco (função)
+
variáveis livres (contexto “não local”)
In [1]: def itemgetter(key): ...: return lambda obj: obj[key]
In [2]: data = {"a": 1, "b": 2, 3: 4}
In [3]: values = [5, 10, 15, 20]
In [4]: itemgetter("b")(data)Out[4]: 2
In [5]: get3 = itemgetter(3)
In [6]: get3(values)Out[6]: 20
In [7]: get3(data)Out[7]: 4
In [1]: def itemgetter(key): ...: return lambda obj: obj[key]
In [2]: data = {"a": 1, "b": 2, 3: 4}
In [3]: values = [5, 10, 15, 20]
In [4]: itemgetter("b")(data)Out[4]: 2
In [5]: get3 = itemgetter(3)
In [6]: get3(values)Out[6]: 20
In [7]: get3(data)Out[7]: 4
# oudef itemgetter(key): def func(obj): return obj[key] return func
# oufrom operator import itemgetter
# oudef itemgetter(key): def func(obj): return obj[key] return func
# oufrom operator import itemgetter
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
19/3619/36
Atribuição“nonlocal” e “global”
Atribuição“nonlocal” e “global”
globalAssocia o nome local à variável do namespace global
nonlocalAssocia o nome à variável do escopo léxico “imediatamente anterior”
Disponível apenas no Python 3
In [1]: a = b = c = 0
In [2]: def outer(): ...: a = b = c = 1 ...: def inner(): ...: global a ...: nonlocal b ...: a = b = c = 2 ...: inner() ...: print(a, b, c)
In [3]: outer()1 2 1
In [4]: print(a, b, c)2 0 0
In [1]: a = b = c = 0
In [2]: def outer(): ...: a = b = c = 1 ...: def inner(): ...: global a ...: nonlocal b ...: a = b = c = 2 ...: inner() ...: print(a, b, c)
In [3]: outer()1 2 1
In [4]: print(a, b, c)2 0 0
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
20/3620/36
locals, globals e built-inslocals, globals e built-ins
Os namespaces de variáveis locais e globais são dicionários que podem ser acessados por meio de built-ins:
Escopo do bloco em execução:locals()
Escopo do módulo ou script:globals()
Há um outro namespace que serve de fallback para o de variáveis globais: o namespace de built-ins
import __builtin__ # Python 2
import builtins # Python 3
In [1]: import builtins
In [2]: builtins.a = builtins.b = 0
In [3]: def func(): ...: globals()["a"] = 2
In [4]: func()
In [5]: "b" in globals()Out[5]: False
In [6]: print(a, b)2 0
In [1]: import builtins
In [2]: builtins.a = builtins.b = 0
In [3]: def func(): ...: globals()["a"] = 2
In [4]: func()
In [5]: "b" in globals()Out[5]: False
In [6]: print(a, b)2 0
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
21/3621/36
Escopo dinâmicoEscopo dinâmico
Valor associado ao contexto/ambiente mais recente na pilha de execução (i.e., ao namespace da função que chamou)
Possível indiretamente através da passagem de “locals()” ou “globals()” como parâmetro
In [1]: def add(context): ...: a, b = context["a"], context["b"] ...: return a + b
In [2]: def call(): ...: a, b = 2, 5 ...: return add(locals())
In [3]: call()Out[3]: 7
In [1]: def add(context): ...: a, b = context["a"], context["b"] ...: return a + b
In [2]: def call(): ...: a, b = 2, 5 ...: return add(locals())
In [3]: call()Out[3]: 7
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
22/3622/36
In [1]: data = {"linguagem": "Python"}
In [2]: nome = "Exemplo"
In [3]: f'Tutorial de {3 + 2} partes sobre {data["linguagem"]}'Out[3]: 'Tutorial de 5 partes sobre Python'
In [4]: # Sem f-strings, seria necessário: ...: "Tutorial de {} partes sobre {data[linguagem]}".format(3 + 2, **locals())Out[4]: 'Tutorial de 5 partes sobre Python'
In [5]: # Representação, como repr(linguagem) ...: f"Agora o {nome!r} está entre aspas simples!"Out[5]: "Agora o 'Exemplo' está entre aspas simples!"
In [6]: # Formatação, sufixo ":3.2f" como no str.format ...: f"Número {len(nome):3.2f} com duas casas decimais..."Out[6]: 'Número 7.00 com duas casas decimais...'
In [1]: data = {"linguagem": "Python"}
In [2]: nome = "Exemplo"
In [3]: f'Tutorial de {3 + 2} partes sobre {data["linguagem"]}'Out[3]: 'Tutorial de 5 partes sobre Python'
In [4]: # Sem f-strings, seria necessário: ...: "Tutorial de {} partes sobre {data[linguagem]}".format(3 + 2, **locals())Out[4]: 'Tutorial de 5 partes sobre Python'
In [5]: # Representação, como repr(linguagem) ...: f"Agora o {nome!r} está entre aspas simples!"Out[5]: "Agora o 'Exemplo' está entre aspas simples!"
In [6]: # Formatação, sufixo ":3.2f" como no str.format ...: f"Número {len(nome):3.2f} com duas casas decimais..."Out[6]: 'Número 7.00 com duas casas decimais...'
f-stringsf-strings
Novidade do Python 3.6
Não utiliza nem globals(), nem locals()Acesso a contextos intermediários (closure)
Mais poderoso que str.format por aceitar qualquer expressão dentro das chaves (até lambdas, desde que entre parênteses)
https://www.python.org/dev/peps/pep-0498
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
23/3623/36
Objetos frame (“quadro” de execução)Namespaces: frame.f_locals e frame.f_globals
Parent frame: frame.f_back
FrameInfo
Devolvidos pelo inspect.stack (pilha)
Contém o próprio frame no atributo “frame”
Python stack frame
Python stack frame
from inspect import stack
def a(): return b()def b(): return c()def c(): return d()def d(): return e()def e(): return stack()
print([finfo.function for finfo in a()])
from inspect import stack
def a(): return b()def b(): return c()def c(): return d()def d(): return e()def e(): return stack()
print([finfo.function for finfo in a()])
$ python 36_inspect_stack.py['e', 'd', 'c', 'b', 'a', '<module>']$ python 36_inspect_stack.py['e', 'd', 'c', 'b', 'a', '<module>']
from inspect import currentframe
def caller(): data = [1, 2, 3] change_data() return data
def change_data(): currentframe().f_back.f_locals["data"].append(5)
print(caller())
from inspect import currentframe
def caller(): data = [1, 2, 3] change_data() return data
def change_data(): currentframe().f_back.f_locals["data"].append(5)
print(caller())
$ python 37_inspect_currentframe.py[1, 2, 3, 5]$ python 37_inspect_currentframe.py[1, 2, 3, 5]
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
24/3624/36
Parte 4Parte 4
CompilaçãoCompilação
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
25/3625/36
eval: evaluate expressionexec: execute statement(s)eval: evaluate expression
exec: execute statement(s)Built-ins para rodar o código de uma string
3 parâmetros: código, globals e locals
2 parâmetros: locals será igual a globals
1 parâmetro: globals e locals resultam dos built-ins homônimos
Distinção explícita entre expressões (valores) e statements (comandos)
No Python 2, exec é um statement
In [1]: import math
In [2]: eval("sin(pi / 2)", vars(math))Out[2]: 1.0
In [3]: ns = {}
In [4]: exec("result = log2(4096)", vars(math), ns)
In [5]: nsOut[5]: {'result': 12.0}
In [1]: import math
In [2]: eval("sin(pi / 2)", vars(math))Out[2]: 1.0
In [3]: ns = {}
In [4]: exec("result = log2(4096)", vars(math), ns)
In [5]: nsOut[5]: {'result': 12.0}
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
26/3626/36
Python é compilado!Python é compilado!Built-in compile(source, filename, mode)
O código (source) é uma string
O nome de arquivo (filename) pode ser algo em colchetes como “<string>” ou “<stdin>” quando a origem não é um arquivo
O modo (mode) pode ser uma string entre:“eval”: expressão
“exec”: statement(s)
“single”: oneliner para REPL
http://lucumr.pocoo.org/2011/2/1/exec-in-python
In [1]: add2tox = compile("x + 2", "<string>", "eval")
In [2]: eval(add2tox, {"x": 3})Out[2]: 5
In [3]: from timeit import timeit
In [4]: timeit('exec("k = 1e3", {})')Out[4]: 10.071980122000241
In [5]: timeit(stmt='exec(code, {})', ...: setup='code = compile("k = 1e3", "<string>", "exec")')Out[5]: 0.3254070729999512
In [1]: add2tox = compile("x + 2", "<string>", "eval")
In [2]: eval(add2tox, {"x": 3})Out[2]: 5
In [3]: from timeit import timeit
In [4]: timeit('exec("k = 1e3", {})')Out[4]: 10.071980122000241
In [5]: timeit(stmt='exec(code, {})', ...: setup='code = compile("k = 1e3", "<string>", "exec")')Out[5]: 0.3254070729999512
Resultado é um objeto code (bytecode) para rodar com eval ou exec
Compile uma vez, execute várias vezes depois, é muito mais rápido!
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
27/3627/36
Segurança e gotchasSegurança e gotchas
Não rode exec/eval com dados não filtrados em ambiente não isolado!
Expressão que apaga o diretório do usuário no Linux (algo provavelmente indesejável):
__import__("subprocess").call("rm -rf ~", shell=True)
Modificar locals() dentro de uma função pode não adiantar…
Solução: namespaces explícitos
Alternativa: usar containers
!!In [1]: a = 7
In [2]: def f(): ...: exec('a = 3') ...: print(locals()) ...: return a
In [3]: f(){'a': 3}Out[3]: 7
In [1]: a = 7
In [2]: def f(): ...: exec('a = 3') ...: print(locals()) ...: return a
In [3]: f(){'a': 3}Out[3]: 7
O exec modifica locals(), mas “a” é uma variável livre
O exec modifica locals(), mas “a” é uma variável livre
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
28/3628/36
In [1]: import inspect, ast
In [2]: print(inspect.getsource(ast.walk))def walk(node): """ Recursively yield all descendant nodes in the tree starting at *node* (including *node* itself), in no specified order. This is useful if you only want to modify nodes in place and don't care about the context. """ from collections import deque todo = deque([node]) while todo: node = todo.popleft() todo.extend(iter_child_nodes(node)) yield node
In [3]: inspect.getsource??Signature: inspect.getsource(object)Source:def getsource(object): """Return the text of the source code for an object.
The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a single string. An OSError is raised if the source code cannot be retrieved.""" lines, lnum = getsourcelines(object) return ''.join(lines)File: /usr/lib/python3.6/inspect.pyType: function
In [1]: import inspect, ast
In [2]: print(inspect.getsource(ast.walk))def walk(node): """ Recursively yield all descendant nodes in the tree starting at *node* (including *node* itself), in no specified order. This is useful if you only want to modify nodes in place and don't care about the context. """ from collections import deque todo = deque([node]) while todo: node = todo.popleft() todo.extend(iter_child_nodes(node)) yield node
In [3]: inspect.getsource??Signature: inspect.getsource(object)Source:def getsource(object): """Return the text of the source code for an object.
The argument may be a module, class, method, function, traceback, frame, or code object. The source code is returned as a single string. An OSError is raised if the source code cannot be retrieved.""" lines, lnum = getsourcelines(object) return ''.join(lines)File: /usr/lib/python3.6/inspect.pyType: function
Obtendo o código fonteObtendo o
código fonte
No IPython, basta utilizar “??” (visualização)
Função inspect.getsource (obtém uma string)
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
29/3629/36
Outros módulosOutros módulos
dis“Disassembler” de bytecode Python
Recursos de análise no Python 3.4+, dependência para o pacote “bytecode” no PyPI
impObsoleto nas versões mais recentes do Python, substituído pelo importlib
importlibComponentes do “import” (e “__import__”) do Python, permite a implementação de um procedimento alternativo de importação
https://docs.python.org/library
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
30/3630/36
Parte 5Parte 5
ASTAbstract Syntax Tree
ASTAbstract Syntax Tree
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
31/3631/36
AST a partir de códigoAST a partir de código
Árvore cujos nós são instâncias de ast.AST
Obtido a partir de ast.parse, ou compile com a flag ast.PyCF_ONLY_AST
Para ser utilizado, precisa ser compilado para bytecode com compile
In [1]: import ast, cmath
In [2]: source = "exp(1j * pi).real"
In [3]: tree = ast.parse(source, "<source>", "eval")
In [4]: print(ast.dump(tree, False, False))Expression(Attribute(Call(Name('exp', Load()), [BinOp(Num(1j), Mult(), Name('pi', Load()))], []), 'real', Load()))
In [5]: code = compile(tree, "<ast>", "eval")
In [6]: eval(code, vars(cmath))Out[6]: -1.0
In [1]: import ast, cmath
In [2]: source = "exp(1j * pi).real"
In [3]: tree = ast.parse(source, "<source>", "eval")
In [4]: print(ast.dump(tree, False, False))Expression(Attribute(Call(Name('exp', Load()), [BinOp(Num(1j), Mult(), Name('pi', Load()))], []), 'real', Load()))
In [5]: code = compile(tree, "<ast>", "eval")
In [6]: eval(code, vars(cmath))Out[6]: -1.0
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
32/3632/36
In [1]: import ast
In [2]: source = """ ...: a = 7 ...: a *= 6 ...: b = a + 5 ...: """
In [3]: tree = ast.parse(source)
In [4]: # Mantém apenas as atribuições ...: tree.body = [node for node in tree.body if isinstance(node, ast.Assign)]
In [5]: code = compile(tree, "<ast>", "exec")
In [6]: ns = {}
In [7]: exec(code, {}, ns)
In [8]: nsOut[8]: {'a': 7, 'b': 12}
In [1]: import ast
In [2]: source = """ ...: a = 7 ...: a *= 6 ...: b = a + 5 ...: """
In [3]: tree = ast.parse(source)
In [4]: # Mantém apenas as atribuições ...: tree.body = [node for node in tree.body if isinstance(node, ast.Assign)]
In [5]: code = compile(tree, "<ast>", "exec")
In [6]: ns = {}
In [7]: exec(code, {}, ns)
In [8]: nsOut[8]: {'a': 7, 'b': 12}
Modificação de ASTModificação de ASTast.Assign é o nó da atribuição
ast.Module é o nó resultante do parse no modo “exec” (padrão), contém os statements no atributo “body”
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
33/3633/36
Modificação de AST com ast.NodeTransformer
Modificação de AST com ast.NodeTransformer
In [9]: # Subtrai 1 em todos os números ...: class Sub1(ast.NodeTransformer): ...: def visit_Num(self, node): ...: return ast.copy_location(ast.Num(node.n - 1), node)
In [10]: tree = Sub1().visit(tree)
In [11]: code = compile(tree, "<ast>", "exec")
In [12]: exec(code, {}, ns)
In [13]: nsOut[13]: {'a': 6, 'b': 10}
In [9]: # Subtrai 1 em todos os números ...: class Sub1(ast.NodeTransformer): ...: def visit_Num(self, node): ...: return ast.copy_location(ast.Num(node.n - 1), node)
In [10]: tree = Sub1().visit(tree)
In [11]: code = compile(tree, "<ast>", "exec")
In [12]: exec(code, {}, ns)
In [13]: nsOut[13]: {'a': 6, 'b': 10}
Cria uma nova árvore com o método visit
Transforma usando os métodos visit_NOME com o NOME da classe (ast.Num, no exemplo)
ast.copy_location é necessário para evitar o “lineno” explícito em todos os nós
ast.Num são números, o valor armazenado fica no atributo “n”
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
34/3634/36
ast.walkast.walk
Não garante a ordenação ao percorrer a árvore
ast.Name é a classe dos identificadores, cujo nome fica no atributo “id”
In [14]: # Obtém todos os identificadores do código (com repetição) ...: [node.id for node in ast.walk(tree) if isinstance(node, ast.Name)]Out[14]: ['a', 'b', 'a']
In [14]: # Obtém todos os identificadores do código (com repetição) ...: [node.id for node in ast.walk(tree) if isinstance(node, ast.Name)]Out[14]: ['a', 'b', 'a']
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
35/3635/36
ASTs válidasASTs válidas
Geração necessita o preenchimento dos números de linha (lineno) e coluna (col_offset)
ast.fix_missing_locations preenche isso automaticamente
ASTs inválidas somente são percebidas como tal no momento da compilação
Green Tree Snakes - the missing Python AST docs (link acima) contém dicas sobre como utilizar ASTs
https://greentreesnakes.readthedocs.io
WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27WTA2017 – São Paulo – SP – Poli-USP – 2017-01-27Instrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobelliniInstrospecção e compilação tardia em Python – Danilo J. S. Bellini – @danilobellini
36/3636/36
Fim!Fim!
Perguntas?Perguntas?