Understanding this memoize decorator - python

I am having trouble understanding this memoize decorator
def method(func):
"""
Decorator for caching parameterless bound method
"""
key = '_memoize_%s' % func.__name__
#wraps(func)
def wrapper(self):
if not hasattr(self, key):
setattr(self, key, func(self))
return getattr(self, key)
return wrapper
Let's say I have:
#method
def add(x,y):
return x+y
Is it attaching a key _memoize_add to the tuple (x,y), since that's what's being passed to the wrapper.

The decorator stores the return value of a method as a private attribute. So it will only work with a class instance, and not with an ordinary function.
The func argument of the decorator is the method it is wrapping, and the returned wrapper function will end up being called instead of the method. When the wrapper is called, its self argument will be the instance of the class, and so the settattr call will cache the result of func as a private attribute named by key. After that, all further calls will return the cached value of the key attribute instead.
Here's a simple test that shows how it could be used:
import random
from functools import wraps
def method(func):
key = '_memoize_%s' % func.__name__
#wraps(func)
def wrapper(self):
if not hasattr(self, key):
setattr(self, key, func(self))
return getattr(self, key)
return wrapper
class Test(object):
#method
def cached(self):
return random.random()
def uncached(self):
return random.random()
t = Test()
for x in range(3):
print('cached: %s' % t.cached())
print('uncached: %s' % t.uncached())
print(t.__dict__)
Output:
cached: 0.6594806157188309
uncached: 0.2492466307551897
cached: 0.6594806157188309
uncached: 0.08718572660830726
cached: 0.6594806157188309
uncached: 0.5501638352647334
{'_memoize_cached': 0.6594806157188309}

Related

How to dynamically remove a decorator from a function?

I'd like to activate or deactivate a "cache" in some class method during execution.
I found a way to activate it with something like that:
(...)
setattr(self, "_greedy_function", my_cache_decorator(self._cache)(getattr(self, "_greedy_function")))
(...)
where self._cache is a cache object of my own that stores the results of self._greedy_function.
It's working fine but now what if I want to deactivate the cache and "undecorate" _greedy_function?
I see a possible solution, storing the reference of _greedy_function before decorating it but maybe there is a way to retrieve it from the decorated function and that would be better.
As requested, here are the decorator and the cache object I'm using to cache results of my class functions:
import logging
from collections import OrderedDict, namedtuple
from functools import wraps
logging.basicConfig(
level=logging.WARNING,
format='%(asctime)s %(name)s %(levelname)s %(message)s'
)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
CacheInfo = namedtuple("CacheInfo", "hits misses maxsize currsize")
def lru_cache(cache):
"""
A replacement for functools.lru_cache() build on a custom LRU Class.
It can cache class methods.
"""
def decorator(func):
logger.debug("assigning cache %r to function %s" % (cache, func.__name__))
#wraps(func)
def wrapped_func(*args, **kwargs):
try:
ret = cache[args]
logger.debug("cached value returned for function %s" % func.__name__)
return ret
except KeyError:
try:
ret = func(*args, **kwargs)
except:
raise
else:
logger.debug("cache updated for function %s" % func.__name__)
cache[args] = ret
return ret
return wrapped_func
return decorator
class LRU(OrderedDict):
"""
Custom implementation of a LRU cache, build on top of an Ordered dict.
"""
__slots__ = "_hits", "_misses", "_maxsize"
def __new__(cls, maxsize=128):
if maxsize is None:
return None
return super().__new__(cls, maxsize=maxsize)
def __init__(self, maxsize=128, *args, **kwargs):
self.maxsize = maxsize
self._hits = 0
self._misses = 0
super().__init__(*args, **kwargs)
def __getitem__(self, key):
try:
value = super().__getitem__(key)
except KeyError:
self._misses += 1
raise
else:
self.move_to_end(key)
self._hits += 1
return value
def __setitem__(self, key, value):
super().__setitem__(key, value)
if len(self) > self._maxsize:
oldest, = next(iter(self))
del self[oldest]
def __delitem__(self, key):
try:
super().__delitem__((key,))
except KeyError:
pass
def __repr__(self):
return "<%s object at %s: %s>" % (self.__class__.__name__, hex(id(self)), self.cache_info())
def cache_info(self):
return CacheInfo(self._hits, self._misses, self._maxsize, len(self))
def clear(self):
super().clear()
self._hits, self._misses = 0, 0
#property
def maxsize(self):
return self._maxsize
#maxsize.setter
def maxsize(self, maxsize):
if not isinstance(maxsize, int):
raise TypeError
elif maxsize < 2:
raise ValueError
elif maxsize & (maxsize - 1) != 0:
logger.warning("LRU feature performs best when maxsize is a power-of-two, maybe.")
while maxsize < len(self):
oldest, = next(iter(self))
print(oldest)
del self[oldest]
self._maxsize = maxsize
Edit: I've updated my code using the __wrapped__ attribute suggested in comments and it's working fine! The whole thing is here: https://gist.github.com/fbparis/b3ddd5673b603b42c880974b23db7cda (kik.set_cache() method...)
You have made things too complicated. The decorator can be simply removed by del self._greedy_function. There's no need for a __wrapped__ attribute.
Here is a minimal implementation of the set_cache and unset_cache methods:
class LRU(OrderedDict):
def __init__(self, maxsize=128, *args, **kwargs):
# ...
self._cache = dict()
super().__init__(*args, **kwargs)
def _greedy_function(self):
time.sleep(1)
return time.time()
def set_cache(self):
self._greedy_function = lru_cache(self._cache)(getattr(self, "_greedy_function"))
def unset_cache(self):
del self._greedy_function
Using your decorator lru_cache, here are the results
o = LRU()
o.set_cache()
print('First call', o._greedy_function())
print('Second call',o._greedy_function()) # Here it prints out the cached value
o.unset_cache()
print('Third call', o._greedy_function()) # The cache is not used
Outputs
First call 1552966668.735025
Second call 1552966668.735025
Third call 1552966669.7354007
Modern versions of functools.wraps install the original function as an attribute __wrapped__ on the wrappers they create. (One could search through __closure__ on the nested functions typically used for the purpose, but other types could be used as well.) It’s reasonable to expect whatever wrapper to follow this convention.
An alternative is to have a permanent wrapper that can be controlled by a flag, so that it can be enabled and disabled without removing and reinstating it. This has the advantage that the wrapper can keep its state (here, the cached values). The flag can be a separate variable (e.g., another attribute on an object bearing the wrapped function, if any) or can be an attribute on the wrapper itself.

How to count method calls, but not attribute access?

I'm preparing a parent class for multiple classes and from its perspective I need to be aware if a particular instance method was called or not.
I started working on something like this:
from collections import defaultdict
class ParentClass:
def __init__(self, ):
self.call_count = defaultdict(int)
def __getattribute__(self, item):
if item != 'call_count':
self.call_count[item] += 1
return object.__getattribute__(self, item)
class ChildClass(ParentClass):
def child_method(self):
pass
Unfortunately, the call_count also includes accessing of the field, without calling it:
ob = ChildClass()
ob.child_method()
ob.child_method
assert ob.call_count['child_method'] == 1 # it's 2
How can I, from object instance detect that its field is being called (not only accessed)?
A (python3) solution using a custom metaclass:
from collections import defaultdict
from functools import wraps
import inspect
def count_calls(func):
name = func.__name__
#wraps(func)
def wrapper(self, *args, **kwargs):
# creates the instance counter if necessary
counter = getattr(self, "_calls_counter", None)
if counter is None:
counter = self._calls_counter = defaultdict(int)
counter[name] += 1
return func(self, *args, **kwargs)
wrapper._is_count_call_wrapper = True
return wrapper
class CallCounterType(type):
def __new__(cls, name, bases, attrs):
for name, attr in attrs.items():
if not inspect.isfunction(attr):
# this will weed out any callable that is not truly a function
# (including nested classes, classmethods and staticmethods)
continue
try:
argspec = inspect.getargspec(attr)
except TypeError:
# "unsupported callable" - can't think of any callable
# that might have made it's way until this point and not
# be supported by getargspec but well...
continue
if not argspec.args:
# no argument so it can't be an instancemethod
# (to be exact: a function designed to be used as instancemethod)
# Here again I wonder which function could be found here that
# doesn't take at least `self` but anyway...
continue
if getattr(attr, "_is_count_call_wrapper", False):
# not sure why we would have an already wrapped func here but etc...
continue
# ok, it's a proper function, it takes at least one positional arg,
# and it's not already been wrapped, we should be safe
attrs[name] = count_calls(attr)
return super(CallCounterType, cls).__new__(cls, name, bases, attrs)
class ParentClass(metaclass=CallCounterType):
pass
class ChildClass(ParentClass):
def child_method(self):
pass
Note that storing call counts on the instance only allow to count instancemethods calls, obviously...
The following is a little "dirty", but wrapping all methods with a counting function could cover what you need:
from collections import defaultdict
class ParentClass:
def __init__(self):
self.call_count = defaultdict(int)
for attr in dir(self):
if not attr.startswith('__') and attr != '_wrapper_factory':
callback = getattr(self, attr)
if hasattr(callback, '__call__'):
setattr(self, attr, self._wrapper_factory(callback))
def _wrapper_factory(self, callback):
def wrapper(*args, **kwargs):
self.call_count[callback.__name__] += 1
callback(*args, **kwargs)
return wrapper
class ChildClass(ParentClass):
def child_method(self):
pass
ob = ChildClass()
ob.child_method()
ob.child_method
assert ob.call_count['child_method'] == 1
Should give no assertion errors.

Python: Accessing class instance's `self` in decorator [duplicate]

How do I pass a class field to a decorator on a class method as an argument? What I want to do is something like:
class Client(object):
def __init__(self, url):
self.url = url
#check_authorization("some_attr", self.url)
def get(self):
do_work()
It complains that self does not exist for passing self.url to the decorator. Is there a way around this?
Yes. Instead of passing in the instance attribute at class definition time, check it at runtime:
def check_authorization(f):
def wrapper(*args):
print args[0].url
return f(*args)
return wrapper
class Client(object):
def __init__(self, url):
self.url = url
#check_authorization
def get(self):
print 'get'
>>> Client('http://www.google.com').get()
http://www.google.com
get
The decorator intercepts the method arguments; the first argument is the instance, so it reads the attribute off of that. You can pass in the attribute name as a string to the decorator and use getattr if you don't want to hardcode the attribute name:
def check_authorization(attribute):
def _check_authorization(f):
def wrapper(self, *args):
print getattr(self, attribute)
return f(self, *args)
return wrapper
return _check_authorization
A more concise example might be as follows:
#/usr/bin/env python3
from functools import wraps
def wrapper(method):
#wraps(method)
def _impl(self, *method_args, **method_kwargs):
method_output = method(self, *method_args, **method_kwargs)
return method_output + "!"
return _impl
class Foo:
#wrapper
def bar(self, word):
return word
f = Foo()
result = f.bar("kitty")
print(result)
Which will print:
kitty!
from re import search
from functools import wraps
def is_match(_lambda, pattern):
def wrapper(f):
#wraps(f)
def wrapped(self, *f_args, **f_kwargs):
if callable(_lambda) and search(pattern, (_lambda(self) or '')):
f(self, *f_args, **f_kwargs)
return wrapped
return wrapper
class MyTest(object):
def __init__(self):
self.name = 'foo'
self.surname = 'bar'
#is_match(lambda x: x.name, 'foo')
#is_match(lambda x: x.surname, 'foo')
def my_rule(self):
print 'my_rule : ok'
#is_match(lambda x: x.name, 'foo')
#is_match(lambda x: x.surname, 'bar')
def my_rule2(self):
print 'my_rule2 : ok'
test = MyTest()
test.my_rule()
test.my_rule2()
ouput:
my_rule2 : ok
Another option would be to abandon the syntactic sugar and decorate in the __init__ of the class.
def countdown(number):
def countdown_decorator(func):
def func_wrapper():
for index in reversed(range(1, number+1)):
print(index)
func()
return func_wrapper
return countdown_decorator
class MySuperClass():
def __init__(self, number):
self.number = number
self.do_thing = countdown(number)(self.do_thing)
def do_thing(self):
print('im doing stuff!')
myclass = MySuperClass(3)
myclass.do_thing()
which would print
3
2
1
im doing stuff!
I know this issue is quite old, but the below workaround hasn't been proposed before. The problem here is that you can't access self in a class block, but you can in a class method.
Let's create a dummy decorator to repeat a function some times.
import functools
def repeat(num_rep):
def decorator_repeat(func):
#functools.wraps(func)
def wrapper_repeat(*args, **kwargs):
for _ in range(num_rep):
value = func(*args, **kwargs)
return
return wrapper_repeat
return decorator_repeat
class A:
def __init__(self, times, name):
self.times = times
self.name = name
def get_name(self):
#repeat(num_rep=self.times)
def _get_name():
print(f'Hi {self.name}')
_get_name()
I know this is an old question, but this solution has not been mentioned yet, hopefully it may help someone even today, after 8 years.
So, what about wrapping a wrapper? Let's assume one cannot change the decorator neither decorate those methods in init (they may be #property decorated or whatever). There is always a possibility to create custom, class-specific decorator that will capture self and subsequently call the original decorator, passing runtime attribute to it.
Here is a working example (f-strings require python 3.6):
import functools
# imagine this is at some different place and cannot be changed
def check_authorization(some_attr, url):
def decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"checking authorization for '{url}'...")
return func(*args, **kwargs)
return wrapper
return decorator
# another dummy function to make the example work
def do_work():
print("work is done...")
###################
# wrapped wrapper #
###################
def custom_check_authorization(some_attr):
def decorator(func):
# assuming this will be used only on this particular class
#functools.wraps(func)
def wrapper(self, *args, **kwargs):
# get url
url = self.url
# decorate function with original decorator, pass url
return check_authorization(some_attr, url)(func)(self, *args, **kwargs)
return wrapper
return decorator
#############################
# original example, updated #
#############################
class Client(object):
def __init__(self, url):
self.url = url
#custom_check_authorization("some_attr")
def get(self):
do_work()
# create object
client = Client(r"https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments")
# call decorated function
client.get()
output:
checking authorisation for 'https://stackoverflow.com/questions/11731136/class-method-decorator-with-self-arguments'...
work is done...
You can't. There's no self in the class body, because no instance exists. You'd need to pass it, say, a str containing the attribute name to lookup on the instance, which the returned function can then do, or use a different method entirely.
It will be very useful to have a general-purpose utility, that can turn any decorator for functions, into decorator for methods. I thought about it for an hour, and actually come up with one:
from typing import Callable
Decorator = Callable[[Callable], Callable]
def decorate_method(dec_for_function: Decorator) -> Decorator:
def dec_for_method(unbounded_method) -> Callable:
# here, `unbounded_method` will be a unbounded function, whose
# invokation must have its first arg as a valid `self`. When it
# return, it also must return an unbounded method.
def decorated_unbounded_method(self, *args, **kwargs):
#dec_for_function
def bounded_method(*args, **kwargs):
return unbounded_method(self, *args, **kwargs)
return bounded_method(*args, **kwargs)
return decorated_unbounded_method
return dec_for_method
The usage is:
# for any decorator (with or without arguments)
#some_decorator_with_arguments(1, 2, 3)
def xyz(...): ...
# use it on a method:
class ABC:
#decorate_method(some_decorator_with_arguments(1, 2, 3))
def xyz(self, ...): ...
Test:
def dec_for_add(fn):
"""This decorator expects a function: (x,y) -> int.
If you use it on a method (self, x, y) -> int, it will fail at runtime.
"""
print(f"decorating: {fn}")
def add_fn(x,y):
print(f"Adding {x} + {y} by using {fn}")
return fn(x,y)
return add_fn
#dec_for_add
def add(x,y):
return x+y
add(1,2) # OK!
class A:
#dec_for_add
def f(self, x, y):
# ensure `self` is still a valid instance
assert isinstance(self, A)
return x+y
# TypeError: add_fn() takes 2 positional arguments but 3 were given
# A().f(1,2)
class A:
#decorate_method(dec_for_add)
def f(self, x, y):
# ensure `self` is still a valid instance
assert isinstance(self, A)
return x+y
# Now works!!
A().f(1,2)

Python functools.wraps equivalent for classes

When defining a decorator using a class, how do I automatically transfer over__name__, __module__ and __doc__? Normally, I would use the #wraps decorator from functools. Here's what I did instead for a class (this is not entirely my code):
class memoized:
"""Decorator that caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):
super().__init__()
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(*args)
self.cache[args] = value
return value
except TypeError:
# uncacheable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.func(*args)
def __repr__(self):
return self.func.__repr__()
def __get__(self, obj, objtype):
return functools.partial(self.__call__, obj)
__doc__ = property(lambda self:self.func.__doc__)
__module__ = property(lambda self:self.func.__module__)
__name__ = property(lambda self:self.func.__name__)
Is there a standard decorator to automate the creation of name module and doc? Also, to automate the get method (I assume that's for creating bound methods?) Are there any missing methods?
Everyone seems to have missed the obvious solution. Using functools.update_wrapper:
>>> import functools
>>> class memoized(object):
"""Decorator that caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):
self.func = func
self.cache = {}
functools.update_wrapper(self, func) ## TA-DA! ##
def __call__(self, *args):
pass # Not needed for this demo.
>>> #memoized
def fibonacci(n):
"""fibonacci docstring"""
pass # Not needed for this demo.
>>> fibonacci
<__main__.memoized object at 0x0156DE30>
>>> fibonacci.__name__
'fibonacci'
>>> fibonacci.__doc__
'fibonacci docstring'
I'm not aware of such things in stdlib, but we can create our own if we need to.
Something like this can work :
from functools import WRAPPER_ASSIGNMENTS
def class_wraps(cls):
"""Update a wrapper class `cls` to look like the wrapped."""
class Wrapper(cls):
"""New wrapper that will extend the wrapper `cls` to make it look like `wrapped`.
wrapped: Original function or class that is beign decorated.
assigned: A list of attribute to assign to the the wrapper, by default they are:
['__doc__', '__name__', '__module__', '__annotations__'].
"""
def __init__(self, wrapped, assigned=WRAPPER_ASSIGNMENTS):
self.__wrapped = wrapped
for attr in assigned:
setattr(self, attr, getattr(wrapped, attr))
super().__init__(wrapped)
def __repr__(self):
return repr(self.__wrapped)
return Wrapper
Usage:
#class_wraps
class memoized:
"""Decorator that caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):
super().__init__()
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(*args)
self.cache[args] = value
return value
except TypeError:
# uncacheable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.func(*args)
def __get__(self, obj, objtype):
return functools.partial(self.__call__, obj)
#memoized
def fibonacci(n):
"""fibonacci docstring"""
if n in (0, 1):
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci)
print("__doc__: ", fibonacci.__doc__)
print("__name__: ", fibonacci.__name__)
Output:
<function fibonacci at 0x14627c0>
__doc__: fibonacci docstring
__name__: fibonacci
EDIT:
And if you are wondering why this wasn't included in the stdlib is because you can
wrap your class decorator in a function decorator and use functools.wraps like this:
def wrapper(f):
memoize = memoized(f)
#functools.wraps(f)
def helper(*args, **kws):
return memoize(*args, **kws)
return helper
#wrapper
def fibonacci(n):
"""fibonacci docstring"""
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
Turns out there's a straightforward solution using functools.wraps itself:
import functools
def dec(cls):
#functools.wraps(cls, updated=())
class D(cls):
decorated = 1
return D
#dec
class C:
"""doc"""
print(f'{C.__name__=} {C.__doc__=} {C.__wrapped__=}')
$ python3 t.py
C.__name__='C' C.__doc__='doc' C.__wrapped__=<class '__main__.C'>
Note that updated=() is needed to prevent an attempt to update the class's __dict__ (this output is without updated=()):
$ python t.py
Traceback (most recent call last):
File "t.py", line 26, in <module>
class C:
File "t.py", line 20, in dec
class D(cls):
File "/usr/lib/python3.8/functools.py", line 57, in update_wrapper
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
AttributeError: 'mappingproxy' object has no attribute 'update'
I needed something that would wrap both classes and functions and wrote this:
def wrap_is_timeout(base):
'''Adds `.is_timeout=True` attribute to objects returned by `base()`.
When `base` is class, it returns a subclass with same name and adds read-only property.
Otherwise, it returns a function that sets `.is_timeout` attribute on result of `base()` call.
Wrappers make best effort to be transparent.
'''
if inspect.isclass(base):
class wrapped(base):
is_timeout = property(lambda _: True)
for k in functools.WRAPPER_ASSIGNMENTS:
v = getattr(base, k, _MISSING)
if v is not _MISSING:
try:
setattr(wrapped, k, v)
except AttributeError:
pass
return wrapped
#functools.wraps(base)
def fun(*args, **kwargs):
ex = base(*args, **kwargs)
ex.is_timeout = True
return ex
return fun
All we really need to do is modify the behavior of the decorator so that it is "hygienic", i.e. it is attribute-preserving.
#!/usr/bin/python3
def hygienic(decorator):
def new_decorator(original):
wrapped = decorator(original)
wrapped.__name__ = original.__name__
wrapped.__doc__ = original.__doc__
wrapped.__module__ = original.__module__
return wrapped
return new_decorator
This is ALL you need. In general. It doesn't preserve the signature, but if you really want that you can use a library to do that. I also went ahead and rewrote the memoization code so that it works on keyword arguments as well. Also there was a bug where failure to convert it to a hashable tuple would make it not work in 100% of cases.
Demo of rewritten memoized decorator with #hygienic modifying its behavior. memoized is now a function that wraps the original class, though you can (like the other answer) write a wrapping class instead, or even better, something which detects if it's a class and if so wraps the __init__ method.
#hygienic
class memoized:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args, **kw):
try:
key = (tuple(args), frozenset(kw.items()))
if not key in self.cache:
self.cache[key] = self.func(*args,**kw)
return self.cache[key]
except TypeError:
# uncacheable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.func(*args,**kw)
In action:
#memoized
def f(a, b=5, *args, keyword=10):
"""Intact docstring!"""
print('f was called!')
return {'a':a, 'b':b, 'args':args, 'keyword':10}
x=f(0)
#OUTPUT: f was called!
print(x)
#OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args': ()}
y=f(0)
#NO OUTPUT - MEANS MEMOIZATION IS WORKING
print(y)
#OUTPUT: {'a': 0, 'b': 5, 'keyword': 10, 'args': ()}
print(f.__name__)
#OUTPUT: 'f'
print(f.__doc__)
#OUTPUT: 'Intact docstring!'
Another solution using inheritance:
import functools
import types
class CallableClassDecorator:
"""Base class that extracts attributes and assigns them to self.
By default the extracted attributes are:
['__doc__', '__name__', '__module__'].
"""
def __init__(self, wrapped, assigned=functools.WRAPPER_ASSIGNMENTS):
for attr in assigned:
setattr(self, attr, getattr(wrapped, attr))
super().__init__()
def __get__(self, obj, objtype):
return types.MethodType(self.__call__, obj)
And, usage:
class memoized(CallableClassDecorator):
"""Decorator that caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, function):
super().__init__(function)
self.function = function
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.function(*args)
self.cache[args] = value
return value
except TypeError:
# uncacheable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.function(*args)

Finding a function's parameters in Python

I want to be able to ask a class's __init__ method what it's parameters are. The straightforward approach is the following:
cls.__init__.__func__.__code__.co_varnames[:code.co_argcount]
However, that won't work if the class has any decorators. It will give the parameter list for the function returned by the decorator. I want to get down to the original __init__ method and get those original parameters. In the case of a decorator, the decorator function is going to be found in the closure of the function returned by the decorator:
cls.__init__.__func__.__closure__[0]
However, it is more complicated if there are other things in the closure, which decorators may do from time to time:
def Something(test):
def decorator(func):
def newfunc(self):
stuff = test
return func(self)
return newfunc
return decorator
def test():
class Test(object):
#Something(4)
def something(self):
print Test
return Test
test().something.__func__.__closure__
(<cell at 0xb7ce7584: int object at 0x81b208c>, <cell at 0xb7ce7614: function object at 0xb7ce6994>)
And then I have to decide if I want to the parameters from decorator or the parameters from the original function. The function returned by the decorator could have *args and **kwargs for its parameters. What if there are multiple decorators and I have to decide which is the one I care about?
So what is the best way to find a function's parameters even when the function may be decorated? Also, what is the best way to go down a chain of decorators back to the decorated function?
Update:
Here is effectively how I am doing this right now (names have been changed to protect the identity of the accused):
import abc
import collections
IGNORED_PARAMS = ("self",)
DEFAULT_PARAM_MAPPING = {}
DEFAULT_DEFAULT_PARAMS = {}
class DICT_MAPPING_Placeholder(object):
def __get__(self, obj, type):
DICT_MAPPING = {}
for key in type.PARAMS:
DICT_MAPPING[key] = None
for cls in type.mro():
if "__init__" in cls.__dict__:
cls.DICT_MAPPING = DICT_MAPPING
break
return DICT_MAPPING
class PARAM_MAPPING_Placeholder(object):
def __get__(self, obj, type):
for cls in type.mro():
if "__init__" in cls.__dict__:
cls.PARAM_MAPPING = DEFAULT_PARAM_MAPPING
break
return DEFAULT_PARAM_MAPPING
class DEFAULT_PARAMS_Placeholder(object):
def __get__(self, obj, type):
for cls in type.mro():
if "__init__" in cls.__dict__:
cls.DEFAULT_PARAMS = DEFAULT_DEFAULT_PARAMS
break
return DEFAULT_DEFAULT_PARAMS
class PARAMS_Placeholder(object):
def __get__(self, obj, type):
func = type.__init__.__func__
# unwrap decorators here
code = func.__code__
keys = list(code.co_varnames[:code.co_argcount])
for name in IGNORED_PARAMS:
try: keys.remove(name)
except ValueError: pass
for cls in type.mro():
if "__init__" in cls.__dict__:
cls.PARAMS = tuple(keys)
break
return tuple(keys)
class BaseMeta(abc.ABCMeta):
def __init__(self, name, bases, dict):
super(BaseMeta, self).__init__(name, bases, dict)
if "__init__" not in dict:
return
if "PARAMS" not in dict:
self.PARAMS = PARAMS_Placeholder()
if "DEFAULT_PARAMS" not in dict:
self.DEFAULT_PARAMS = DEFAULT_PARAMS_Placeholder()
if "PARAM_MAPPING" not in dict:
self.PARAM_MAPPING = PARAM_MAPPING_Placeholder()
if "DICT_MAPPING" not in dict:
self.DICT_MAPPING = DICT_MAPPING_Placeholder()
class Base(collections.Mapping):
__metaclass__ = BaseMeta
"""
Dict-like class that uses its __init__ params for default keys.
Override PARAMS, DEFAULT_PARAMS, PARAM_MAPPING, and DICT_MAPPING
in the subclass definition to give non-default behavior.
"""
def __init__(self):
pass
def __nonzero__(self):
"""Handle bool casting instead of __len__."""
return True
def __getitem__(self, key):
action = self.DICT_MAPPING[key]
if action is None:
return getattr(self, key)
try:
return action(self)
except AttributeError:
return getattr(self, action)
def __iter__(self):
return iter(self.DICT_MAPPING)
def __len__(self):
return len(self.DICT_MAPPING)
print Base.PARAMS
# ()
print dict(Base())
# {}
At this point Base reports uninteresting values for the four contants and the dict version of instances is empty. However, if you subclass you can override any of the four, or you can include other parameters to the __init__:
class Sub1(Base):
def __init__(self, one, two):
super(Sub1, self).__init__()
self.one = one
self.two = two
Sub1.PARAMS
# ("one", "two")
dict(Sub1(1,2))
# {"one": 1, "two": 2}
class Sub2(Base):
PARAMS = ("first", "second")
def __init__(self, one, two):
super(Sub2, self).__init__()
self.first = one
self.second = two
Sub2.PARAMS
# ("first", "second")
dict(Sub2(1,2))
# {"first": 1, "second": 2}
Consider this decorator:
def rickroll(old_function):
return lambda junk, junk1, junk2: "Never Going To Give You Up"
class Foo(object):
#rickroll
def bar(self, p1, p2):
return p1 * p2
print Foo().bar(1, 2)
In it, the rickroll decorator takes the bar method, discards it, replaces it with a new function that ignores its differently-named (and possibly numbered!) parameters and instead returns a line from a classic song.
There are no further references to the original function, and the garbage collector can come and remove it any time it likes.
In such a case, I cannot see how you could find the parameter names p1 and p2. In my understanding, even the Python interpreter itself has no idea what they used to be called.

Categories

Resources