Metadata
Metaphysics
Metaprogramming
- Duck typing
- Exec and eval
- Decorators
- Descriptors
- Metaclasses
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
class Duck:
def quack(self):
print("Quack!")
class Panda:
def quack(self):
print("Panda quack!")
def duck_duck_go(obj):
obj.quack()
duck_duck_go(Duck()) # Works
duck_duck_go(Panda()) # Works
duck_duck_go(1) # AE: 'int' object has no attribute 'quack'
def duck_duck_go(obj):
if hasattr(obj, 'quack'):
obj.quack()
duck_duck_go(Duck()) # Works
duck_duck_go(Panda()) # Works
duck_duck_go(1) # Nothing happens
class Person:
def __init__(self, name):
self.quack = name
duck_duck_go(Person('Ivo'))
# TypeError: 'str' object is not callable
class Panda:
pass
def create_panda(attributes):
panda = Panda()
for key, value in attributes.items():
setattr(panda, key, value)
return panda
p = create_panda({'name': 'Ivo', 'age': 23, 'weight': 80})
print(p.__dict__) # {'name': 'Ivo', 'age': 23, 'weight': 80}
p = create_panda({'name': 'Ivo', 'weight': 80})
print(p.__dict__) # {'name': 'Ivo', 'weight': 80}
def duck_duck_go(obj):
if hasattr(obj, 'quack')\
and callable(getattr(obj, 'quack')):
obj.quack()
code = '''
for i in range(0, 3):
print(i)
'''
exec(code)
# 0
# 1
# 2
Dynamic execution of Python code. Returns None.
expression = '1 + 2*(3**3)'
print(eval(expression)) # 55
Evals a single expression, returns result
code = '''
for i in range(0, 3):
print(i)
'''
eval(code) # Syntax error
def debug(func):
fname = func.__qualname__
@wraps(func)
def wrapper(*arg, **kwargs):
print('Calling {}'.format(fname))
return func(*arg, **kwargs)
return wrapper
class Panda:
@debug
def be_panda(self):
print('Being panda')
@debug
def awesome(self):
print('Pandas are awesome!')
@debugmethods
class Panda:
def be_panda(self):
print('Being panda')
def awesome(self):
print('Pandas are awesome!')
def debugmethods(cls):
return cls
def debugmethods(cls):
for attr, value in vars(cls).items():
if callable(value):
setattr(cls, attr, debug(value))
return cls
@debugmethods
class Panda:
def be_panda(self):
print('Being panda')
def awesome(self):
print('Pandas are awesome!')
p = Panda()
p.be_panda() # prints 'Calling be_panda'
p.awesome() # prints 'Calling awesome'
@debugmethods
class Panda:
pass
@debugmethods
class Person:
pass
@debugmethods
class Task:
pass
# ...
- Called for object being accessed as an atrubite of another object
- If we have class A and class B
class Panda:
def __get__(self, instance, owner):
# You're trying to get me ?
return super().__get__(instance, owner)
def __set__(self, instance, value):
# You're trying to change me ?
return super().__set__(instance, value)
def __delete__(self, instance):
# You're trying to delete me ?
return super().__delete__(instance)
class Panda:
def __get__(self, instance, owner):
return "Don't touch me"
def __set__(self, instance, value):
print("You're trying to change me :O ?!")
def __delete__(self, instance):
print("You can't delete me :D !")
>>> type(5)
<class 'int'>
>>> type('python')
<class 'str'>
>>> class Panda: pass
...
>>> p = Panda()
>>> type(p)
<class '__main__.Panda'>
>>> class Panda: pass
...
>>> p = Panda()
>>> type(p)
<class '__main__.Panda'>
>>> int
<class 'int'>
>>> str
<class 'str'>
>>> Panda
<class '__main__.Panda'>
>>> type(int)
<class 'type'>
>>> type(str)
<class 'type'>
>>> type(Panda)
<class 'type'>
>>> type(type)
<class 'type'>
class type:
...
vs
class Panda:
def __new__(cls, name):
print('This is the constructor')
return super().__new__(cls)
def __init__(self, name):
print('This is the initializator')
self.name = name
def __delete__(self):
print('Aaand this is the destructor')
return super().__delete__(self)
Panda.__dict__ == vars(Panda)
p = Panda()
p.__dict__ == vars(p)
module.__dict__ == vars(module)
class Panda:
def __init__(self, name):
self.name = name
def be_panda(self):
print('Being panda')
body = '''
def __init__(self, name):
self.name = name
def be_panda(self):
print('Bamboo & sleep')
'''
clsname = 'Panda'
bases = (object, )
clsdict = type.__prepare__(clsname, bases)
exec(body, globals(), clsdict)
Panda = type(clsname, bases, clsdict)
body = '''
def __init__(self, name):
self.name = name
def be_panda(self):
print('Bamboo & sleep')
'''
clsname = 'Panda'
bases = (object, )
clsdict = type.__prepare__(clsname, bases)
exec(body, globals(), clsdict)
Panda = type(clsname, bases, clsdict)
p = Panda('Ivo')
print(p.name)
p.be_panda()
class Panda(metaclass=type):
def __init__(self, name):
self.name = name
def be_panda(self):
print('Being panda')
class mytype(type):
def __new__(cls, name, bases, clsdict):
clsobj = super().__new__(cls, name, bases, clsdict)
print('Constructed new type')
return clsobj
class Panda(metaclass=mytype):
pass
p = Panda() # Prints 'Constructed a new type'
@debugmethods
class Panda:
pass
@debugmethods
class Person:
pass
@debugmethods
class Task:
pass
# ...
class debugmeta(type):
def __new__(cls, clsname, bases, clsdict):
clsobj = super().__new__(cls, clsname, bases, clsdict)
# Decoration here.
clsobj = debugmethods(clsobj)
return clsobj
class Base(metaclass=debugmeta):
pass
class Panda(Base):
pass
class Person(Base):
pass
class Task(Base):
pass
class no_multiple_inheritence(type):
def __new__(cls, name, bases, clsdict):
if len(bases) > 1:
raise TypeError('No multiple inheritence!')
return super().__new__(cls, name, bases, clsdict)
class Base(metaclass=no_multiple_inheritence):
pass
class A(Base):
pass
class B(Base):
pass
class C(A, B): # Raises error
pass