Transcript
Page 1: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 2: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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!

Page 3: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 4: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 5: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 6: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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'}

Page 7: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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”)

Page 8: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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__

Page 9: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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)

Page 10: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 11: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 12: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 13: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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'

Page 14: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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)

Page 15: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 16: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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"

Page 17: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 18: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 19: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 20: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 21: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 22: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 23: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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]

Page 24: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 25: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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}

Page 26: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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!

Page 27: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 28: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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)

Page 29: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 30: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 31: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 32: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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”

Page 33: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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”

Page 34: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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']

Page 35: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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

Page 36: (2017-01-27) [WTA2017] Instrospecção e compilação tardia em Python (Tutorial)

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?


Top Related