[Python-ideas] 'Injecting' objects as function-local constants

Steven D'Aprano steve at pearwood.info
Fri Jun 17 05:21:03 CEST 2011


Jan Kaliszewski wrote:
> Steven D'Aprano dixit (2011-06-16, 10:46):
> 
>> Making locals unrebindable is a change of semantics that is far
>> beyond anything I've been discussed here. This will be a big enough
>> change without overloading it with changes that will be even more
>> controversial!
> 
> That variant (#1) would be simply shortcut for a closure application
> -- nothing really new.
> 
>     def factory(n):
>         """Today's Python example."""
>         closuring = 'foo'
>         def func(m):
>             s = min(n, m) * closuring
>             # here you also cannot rebind 'closuring' because is has
>             # been referenced above

The error occurs BEFORE the rebinding attempt. You get UnboundLocalError 
when you attempt to execute min(n, m), not when rebinding the closure 
variable. This is a side-effect of the compiler's rule "if you see an 
assignment to a variable, make it a local", that is all.

You can rebind closuring if you tell the compiler that it isn't a local 
variable:

 >>> def factory(n):
...     closuring = 'foo'
...     def func(m):
...         nonlocal closuring
...         s = min(n, m)*closuring
...         closuring = 'spam'
...         return s + closuring
...     return func
...
 >>> f = factory(10)
 >>> f(3)
'foofoofoospam'


In that regard, closure variables are no different from globals. You 
wouldn't say that global are unrebindable because of this:

 >>> def f():
...     print(x)
...     x = 1
...
 >>> f()
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment


The situation is very similar.


>> Introducing magic syntax that is recognised by the compiler but
>> otherwise is not usable as a function is completely unacceptable.
> 
> Because of?... And it would not be more 'magic' than any other
> language syntax construct -- def, class, decorating with their @, *, **
> arguments etc.  The fact that such a new syntax would be similar to
> something already known and well settled (decorator function application
> syntax) would be rather an andantage than a drawback.

But the problem is that it is deceptively similar: it only *seems* 
similar, while the differences are profound.

super() is the only magic function I know of in Python, and that change 
was controversial, hard to implement, and fragile. super() is special 
cased by the compiler and works in ways that no other function can do. 
Hence it is magic. I can't imagine that Guido will agree to a second 
example, at least not without a blindingly obvious benefit.

You can't reason about super()'s behaviour like any other function. 
Things which should work if super() were non-magical break, such as 
aliasing:

my_super = super  # Just another name for the same function.

class MyList(list):
     def __init__(self, *args):
         my_super().__init__(*args)
         self.attr = None

 >>> MyList([])
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<stdin>", line 3, in __init__
SystemError: super(): __class__ cell not found


And wrapping:


_saved_super = super
def super(*args, **kwargs):
     print(args, kwargs)
     return _saved_super(*args, **kwargs)

class MyList(list):
     def __init__(self, *args):
         super().__init__(*args)
         self.attr = None


 >>> MyList([])
() {}
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
   File "<stdin>", line 3, in __init__
   File "<stdin>", line 3, in super
SystemError: super(): no arguments



Only the exact incantation of built-in super() inside a method of a 
class works. As I said: magic. (Although you can supply all the 
arguments for super manually, which is tricky to get right but non-magic.)


You are proposing that inject should also be magic: only the exact 
incantation @inject(...) directly above a function will work. We won't 
be able to wrap inject in another function, or alias it, or use it 
without the @ syntax. inject() isn't really a decorator, although it 
superficially looks like one. It's actually a compiler directive.

If you want to propose #pragma for Python, do so, but don't call it a 
decorator!

Most importantly, we won't be able to apply it to functions that already 
exist:

list_of_functions = [spam, ham, cheese]  # defined elsewhere
decorator = inject(a=1)
decorated = [decorator(f) for f in list_of_functions]

will fail. I consider this completely unacceptable.




-- 
Steven




More information about the Python-ideas mailing list