Theme: Programming Fundamentals
Topic: Metaprogramming
Presenter | James Powell james@dutc.io |
Date | Friday, May 7, 2021 |
Time | 12:30 PM PST |
Computers make our lives easier by allowing us to automate tasks we would otherwise do by hand. But the practice of writing code itself can be tedious. So why can’t I write a computer programme that automates my task of writing computer programmes that automates my task of writing computer programmes that automates my task of writing computer programmes…?
This seminar will present a view of metaprogramming approaches in Python, focusing on questions like:
print("Let's go!")
Avoid repetition!
Avoid “update anomalies!”
from pandas import DataFrame, date_range, IndexSlice
from string import ascii_lowercase
from numpy import tile, repeat, hstack
from numpy.random import default_rng
from pandas import Timestamp
rng = default_rng(Timestamp('2021-05-07').asm8.astype('uint32'))
devices1 = rng.choice([*ascii_lowercase], size=(num_devices := 5, 8))
devices1[:, -4:] = [*'.net']
devices1 = devices1.view('<U8').ravel()
devices2 = rng.choice([*ascii_lowercase], size=(len(devices1) - 2, 8))
devices2[:, -4:] = [*'.net']
devices2 = devices2.view('<U8').ravel()
devices2 = hstack([devices1[:2], devices2])
time = date_range('2021-05-07 9:00', periods=(num_periods := 8), freq='1H')
df1 = DataFrame({
'device': repeat(devices1, len(time)),
'time': tile(time, len(devices1)),
'signal': rng.normal(size=(len(time) * len(devices1))),
})
df2 = DataFrame({
'device': repeat(devices2, len(time)),
'time': tile(time, len(devices2)),
'signal': rng.normal(size=(len(time) * len(devices2))),
})
print(
# df1.head(3),
# df2.head(3),
# df1['device'].unique(),
# df2['device'].unique(),
# df1.set_index(['device', 'time']),
# df2.set_index(['device', 'time']),
# (
# df1.set_index(['device', 'time'])
# + df2.set_index(['device', 'time'])
# ).dropna(),
)
print(
df1.set_index(['device', 'time']).loc[IndexSlice[:, '2021-05-07 12:00':]] * .5,
# df2.set_index(['device', 'time']).loc[IndexSlice[:, '2021-05-07 12:00':]] * .5,
)
def clean_data(df):
df = df.set_index(['device', 'time'])
...
return df
def f(data):
data = clean_data(data, remove_outliers=True)
...
def g(data):
data = clean_data(data, remove_outliers=True)
...
def h(data):
data = clean_data(data, remove_outliers=False)
...
def remove_outliers(data):
...
def normalize_units(data):
...
def scale_data(data):
...
def f(data):
data = remove_outliers(data)
data = normalize_units(data)
def g(data):
data = scale_data(data)
data = normalize_units(data)
def h(data):
data = scale_data(data)
data = normalize_units(data)
def clean_data_400g(data):
pass
def clean_data_100g(data):
pass
__build_class__
, metaclasses, and __init_subclass__
)eval
/exec
class T:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'T({self.x!r}, {self.y!r})'
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
a = T(1, 2)
b = T(1, 2)
print(
f'{a = }',
f'{a == b = }',
sep='\n'
)
from dataclasses import dataclass
from functools import total_ordering
@total_ordering
@dataclass
class T:
x : int
y : int
def __lt__(self, other):
return (self.x, self.y) < (other.x, other.y)
# def __gt__(self, other):
# return (self.x, self.y) > (other.x, other.y)
# def __lte__(self, other):
# return (self.x, self.y) <= (other.x, other.y)
# def __gte__(self, other):
# return (self.x, self.y) >= (other.x, other.y)
x = T(1, 2)
y = T(1, 2)
z = T(3, 4)
print(
x,
x == y,
x < z,
sep='\n'
)
from inspect import signature
def f(name='Aditya'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
f('Raj')
f.__name__ = f.__qualname__ = 'eff'
print(
f'{f = }',
f'{f.__name__ = }',
f'{f.__doc__ = }',
f'{f.__defaults__ = }',
f'{signature(f) = }',
sep='\n',
)
def do_twice(f):
for _ in range(2):
f()
def f(name='Raj'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
def g(msg='Goobye', name='Raj'):
''' prints a message to the screen '''
print(f'{msg}, {name}!')
do_twice(f)
do_twice(g)
def do_twice(f, *args, **kwargs):
for _ in range(2):
f(*args, **kwargs)
def f(name='Raj'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
do_twice(f, name='Radhika')
def do_n_times(f, *args, times=2, **kwargs):
for _ in range(times):
f(*args, **kwargs)
def f(name='Raj', times='good'):
''' prints a message to the screen '''
print(f"Hello, {name}! We're having a {times} time")
do_n_times(f, times=4, name='Radhika')
for _ in range(10):
def f():
pass
f()
def do_n_times(f, times=2):
def inner(*args, **kwargs):
for _ in range(times):
f(*args, **kwargs)
return inner
def f(name='Raj'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
new_f = do_n_times(f, times=4)
new_f(name='Radhika')
do_n_times(f, times=4)(name='Radhika')
def do_n_times(f, times=2):
def inner(*args, **kwargs):
for _ in range(times):
f(*args, **kwargs)
return inner
def f(name='Raj'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
f = do_n_times(f)
f(name='Radhika')
def do_twice(f):
def inner(*args, **kwargs):
for _ in range(2):
f(*args, **kwargs)
return inner
@do_twice
def f(name='Raj'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
# f = do_twice(f)
f(name='Radhika')
def do_n_times(times=2):
def dec(f):
def inner(*args, **kwargs):
for _ in range(times):
f(*args, **kwargs)
return inner
return dec
@do_n_times(times=4)
def f(name='Raj'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
f(name='Radhika')
from functools import wraps
def do_n_times(times=2):
def dec(f):
@wraps(f)
def inner(*args, **kwargs):
for _ in range(times):
f(*args, **kwargs)
return inner
return dec
@do_n_times(times=4)
def f(name='Raj'):
''' prints a message to the screen '''
print(f'Hello, {name}!')
f(name='Radhika')
from functools import wraps
def dec(f):
@wraps(f)
def inner(*args, **kwargs):
return f(*args, **kwargs)
return inner
# “metaphor”
@dec
def f():
pass
# f = dec(f)
from functools import wraps
registry = {}
def metadata(**kwargs):
def dec(f):
registry[f] = kwargs
return f
return dec
@metadata(
author = 'Radhika R',
email = 'radr@example.com',
)
def f():
pass
@metadata(
author = 'Aditya A',
email = 'aa@example.com',
platform = '...',
)
def g():
pass
print(registry)
class T:
pass
def foo(self):
pass
T.foo = foo
x = T()
x.foo()
print(
T,
f'{T.__name__ = }',
f'{T.__mro__ = }',
f'{dir(T) = }',
sep='\n',
)
def dec(cls):
return cls
@dec
class T:
pass
print(
T,
# f'{dir(T) = }',
)
from dataclasses import dataclass
class Interface:
pass
@dataclass
class Derived(Interface):
x : int
y : int
from enum import Enum, auto
from random import choice
class Colors(Enum):
Red = auto()
Green = auto()
Blue = auto()
print(
Colors,
Colors.Red,
Colors['Red'],
Colors['Red'] is Colors['Red'],
# Colors['Orange'],
# f'{Colors["Red"].value = }',
# f'{[*Colors] = }',
f'{choice([*Colors]) = }',
sep='\n',
)
class T:
pass
print(T)
for _ in range(10):
class T:
pass
print(T)
for _ in range(10):
def f():
pass
for _ in range(10):
class T:
for _ in range(10):
def f():
pass
print(T)
def f():
class T:
pass
return T
print(f())
print(f())
print(f())
T = f()
x = T()
T = f()
print(f'{isinstance(x, T) = }')
def f():
class T:
pass
return T
from dis import dis
dis(f.__code__)
import builtins
@lambda f: setattr(builtins, '__build_class__', f(builtins.__build_class__))
def bind(orig):
def __build_class__(func, name, *args, **kwargs):
print(f'{name = }, {func = }')
cls = orig(func, name, *args, **kwargs)
return cls
return __build_class__
class A:
pass
class B:
pass
print(
# A,
)
import pandas
import matplotlib.pyplot
import networkx
class A:
pass
class B(A):
def __new__(cls):
print(f'C.__new__({cls!r})')
return super().__new__(cls)
def __init__(self):
print(f'C.__init__({self!r})')
super().__init__()
def __call__(self):
print(f'C.__call__({self!r})')
x = B()
print(
f'{x = }',
f'{x() = }',
sep='\n'
)
class metaclass(type):
def __call__(self):
print(f'metaclass.__call__({self!r})')
return super().__call__()
def __new__(cls, name, bases, body, **kwds):
print(f'metaclass.__new__({cls!r}, {name!r}, {bases!r}, {body!r}, {kwds!r})')
return super().__new__(cls, name, bases, body)
def __init__(self, name, bases, body, **kwds):
print(f'metaclass.__init__({self!r}, {name!r}, {bases!r}, {body!r}, {kwds!r})')
return super().__init__(name, bases, body)
@classmethod
def __prepare__(cls, name, bases, **kwds):
print(f'metaclass.__prepare__({cls!r}, {name!r}, {bases!r}, {kwds!r})')
return super().__prepare__(cls, name, bases, **kwds)
class A(metaclass=metaclass): pass
# x = A()
class B(A, x=10, y=200, z=3_000): pass
class C(B):
def __new__(cls):
print(f'C.__new__({cls!r})')
return super().__new__(cls)
def __init__(self):
print(f'C.__init__({self!r})')
super().__init__()
def __call__(self):
print(f'C.__call__({self!r})')
# def __init_subclass__(cls, **kwds):
# print(f'C.__init_subclass__({cls!r}, {kwds!r})')
# class D(C): pass
# class E(D, x='ecks', y='why', z='zee'): pass
class Base:
pass
# ---
class Derived(Base):
pass
class Base:
def foo(self):
pass
pass
# ---
print(
Base,
)
if not hasattr(Base, 'foo'):
raise TypeError(f'{Base} must have attribute "foo"')
class Derived(Base):
def bar(self):
return self.foo()
pass
class Base:
def foo(self):
return self.bar() + 1
# ---
class Derived(Base):
pass
class BaseMeta(type):
def __init__(self, name, bases, body):
if name != 'Base' and 'bar' not in body:
raise TypeError(f'{name} must have attribute "foo"')
class Base(metaclass=BaseMeta):
def foo(self):
return self.bar()
# ---
class Derived(Base):
def bar(self):
pass
pass
def check(cls):
if not hasattr(cls, 'bar'):
raise TypeError(f'{cls} must have attribute "bar"')
return cls
# @check
class Derived:
# def bar(self):
# pass
pass
class Base:
def foo(self):
return self.bar()
def __init_subclass__(cls):
if not hasattr(cls, 'bar'):
raise TypeError(f'{cls} must have attribute "bar"')
# ---
class Derived(Base):
def bar(self):
pass
pass
from dataclasses import dataclass
class Component:
TYPES = {}
@classmethod
def from_name(cls, name, **fields):
return cls.TYPES[name](**fields)
def __init_subclass__(cls, names):
for n in names:
cls.TYPES[n] = cls
@dataclass
class Resistor(Component, names={'resistor'}):
resistance : float
@dataclass
class Capacitor(Component, names={'capacitor', 'cap'}):
capacitance : float
x = Component.from_name('resistor', resistance=1.5)
y = Component.from_name('capacitor', capacitance=3.5)
print(
# f'{x = }',
# f'{y = }',
sep='\n',
)
from pandas import Index, date_range
idx = Index([1, 2, 3])
idx = Index([1., 2., 3.])
idx = Index(range(3))
idx = Index([*'abc'])
idx = Index(date_range('2021-05-07', periods=3))
print(
idx,
)
from dataclasses import dataclass
from collections import namedtuple
class Component:
TYPES = {}
@classmethod
def from_name(cls, name, **fields):
return cls.TYPES[name](**fields)
def __init_subclass__(cls, names):
for n in names:
cls.TYPES[n] = cls
ComponentType = namedtuple('ComponentType', 'cls_name names fields')
component_types = [
ComponentType('Resistor', frozenset({'resistor'}), {'resistance': float}),
ComponentType('Capacitor', frozenset({'capacitor', 'cap'}), {'capacitance': float}),
ComponentType('Inductor', frozenset({'inductor'}), {'inductance': float}),
]
for ct in component_types:
locals()[ct.cls_name] = type(ct.cls_name,
(Component,),
{'__annotations__': ct.fields},
names=ct.names)
locals()[ct.cls_name] = dataclass(locals()[ct.cls_name])
x = Component.from_name('resistor', resistance=1.5)
y = Component.from_name('capacitor', capacitance=3.5)
print(
f'{x = }',
f'{y = }',
sep='\n',
)
eval
and exec
print(
eval('1 + 1')
)
exec('x = 1 + 1', globals())
print(
x,
)
class A:
pass
class B:
pass
class C:
pass
from string import ascii_uppercase
from textwrap import dedent
for cls_name in ascii_uppercase[:3]:
code = dedent(f'''
class {cls_name}:
pass
''')
print(code)
exec(code, globals())
x, y, z = A(), B(), C()
print(
# x,
# y,
# z,
sep='\n'
)
from sys import version_info
assert version_info.major == 2
from collections import namedtuple
T = namedtuple('T', 'a b c', verbose=True)
functools.total_ordering
)dataclasses.dataclass
)type
__build_class__
enum.Enum
)__init_subclass__
exec
(e.g., Python 2’s collections.namedtuple
)