[Python-ideas] make __closure__ writable

Yury Selivanov yselivanov.ml at gmail.com
Tue Mar 20 15:56:10 CET 2012


Because usually you write decorators as functions, not classes.  And
when you do the former style, you usually do it in the following way:

def decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    functools.wraps(wrapper, func)
    return wrapper

Now, let's use it:

@decorator
def some_func(): pass

OK.  At this point, 'some_func' object has a '__wrapped__' attribute,
that points to original 'some_func' function.  But whatever you write
to 'some_func.__wrapped__' won't change anything, as the 'wrapper'
will continue to call old 'some_func'.  Instead of assigning something
to __wrapped__, we need to change it in-place, by doing 
'__wrapped__.__closure__ = new_closure'.

On 2012-03-20, at 10:43 AM, Mark Shannon wrote:

> Yury Selivanov wrote:
>> I did provide such example earlier in this thread.  I'm copying and
>> pasting it to this mail.  Please read the example carefully, as it
>> explains why returning new types.FunctionType() is not enough.
>> ----
>> Yes, your approach will work if your decorator is the only one applied.
>> But, as I said, if you have many of them (see below), you can't just
>> return a new function out of your decorator, you need to change the
>> underlying "in-place".  Consider the following:
>> def modifier(func):
>> orig_func = func
>> while func.__wrapped__:
>>   func = func.__wrapped__
>> # patch func.__code__ and func.__closure__
>> return orig_func # no need to wrap anything
>> def some_decorator(func):
>> def wrapper(*args, **kwargs):
>>     # some code
>>     return func(*args, **kwargs)
>> functools.wraps(wrapper, func)
>> return wrapper
>> @modifier
>> @some_decorator
>> def foo():
>> # this code needs to be verified/augmented/etc
>> So, in the above snippet, if you don't want to discard the
>> @some_decorator by returning a new function object, you need to modify the 'foo' from the @modifier.
>> In a complex framework, where you can't guarantee that your magic
>> decorator will always be called first, rewriting the __closure__ attribute is the only way.  
> 
> So why won't this work?
> 
> def f_with_new_closure(f, closure):
>    return types.FunctionType(f.__code__,
>                              f.__globals__,
>                              f.__name__,
>                              f.__defaults__,
>                              closure)
> 
> def modifier(func, closure):
>    if func.__wrapped__:
> 	while func.__wrapped__.__wrapped__:
> 	    func = func.__wrapped__
> 	func.__wrapped__ = f_with_new_closure(func.__wrapped__,
>                                              closure)
>    else:
>        return f_with_new_closure(func, closure)
>    if func.__wrapped__:
> 	return f_with_new_closure(func,
> 		   f_with_new_closure(func.__wrapped__))
>    else:
> 	return f_with_new_closure(func, closure)
> 
> Cheers,
> Mark.
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas




More information about the Python-ideas mailing list