[Python-ideas] Multiple arguments for decorators
Andrew Barnert
abarnert at yahoo.com
Tue Dec 1 07:01:06 EST 2015
On Dec 1, 2015, at 01:56, Nick Coghlan <ncoghlan at gmail.com> wrote:
>
> On 1 December 2015 at 12:52, Andrew Barnert via Python-ideas
> <python-ideas at python.org> wrote:
>> It seems a lot cleaner to just pass a class to the decorator:
>>
>> class Property:
>> def __init__(self, cls):
>> self.fget = getattr(cls, 'fget', None)
>> self.fset = getattr(cls, 'fset', None)
>> self.fdel = getattr(cls, 'fdel', None)
>> self.doc = getattr(cls, '__doc__', None)
>> # everything below this point is exactly the same as the
>> # existing implementation in the descriptor HOWTO (or
>> # the C implementation in descrobject.c).
>>
>> class Foo:
>> def __init__(self):
>> self._x = 42
>> @Property
>> class x:
>> def fget(self):
>> return self._x
>> def fset(self, value):
>> self._x = value
>> def fdel(self):
>> del self._x
>
> I'm not following this discussion closely, but saw a reference to "Why
> not just use a class?" in one of the later posts, and hence went
> looking for the specific post suggesting that (since it's a question
> with a specific-but-not-obvious answer).
>
> A class based approach like the one suggested here came up in the
> previous discussion that gave us the current syntax:
I don't know the exact timing here, but I'm willing to bet that at the time that discussion happened:
1. Python didn't have class decorators yet, and the very notion was seen as obscure and unnecessary.
2. Inner and nested classes were an unfamiliar feature that almost no other major language supported, rather than being fundamental tools in Java. (Which means nobody had yet had to face the "which self" question, for example.)
3. Modern functional/OO hybrids like F# and Scala didn't exist (and OCaml was barely known outside specific academic circles), so the only familiar notion of dynamic class creation was the SmallTalk style, rather than functions that return classes (like namedtuple--although its implementation is more Tcl-ish than anything, the interface is still all about using types as first-class values).
So, I'm not sure the objections hold as well today as they did back then. But I'll admit that they're certainly not empty; I'll have to sleep on them, then play with it and see how it really looks.
Meanwhile, the point you make at the end is a good one:
> Beyond that, property and any similar decorators are really just a
> special case of a higher order function accepting multiple distinct
> functions as inputs, and Python's syntax generally isn't structured to
> make that easy to do.
When I think about how you'd do this even in a language like JS, I see your point. Using Pythonesque syntax:
x = property({
'doc': 'x',
'fget': lambda self: self._x,
'fset': lambda self, value: self._x = value, # oops
'fdel': lambda self: del self._x # oops
})
The fact that we need def means that we need a suite, not an expression.
And I think you're right that this all of the proposals using a suite have basically the same problem as using a class. But they have an additional problem: a class is a standard way to wrap up a bunch of function definitions in a single "thing", and the other suggestions aren't, and Python doesn't have anything else that is.
The one obvious alternative is to use a function instead of a class. Maybe something like this:
@property
def x():
"""x"""
def fget(self): return self._x
def fset(self, value): self._x = value
def fdel(self, value): del self._x
return fget, fset, fdel
... where property is something like:
def property(propfunc):
args = propfunc()
doc = args[3] if len(args) > 3 else propfunc.__doc__
return existing_property(*args)
The only practical problem here is that you need that extra name-repeating return at the end of each @property (and compared to needing to decorate three separate functions, that doesn't seem bad). You could maybe avoid the name repetition by using "return var()" and having property use **ret, but that seems a bit opaque and hacky. (You could even avoid the return entirely by having it use reflective magic to extract the constants from the function's code object, but that seems _really_ hacky.)
The conceptual problem is that a decorator that actually calls its function and uses its return value rather than the function itself is pretty weird--a lot weirder than a class decorator, at least to me. It still seems better to me than any of the scope-based alternatives besides class, but a function as a container of functions doesn't feel as right as a class.
But if no variation on either of these feels right enough, I think the current design is the best we're going to do. And it really isn't that bad in the first place. It's not like it's hard to tell what the setter is attached to. And repeating the property name up to two times in the secondary decorators is hardly terrible.
One more possibility, if property is all we care about, is dedicated syntax. Plenty of other languages have it:
property x:
"""x"""
def fget(self): return self._x
def fset(self, value): self._x = value
I'll bet you could get pretty close to this with MacroPy (and Haoyi has probably already done it for you)...
More information about the Python-ideas
mailing list