How to Wrap Every Method of a Class

How to wrap every method of a class?

An elegant way to do it is described in Michael Foord's Voidspace blog in an entry about what metaclasses are and how to use them in the section titled A Method Decorating Metaclass. Simplifying it slightly and applying it to your situation resulted in this:

from functools import wraps
from types import FunctionType

def wrapper(method):
@wraps(method)
def wrapped(*args, **kwargs):
# ... <do something to/with "method" or the result of calling it>
return wrapped

class MetaClass(type):
def __new__(meta, classname, bases, classDict):
newClassDict = {}
for attributeName, attribute in classDict.items():
if isinstance(attribute, FunctionType):
# replace it with a wrapped version
attribute = wrapper(attribute)
newClassDict[attributeName] = attribute
return type.__new__(meta, classname, bases, newClassDict)

class MyClass(object):
__metaclass__ = MetaClass # wrap all the methods
def method1(self, ...):
# ...etc ...

In Python, function/method decorators are just function wrappers plus some syntactic sugar to make using them easy (and prettier).

Python 3 Compatibility Update

The previous code uses Python 2.x metaclass syntax which would need to be translated in order to be used in Python 3.x, however it would then no longer work in the previous version. This means it would need to use:

class MyClass(metaclass=MetaClass)  # apply method-wrapping metaclass
...

instead of:

class MyClass(object):
__metaclass__ = MetaClass # wrap all the methods
...

If desired, it's possible to write code which is compatible with both Python 2.x and 3.x, but doing so requires using a slightly more complicated technique which dynamically creates a new base class that inherits the desired metaclass, thereby avoiding errors due to the syntax differences between the two versions of Python. This is basically what Benjamin Peterson's six module's with_metaclass() function does.

from types import FunctionType
from functools import wraps

def wrapper(method):
@wraps(method)
def wrapped(*args, **kwargs):
print('{!r} executing'.format(method.__name__))
return method(*args, **kwargs)
return wrapped

class MetaClass(type):
def __new__(meta, classname, bases, classDict):
newClassDict = {}
for attributeName, attribute in classDict.items():
if isinstance(attribute, FunctionType):
# replace it with a wrapped version
attribute = wrapper(attribute)
newClassDict[attributeName] = attribute
return type.__new__(meta, classname, bases, newClassDict)

def with_metaclass(meta):
""" Create an empty class with the supplied bases and metaclass. """
return type.__new__(meta, "TempBaseClass", (object,), {})

if __name__ == '__main__':

# Inherit metaclass from a dynamically-created base class.
class MyClass(with_metaclass(MetaClass)):
@staticmethod
def a_static_method():
pass

@classmethod
def a_class_method(cls):
pass

def a_method(self):
pass

instance = MyClass()
instance.a_static_method() # Not decorated.
instance.a_class_method() # Not decorated.
instance.a_method() # -> 'a_method' executing

Wrap all methods of python superclass

One option is to use a metaclass:

class ChildMeta(type):
def __new__(cls, name, bases, dct):
child = super().__new__(cls, name, bases, dct)
for base in bases:
for field_name, field in base.__dict__.items():
if callable(field):
setattr(child, field_name, force_child(field))
return child

class Child(Base, metaclass=ChildMeta):
pass

It will automatically wrap all the Bases methods with your force_child decorator.

How to wrap each method in a class with a context manager?

Here is a metaclass version:

import contextlib
import os
from functools import wraps
from types import FunctionType

ROOT = "/base"

@contextlib.contextmanager
def set_directory(path):
"""
Sets the cwd within the context

Args:
path (Path): The path to the cwd

Yields:
None
"""
origin = os.path.abspath(os.getcwd())
try:
os.chdir(path)
yield
finally:
os.chdir(origin)

def use_chdir_context(f):
@wraps(f)
def wrapped(self, *args, **kwargs):
with set_directory(f"{ROOT}/{self.path}"):
return f(self, *args, **kwargs)
return wrapped

class ChdirContextMetaclass(type):
"""
Wraps all methods in the class with use_chdir_context decorator
"""
def __new__(metacls, name, bases, class_dict):
new_class_dict = {}
for attr_name, attr in class_dict.items():
if isinstance(attr, FunctionType):
attr = use_chdir_context(attr)
new_class_dict[attr_name] = attr
return type.__new__(metacls, name, bases, new_class_dict)

class Hi(metaclass=ChdirContextMetaclass):
path = "hi/1/2/3"

def lovely(self):
print(os.getcwd())

You could also do away with the context manager and have the same thing just in the decorator definition, i.e.:

def use_chdir_context(f):
@wraps(f)
def wrapped(self, *args, **kwargs):
origin = os.path.abspath(os.getcwd())
try:
os.chdir(f"{ROOT}/{self.path}")
return f(self, *args, **kwargs)
finally:
os.chdir(origin)
return wrapped

One advantage of the metaclass version is that the work to decorate the methods of the class happens only once, at "import time", instead of each time you make an instance. So that would be a little more efficient if you are making lots of instances and there are many methods to decorate.

Wrap all class methods using a meta class

When the metaclass __init__ gets called, the class object has already been built so modifying the attributes dict (class_dict in your code) at this stage is totally useless indeed. You want to use setattr instead:

class MyMeta(type):   
def __init__(cls, classname, bases, class_dict):
for attr_name in dir(cls):
if attr_name == "__class__":
# the metaclass is a callable attribute too,
# but we want to leave this one alone
continue
attr = getattr(cls, attr_name)
if hasattr(attr, '__call__'):
attr = wrapper(attr)
setattr(cls, attr_name, attr)

# not need for the `return` here
super(MyMeta, cls).__init__(classname, bases, class_dict)

How can I automatically wrap all the methods of a class?

consoleDebugWrapFunction = (name, fn) ->
->
console.log "#{name} called with arguments: #{[].slice.call(arguments).join ', '}"
fn.apply this, arguments

consoleDebugWrapClass = (Klass) ->
for prop of Klass.prototype
obj = Klass.prototype[prop]
if typeof obj is 'function'
Klass.prototype[prop] = consoleDebugWrapFunction prop, obj

Usage example:

class A
foo: (a, b) ->
a + b

consoleDebugWrapClass A
a = new A
console.log a.foo 3, 6

Output:

foo called with arguments: 3, 6
9

How to wrap a base class method in python

This is what I eventually ended up writing. Had to change the attribute name in MyClass which I didn't want to but could not get it working without that

class RestRetry:
'''
Wrapper over RESTApi class instance with retries
'''
def __init__(self, vm):
self._vm= vm

def __getattr__(self, attrb):
return self.rest_retry(attrb)

def rest_retry(self, attrb):
def _retry(*args, **kwargs):
try:
# class REST API with retries though
result = getattr(self._vm.restapi, attrb)(*args, **kwargs)
except:
self._vm._refresh_restapi_connection()
return getattr(self._vm.restapi, attrb)(*args, **kwargs)
else:
return result
return _retry

And then in MyClass

def __init__(self):
BaseVM.__init__(self)
self._restapi = self.RestRetry(self)

def any_method(self):
self._restapi.hello_world()

How to decorate all functions of a class without typing it over and over for each method?

Decorate the class with a function that walks through the class's attributes and decorates callables. This may be the wrong thing to do if you have class variables that may happen to be callable, and will also decorate nested classes (credits to Sven Marnach for pointing this out) but generally it's a rather clean and simple solution. Example implementation (note that this will not exclude special methods (__init__ etc.), which may or may not be desired):

def for_all_methods(decorator):
def decorate(cls):
for attr in cls.__dict__: # there's propably a better way to do this
if callable(getattr(cls, attr)):
setattr(cls, attr, decorator(getattr(cls, attr)))
return cls
return decorate

Use like this:

@for_all_methods(mydecorator)
class C(object):
def m1(self): pass
def m2(self, x): pass
...

In Python 3.0 and 3.1, callable does not exist. It existed since forever in Python 2.x and is back in Python 3.2 as wrapper for isinstance(x, collections.Callable), so you can use that (or define your own callable replacement using this) in those versions.



Related Topics



Leave a reply



Submit