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 Base
s 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
What's a Good Equivalent to Subprocess.Check_Call That Returns the Contents of Stdout
Python | Accessing Dll Using Ctypes
Matplotlib Connect Scatterplot Points with Line - Python
How to Erase the File Contents of Text File in Python
No Multiline Lambda in Python: Why Not
How to Print Utf-8 Encoded Text to the Console in Python < 3
How to Search a Word in a Word 2007 .Docx File
How to Ssh Connect Through Python Paramiko with Ppk Public Key
Spark Iteration Time Increasing Exponentially When Using Join
All Synonyms for Word in Python
Distributing My Python Scripts as Jar Files with Jython
Reverse a Get_Dummies Encoding in Pandas
How to Fix "Webdriverexception: Message: Connection Refused"