Python supercharged for fastai development
56
Advanced metaclasses, function signature manipulation, delegation patterns, and dynamic code generation utilities for building flexible and extensible APIs. The meta module provides powerful tools for creating sophisticated object-oriented designs and dynamic programming patterns.
Specialized metaclasses that modify class creation and object initialization behavior.
class FixSigMeta(type):
"""
Metaclass that fixes function signatures on classes overriding __new__.
Automatically sets __signature__ attribute based on __init__ method
to ensure proper introspection and IDE support for classes that
override __new__ but define parameters in __init__.
Usage:
class MyClass(metaclass=FixSigMeta):
def __init__(self, param1, param2): ...
def __new__(cls, *args, **kwargs): ...
# MyClass.__signature__ automatically reflects __init__ signature
"""
def __new__(cls, name, bases, dict): ...
class PrePostInitMeta(FixSigMeta):
"""
Metaclass that calls optional __pre_init__ and __post_init__ methods.
Automatically calls __pre_init__ before __init__ and __post_init__
after __init__ if these methods are defined. Provides hooks for
setup and cleanup logic around object initialization.
Execution order:
1. __new__ called
2. __pre_init__ called (if defined)
3. __init__ called
4. __post_init__ called (if defined)
Usage:
class MyClass(metaclass=PrePostInitMeta):
def __pre_init__(self, *args, **kwargs): ...
def __init__(self, *args, **kwargs): ...
def __post_init__(self, *args, **kwargs): ...
"""
def __call__(cls, *args, **kwargs): ...
class AutoInit(metaclass=PrePostInitMeta):
"""
Base class that automatically calls super().__init__ in __pre_init__.
Eliminates need for subclasses to remember to call super().__init__()
by automatically handling parent class initialization.
Usage:
class Parent(AutoInit):
def __init__(self, x): self.x = x
class Child(Parent):
def __init__(self, x, y): # No need to call super().__init__
self.y = y
"""
def __pre_init__(self, *args, **kwargs): ...
class NewChkMeta(FixSigMeta):
"""
Metaclass to avoid recreating objects passed to constructor.
If an object of the same class is passed to the constructor
with no additional arguments, returns the object unchanged
rather than creating a new instance.
Usage:
class MyClass(metaclass=NewChkMeta): pass
obj1 = MyClass()
obj2 = MyClass(obj1) # Returns obj1, not a new instance
obj3 = MyClass(obj1, extra_arg=True) # Creates new instance
"""
def __call__(cls, x=None, *args, **kwargs): ...
class BypassNewMeta(FixSigMeta):
"""
Metaclass that casts objects to class type instead of creating new instances.
If object is of _bypass_type, changes its __class__ to the target class
rather than creating a new instance. Useful for wrapping existing objects.
Class can define:
- _bypass_type: Type to cast from
- _new_meta: Custom creation method
Usage:
class StringWrapper(metaclass=BypassNewMeta):
_bypass_type = str
def upper_method(self): return self.upper()
wrapped = StringWrapper("hello") # Casts str to StringWrapper
print(wrapped.upper_method()) # "HELLO"
"""
def __call__(cls, x=None, *args, **kwargs): ...Tools for analyzing, modifying, and working with function signatures dynamically.
def test_sig(f, expected_signature):
"""
Test that function has expected signature string.
Utility for testing that function signatures match expected format,
useful for validating decorator effects and signature modifications.
Parameters:
- f: function to test
- expected_signature: str, expected signature format
Raises:
AssertionError: If signatures don't match
"""
def empty2none(param):
"""
Convert Parameter.empty to None for cleaner handling.
Helper function to normalize inspect.Parameter.empty values
to None for easier processing and comparison.
Parameters:
- param: inspect.Parameter value
Returns:
None if param is Parameter.empty, otherwise param unchanged
"""
def anno_dict(func):
"""
Get function annotations as dict with empty values normalized.
Extracts __annotations__ from function and converts Parameter.empty
values to None for cleaner processing.
Parameters:
- func: function to get annotations from
Returns:
dict: Annotations with empty values normalized to None
"""
def use_kwargs_dict(keep=False, **kwargs):
"""
Decorator to replace **kwargs in signature with specific parameters.
Modifies function signature to show specific keyword parameters
instead of generic **kwargs, improving IDE support and documentation.
Parameters:
- keep: bool, whether to keep **kwargs in addition to named params
- **kwargs: parameter names and defaults to add to signature
Returns:
Decorator function
Usage:
@use_kwargs_dict(param1=None, param2="default")
def func(**kwargs): ...
# Signature shows func(*, param1=None, param2='default')
"""
def use_kwargs(names, keep=False):
"""
Decorator to replace **kwargs with list of parameter names.
Similar to use_kwargs_dict but takes parameter names as list
without default values.
Parameters:
- names: list of str, parameter names to add
- keep: bool, whether to keep **kwargs
Returns:
Decorator function
"""
def delegates(to=None, keep=False, but=None):
"""
Decorator to delegate function parameters from another function.
Copies parameters from target function to decorated function,
enabling parameter forwarding while maintaining proper signatures
for IDE support and introspection.
Parameters:
- to: callable, function to delegate parameters from
- keep: bool, keep original parameters in addition to delegated ones
- but: str|list, parameter names to exclude from delegation
Returns:
Decorator that modifies function signature
Usage:
def target_func(a, b=1, c=2): pass
@delegates(target_func)
def wrapper(**kwargs):
return target_func(**kwargs)
# wrapper now has signature: wrapper(a, b=1, c=2)
"""
def method(func):
"""
Convert function to method by adding self parameter.
Modifies function signature to include 'self' as first parameter,
useful for dynamically creating methods.
Parameters:
- func: function to convert to method
Returns:
Function with method signature (including self)
"""
def funcs_kwargs(as_method=False):
"""
Get kwargs specification for function parameters.
Analyzes function parameters and returns specification
for **kwargs handling and parameter forwarding.
Parameters:
- as_method: bool, treat as method (skip self parameter)
Returns:
dict: Parameter specification for kwargs processing
"""Utilities for generating and modifying code at runtime.
def _mk_param(name, default=None):
"""
Create inspect.Parameter for signature construction.
Helper function to create Parameter objects for building
function signatures programmatically.
Parameters:
- name: str, parameter name
- default: default value for parameter
Returns:
inspect.Parameter: Configured parameter object
"""
def _rm_self(signature):
"""
Remove 'self' parameter from function signature.
Utility to convert method signatures to function signatures
by removing the self parameter.
Parameters:
- signature: inspect.Signature with self parameter
Returns:
inspect.Signature: New signature without self parameter
"""from fastcore.meta import FixSigMeta, PrePostInitMeta, AutoInit
# Class with proper signature introspection
class DataContainer(metaclass=FixSigMeta):
def __init__(self, data, transform=None, validate=True):
self.data = data
self.transform = transform
self.validate = validate
def __new__(cls, *args, **kwargs):
# Custom object creation logic
instance = super().__new__(cls)
return instance
# Signature is properly reflected for IDE support
import inspect
print(inspect.signature(DataContainer))
# <Signature (data, transform=None, validate=True)>
# Class with initialization hooks
class ConfigurableClass(metaclass=PrePostInitMeta):
def __pre_init__(self, *args, **kwargs):
print("Setting up configuration...")
self.setup_config()
def __init__(self, name, value):
self.name = name
self.value = value
def __post_init__(self, *args, **kwargs):
print("Finalizing setup...")
self.validate()
def setup_config(self):
self.config = {"initialized": True}
def validate(self):
assert hasattr(self, 'name')
assert hasattr(self, 'value')
obj = ConfigurableClass("test", 42)
# Prints: Setting up configuration...
# Prints: Finalizing setup...
# Simplified inheritance with AutoInit
class BaseProcessor(AutoInit):
def __init__(self, name):
self.name = name
self.processed_count = 0
class AdvancedProcessor(BaseProcessor):
def __init__(self, name, algorithm):
# No need to call super().__init__() - handled automatically
self.algorithm = algorithm
self.features = []
processor = AdvancedProcessor("nlp", "transformer")
print(processor.name) # "nlp" - inherited properlyfrom fastcore.meta import NewChkMeta, BypassNewMeta
# Avoid unnecessary object recreation
class SmartContainer(metaclass=NewChkMeta):
def __init__(self, data=None):
self.data = data or []
def add(self, item):
self.data.append(item)
container1 = SmartContainer([1, 2, 3])
container2 = SmartContainer(container1) # Returns container1, not new instance
print(container1 is container2) # True
container3 = SmartContainer(container1, extra_data=True) # Creates new instance
print(container1 is container3) # False
# Type casting with BypassNewMeta
class EnhancedString(str, metaclass=BypassNewMeta):
_bypass_type = str
def word_count(self):
return len(self.split())
def title_case(self):
return ' '.join(word.capitalize() for word in self.split())
# Cast existing string to EnhancedString
text = "hello world python programming"
enhanced = EnhancedString(text) # Casts str to EnhancedString
print(enhanced.word_count()) # 4
print(enhanced.title_case()) # "Hello World Python Programming"
print(type(enhanced)) # <class 'EnhancedString'>
# Works with string methods too
print(enhanced.upper()) # "HELLO WORLD PYTHON PROGRAMMING"from fastcore.meta import delegates, use_kwargs_dict, use_kwargs
import matplotlib.pyplot as plt
# Delegate parameters from matplotlib.pyplot.plot
@delegates(plt.plot)
def enhanced_plot(*args, title="Enhanced Plot", save_path=None, **kwargs):
"""Enhanced plotting with automatic title and save functionality."""
fig, ax = plt.subplots()
ax.plot(*args, **kwargs)
ax.set_title(title)
if save_path:
plt.savefig(save_path)
return fig, ax
# Function now has full plt.plot signature plus enhancements
import inspect
sig = inspect.signature(enhanced_plot)
print(sig) # Shows all matplotlib plot parameters
# Use the enhanced function
fig, ax = enhanced_plot([1, 2, 3], [1, 4, 9],
linestyle='--', color='red',
title="My Data", save_path="plot.png")
# Replace **kwargs with specific parameters for better IDE support
@use_kwargs_dict(method='GET', timeout=30, headers=None)
def api_request(url, **kwargs):
"""Make API request with specified parameters."""
import requests
return requests.request(**kwargs, url=url)
# Signature now shows: api_request(url, *, method='GET', timeout=30, headers=None)
# Replace **kwargs with parameter names from list
@use_kwargs(['host', 'port', 'database', 'username', 'password'])
def connect_db(**kwargs):
"""Connect to database with specified parameters."""
# Connection logic here
return f"Connected with {kwargs}"
# Signature shows all database connection parametersfrom fastcore.meta import delegates, method
from functools import partial
# Complex delegation with exclusions
class DataFrameWrapper:
def __init__(self, df):
self.df = df
@delegates(lambda self: self.df.groupby, but=['by'])
def smart_groupby(self, columns, **kwargs):
"""Enhanced groupby with automatic column handling."""
if isinstance(columns, str):
columns = [columns]
return self.df.groupby(columns, **kwargs)
# Delegate from multiple sources
class MLPipeline:
@delegates(lambda: sklearn.model_selection.train_test_split,
but=['arrays'])
@delegates(lambda: sklearn.preprocessing.StandardScaler,
keep=True)
def prepare_data(self, X, y, scale=True, **kwargs):
"""Prepare data with splitting and optional scaling."""
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Split data
split_kwargs = {k: v for k, v in kwargs.items()
if k in ['test_size', 'random_state', 'stratify']}
X_train, X_test, y_train, y_test = train_test_split(X, y, **split_kwargs)
# Optional scaling
if scale:
scaler_kwargs = {k: v for k, v in kwargs.items()
if k in ['copy', 'with_mean', 'with_std']}
scaler = StandardScaler(**scaler_kwargs)
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
return X_train, X_test, y_train, y_test
# Dynamic method creation
def create_property_method(attr_name):
@method
def get_property(self):
return getattr(self, f"_{attr_name}")
return get_property
class DynamicClass:
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, f"_{key}", value)
# Create getter method dynamically
setattr(self, f"get_{key}",
create_property_method(key).__get__(self, type(self)))
obj = DynamicClass(name="test", value=42, items=[1, 2, 3])
print(obj.get_name()) # "test"
print(obj.get_value()) # 42
print(obj.get_items()) # [1, 2, 3]from fastcore.meta import test_sig, anno_dict, empty2none
import inspect
# Test function signatures
def sample_function(a: int, b: str = "default", c=None) -> bool:
return True
# Verify signature matches expected format
test_sig(sample_function, "(a: int, b: str = 'default', c=None) -> bool")
# Extract and process annotations
annotations = anno_dict(sample_function)
print(annotations) # {'a': <class 'int'>, 'b': <class 'str'>, 'c': None, 'return': <class 'bool'>}
# Signature modification validation
def modify_signature(func, new_params):
"""Modify function signature and validate result."""
sig = inspect.signature(func)
params = list(sig.parameters.values())
# Add new parameters
for name, default in new_params.items():
param = inspect.Parameter(
name,
inspect.Parameter.KEYWORD_ONLY,
default=default
)
params.append(param)
# Create new signature
new_sig = sig.replace(parameters=params)
func.__signature__ = new_sig
return func
# Test the modification
@modify_signature
def test_func(x, y=1):
pass
modified = modify_signature(test_func, {'extra1': 'default', 'extra2': None})
test_sig(modified, "(x, y=1, *, extra1='default', extra2=None)")
# Parameter processing
def process_parameters(func):
"""Analyze function parameters and provide summary."""
sig = inspect.signature(func)
summary = {
'total_params': len(sig.parameters),
'required_params': [],
'optional_params': [],
'annotations': anno_dict(func)
}
for name, param in sig.parameters.items():
if param.default is inspect.Parameter.empty:
summary['required_params'].append(name)
else:
summary['optional_params'].append((name, empty2none(param.default)))
return summary
def example_func(a, b: int, c="default", d: str = None):
pass
summary = process_parameters(example_func)
print(f"Required: {summary['required_params']}") # ['a', 'b']
print(f"Optional: {summary['optional_params']}") # [('c', 'default'), ('d', None)]
print(f"Annotations: {summary['annotations']}") # {'b': int, 'd': str}from fastcore.meta import delegates, use_kwargs_dict, PrePostInitMeta
# Flexible configuration system
class ConfigurableAPI(metaclass=PrePostInitMeta):
def __pre_init__(self, *args, **kwargs):
self.config = {}
self.validators = []
def __init__(self, base_url, **config):
self.base_url = base_url
self.config.update(config)
def __post_init__(self, *args, **kwargs):
self.validate_config()
def validate_config(self):
for validator in self.validators:
validator(self.config)
def add_validator(self, validator_func):
self.validators.append(validator_func)
# API client with delegated requests parameters
import requests
class APIClient(ConfigurableAPI):
@delegates(requests.get, but=['url'])
def get(self, endpoint, **kwargs):
url = f"{self.base_url}/{endpoint.lstrip('/')}"
return requests.get(url, **kwargs)
@delegates(requests.post, but=['url'])
def post(self, endpoint, **kwargs):
url = f"{self.base_url}/{endpoint.lstrip('/')}"
return requests.post(url, **kwargs)
# Plugin system with dynamic method delegation
class PluginSystem:
def __init__(self):
self.plugins = {}
def register_plugin(self, name, plugin_class):
self.plugins[name] = plugin_class
# Delegate plugin methods to main system
for method_name in dir(plugin_class):
if not method_name.startswith('_'):
method = getattr(plugin_class, method_name)
if callable(method):
self.add_delegated_method(name, method_name, method)
@delegates(lambda: None) # Will be updated dynamically
def add_delegated_method(self, plugin_name, method_name, method):
def wrapper(*args, **kwargs):
plugin_instance = self.plugins[plugin_name]()
return getattr(plugin_instance, method_name)(*args, **kwargs)
# Add method with proper delegation
wrapper = delegates(method)(wrapper)
setattr(self, f"{plugin_name}_{method_name}", wrapper)
# Usage example
class DatabasePlugin:
def connect(self, host='localhost', port=5432, database='mydb'):
return f"Connected to {host}:{port}/{database}"
def query(self, sql, params=None, timeout=30):
return f"Executed: {sql}"
system = PluginSystem()
system.register_plugin('db', DatabasePlugin)
# Now system has db_connect and db_query methods with proper signatures
result = system.db_connect(host='production.db', port=5433)
query_result = system.db_query("SELECT * FROM users", timeout=60)Install with Tessl CLI
npx tessl i tessl/pypi-fastcoredocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10