Skip to content

Instantly share code, notes, and snippets.

@mgarod
Last active September 30, 2023 00:13
Show Gist options
  • Save mgarod/09aa9c3d8a52a980bd4d738e52e5b97a to your computer and use it in GitHub Desktop.
Save mgarod/09aa9c3d8a52a980bd4d738e52e5b97a to your computer and use it in GitHub Desktop.
Dynamically add a method to a class
from functools import wraps # This convenience func preserves name and docstring
class A:
pass
def add_method(cls):
def decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
return func(*args, **kwargs)
setattr(cls, func.__name__, wrapper)
# Note we are not binding func, but wrapper which accepts self but does exactly the same as func
return func # returning func means func can still be used normally
return decorator
# No trickery. Class A has no methods nor variables.
a = A()
try:
a.foo()
except AttributeError as ae:
print(f'Exception caught: {ae}') # 'A' object has no attribute 'foo'
try:
a.bar('The quick brown fox jumped over the lazy dog.')
except AttributeError as ae:
print(f'Exception caught: {ae}') # 'A' object has no attribute 'bar'
# Non-decorator way (note the function must accept self)
# def foo(self):
# print('hello world!')
# setattr(A, 'foo', foo)
# def bar(self, s):
# print(f'Message: {s}')
# setattr(A, 'bar', bar)
# Decorator can be written to take normal functions and make them methods
@add_method(A)
def foo():
print('hello world!')
@add_method(A)
def bar(s):
print(f'Message: {s}')
a.foo()
a.bar('The quick brown fox jumped over the lazy dog.')
print(a.foo) # <bound method foo of <__main__.A object at {ADDRESS}>>
print(a.bar) # <bound method bar of <__main__.A object at {ADDRESS}>>
# foo and bar are still usable as functions
foo()
bar('The quick brown fox jumped over the lazy dog.')
print(foo) # <function foo at {ADDRESS}>
print(bar) # <function bar at {ADDRESS}>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment