Multiple arguments for decorators
An idea that I had a couple of times was to have a syntactic way to define multiple arguments for a @decorator. This would be good if you want to, for example, define a property with a getter and a setter. I had a few ideas, but none of them seem to be "the obvious way to do it". I'm including them anyway, but I'd rather see if people are interested in the idea or if it's just something that's not worth it. I'll gladly write a PEP if it gains a lot of interest. Method 1: Add parens around all arguments class Foo: def __init__(self): self._x = 42 @property( def x(self): # getter return self._x def x(self, value): # setter self._x = value def x(self): # deleter del self._x ) This one was my first idea, but with long methods you might very well miss the closing paren, or even wonder "how does the code even run?" Good for small properties like this, bad with longer methods. -1 from me Method 2: Specify how many arguments in the decorator class Foo: def __init__(self): self._x = 42 @3:property def x(self): # getter return self._x def x(self, value): # setter self._x = value def x(self): # deleter del self._x This one would get all three following methods defined (or should it get any value defined -- for example, a None in place of the setter there, would set the setter to None). Implementation-wise, I believe the order is kept while the class is first evaluated, and then often thrown away when it's inserted into the class' __dict__ (I could be wrong, though). This has the advantage of not breaking code already written, as it cannot possibly conflict with an existing name. I'm neutral here. Method 3: Specify arguments using the parameters' names class Foo: def __init__(self): self._x = 42 @property def x:fget(self): return self._x def x:fset(self, value): self._x = value def x:fdel(self): del self._x This has the advantage of being explicit (and the arguments can be swapped since they're named), but the disadvantage that some builtins don't accept kwargs (this would be a good occasion to fix that, though, but that is besides my point). I've got mixed feelings on this one. I'm neutral as well here. What do you guys think? Is this a good idea, or is it not? Thank you for your time!-Emanuel
On Tue, Dec 1, 2015 at 12:07 PM, Emanuel Barry <vgr255@live.ca> wrote:
An idea that I had a couple of times was to have a syntactic way to define multiple arguments for a @decorator. This would be good if you want to, for example, define a property with a getter and a setter. I had a few ideas, but none of them seem to be "the obvious way to do it". I'm including them anyway, but I'd rather see if people are interested in the idea or if it's just something that's not worth it. I'll gladly write a PEP if it gains a lot of interest.
Other than @property, are there any use-cases you know of?
Method 1: Add parens around all arguments
Definitely -1 on this syntax - it'd make incomplete lines of code hard to diagnose.
Method 2: Specify how many arguments in the decorator
class Foo: def __init__(self): self._x = 42 @3:property def x(self): # getter return self._x def x(self, value): # setter self._x = value def x(self): # deleter del self._x
-0.5 on this syntax. It's too much action-at-a-distance; if the three functions are really trivial, then it wouldn't be too bad, but otherwise how do you know that "def x(self):" is making the deleter?
Method 3: Specify arguments using the parameters' names
class Foo: def __init__(self): self._x = 42 @property def x:fget(self): return self._x def x:fset(self, value): self._x = value def x:fdel(self): del self._x
This has the advantage of being explicit (and the arguments can be swapped since they're named), but the disadvantage that some builtins don't accept kwargs (this would be a good occasion to fix that, though, but that is besides my point).
This is very close to the existing syntax for @property, and it better adorns the individual functions. +0.5. What happens if you rename the property, though? How about this: class Foo: def __init__(self): self._x = 42 @property def x(self): # First positional parameter return self._x def :fset(self, value): # Named parameter self._x = value def :fdel(self): # Another named parameter del self._x Remove the repetition of the name "x", and then there's no chance of getting it wrong. But it's sounding here more like you're creating a block of code. And that, to my mind, suggests that it should be indented. Something like: class Foo: def __init__(self): self._x = 42 with @property as x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x This groups the three functions, and their names would be available to use as keyword arguments. It would be rather different from current with-block semantics, though. Effectively, it'd be something like this: 1) Evaluate the decorator itself (in this case, the simple name 'property'), but don't call it. 2) Create a new scope, nested inside the current scope. (Similar to a list comp.) 3) Execute the indented block in that scope. 4) Call the decorator, passing all names bound in this scope as keyword arguments. 5) Bind the return value of the decorator to the given name. Thoughts? ChrisA
On Nov 30, 2015, at 8:43 PM, Chris Angelico <rosuav@gmail.com> wrote:
On Tue, Dec 1, 2015 at 12:07 PM, Emanuel Barry <vgr255@live.ca> wrote:
<<SNIP>>
But it's sounding here more like you're creating a block of code. And that, to my mind, suggests that it should be indented. Something like:
class Foo: def __init__(self): self._x = 42 with @property as x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
This groups the three functions, and their names would be available to use as keyword arguments. It would be rather different from current with-block semantics, though. Effectively, it'd be something like this:
1) Evaluate the decorator itself (in this case, the simple name 'property'), but don't call it. 2) Create a new scope, nested inside the current scope. (Similar to a list comp.) 3) Execute the indented block in that scope. 4) Call the decorator, passing all names bound in this scope as keyword arguments. 5) Bind the return value of the decorator to the given name.
I like this; it really SHOULD be indented. But I also agree that 'with' is probably not the best keyword here, it makes it a little difficult to quickly read and see what's going on. Since this is for decorators, could we just drop 'with' altogether? E.g.: class Foo: def __init__(self): self._x = 42 @property as x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x Thanks, Cem Karan
On Tue, Dec 1, 2015 at 12:54 PM, Cem Karan <cfkaran2@gmail.com> wrote:
I like this; it really SHOULD be indented. But I also agree that 'with' is probably not the best keyword here, it makes it a little difficult to quickly read and see what's going on. Since this is for decorators, could we just drop 'with' altogether? E.g.:
class Foo: def __init__(self): self._x = 42 @property as x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
Thanks, Cem Karan
Either way works for me. My first try was some kind of hack that actually used a context manager as a context manager, but without compiler support, it wouldn't really work properly. So the syntax can be varied away from 'with'. ChrisA
Date: Tue, 1 Dec 2015 12:43:28 +1100 From: rosuav@gmail.com CC: python-ideas@python.org Subject: Re: [Python-ideas] Multiple arguments for decorators
Other than @property, are there any use-cases you know of?
Not in the builtins/stdlib, but I do have decorators that I'd probably rewrite to support the new syntax. In one of my projects I work around this by some other means, but it looks ugly.
Method 1: Add parens around all arguments
Definitely -1 on this syntax - it'd make incomplete lines of code hard to diagnose.
Yep, not surprised -- I only included it because that was the first idea I had, but I myself don't like it :)
Method 2: Specify how many arguments in the decorator
-0.5 on this syntax. It's too much action-at-a-distance; if the three functions are really trivial, then it wouldn't be too bad, but otherwise how do you know that "def x(self):" is making the deleter? I guess it does make it a bit of a hassle to maintain, maybe if it was combined with another one it would make sense. We'll see.
Method 3: Specify arguments using the parameters' names
This is very close to the existing syntax for @property, and it better adorns the individual functions. +0.5. What happens if you rename the property, though? How about this:
class Foo: def __init__(self): self._x = 42 @property def x(self): # First positional parameter return self._x def :fset(self, value): # Named parameter self._x = value def :fdel(self): # Another named parameter del self._x
Remove the repetition of the name "x", and then there's no chance of getting it wrong. I personally don't like this, the colon there looks weird to me. And you're just ignoring the first method's name and passing it positional, unlike the others which are named. I like the general idea you're bringing though, but it could use a tweak or three imo. But it's sounding here more like you're creating a block of code. And that, to my mind, suggests that it should be indented. Something like:
class Foo: def __init__(self): self._x = 42 with @property as x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
This groups the three functions, and their names would be available to use as keyword arguments. It would be rather different from current with-block semantics, though. Effectively, it'd be something like this:
1) Evaluate the decorator itself (in this case, the simple name 'property'), but don't call it. 2) Create a new scope, nested inside the current scope. (Similar to a list comp.) 3) Execute the indented block in that scope. 4) Call the decorator, passing all names bound in this scope as keyword arguments. 5) Bind the return value of the decorator to the given name.
Thoughts?
Glancing at this, it seems to me that "property" is having a unary @ operator applied to it, but I guess that since the possibility to introduce a unary @ operator shrank down to exactly 0 when the decorator syntax was added, that's not really an issue. I'm also not sure about overloading the semantics of the 'with' statement. Nevertheless, I like this approach. I wonder if something similar (using a with statement) can be achieved right now. Probably, with the use of vars and sys._getframe (I never said it would be clean!)
ChrisA
Thanks for your input!
On Tue, Dec 1, 2015 at 1:01 PM, Emanuel Barry <vgr255@live.ca> wrote:
Date: Tue, 1 Dec 2015 12:43:28 +1100 From: rosuav@gmail.com CC: python-ideas@python.org Subject: Re: [Python-ideas] Multiple arguments for decorators
Other than @property, are there any use-cases you know of?
Not in the builtins/stdlib, but I do have decorators that I'd probably rewrite to support the new syntax. In one of my projects I work around this by some other means, but it looks ugly.
The proposal would be strengthened by more examples. Currently, @property can do something very similar to what your proposal offers, so this is only a small improvement.
This is very close to the existing syntax for @property, and it better adorns the individual functions. +0.5. What happens if you rename the property, though? How about this:
class Foo: def __init__(self): self._x = 42 @property def x(self): # First positional parameter return self._x def :fset(self, value): # Named parameter self._x = value def :fdel(self): # Another named parameter del self._x
Remove the repetition of the name "x", and then there's no chance of getting it wrong.
I personally don't like this, the colon there looks weird to me. And you're just ignoring the first method's name and passing it positional, unlike the others which are named. I like the general idea you're bringing though, but it could use a tweak or three imo.
Agreed that the colon looks weird, but I don't know of any better way to spell it. This was just a half-way house to the thought that followed, though.
But it's sounding here more like you're creating a block of code. And that, to my mind, suggests that it should be indented. Something like:
class Foo: def __init__(self): self._x = 42 with @property as x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
This groups the three functions, and their names would be available to use as keyword arguments. It would be rather different from current with-block semantics, though. Effectively, it'd be something like this:
1) Evaluate the decorator itself (in this case, the simple name 'property'), but don't call it. 2) Create a new scope, nested inside the current scope. (Similar to a list comp.) 3) Execute the indented block in that scope. 4) Call the decorator, passing all names bound in this scope as keyword arguments. 5) Bind the return value of the decorator to the given name.
Thoughts?
Glancing at this, it seems to me that "property" is having a unary @ operator applied to it, but I guess that since the possibility to introduce a unary @ operator shrank down to exactly 0 when the decorator syntax was added, that's not really an issue. I'm also not sure about overloading the semantics of the 'with' statement.
Nevertheless, I like this approach. I wonder if something similar (using a with statement) can be achieved right now. Probably, with the use of vars and sys._getframe (I never said it would be clean!)
I don't think it can, because there's no way for a context manager to say "tell me about all name bindings in this block". The nearest I can think of is a nested class definition, which I can make work, but it's definitely ugly: def call(func): def inner(cls): return func(**{k:v for k,v in cls.__dict__.items() if not k.startswith('_')}) return inner class Foo: def __init__(self): self._x = 42 @call(property) class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x foo = Foo() print(foo.x) foo.x = 28 print(foo._x) ChrisA
On Mon, Nov 30, 2015 at 6:14 PM, Chris Angelico <rosuav@gmail.com> wrote:
def call(func): def inner(cls): return func(**{k:v for k,v in cls.__dict__.items() if not k.startswith('_')}) return inner
class Foo: def __init__(self): self._x = 42 @call(property) class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
I think this looks perfectly nice, actually. I was just trying to work out almost the same thing but using a `def x()` rather than `class f` as the nesting construct. I think Chris' is better though. I think I might want to define something like: make_property = call(property) class Foo: def __init__(self): self._x = 42 @make_property class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x -- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
David Mertz writes:
On Mon, Nov 30, 2015 at 6:14 PM, Chris Angelico <rosuav@gmail.com> wrote:
def call(func): def inner(cls): return func(**{k:v for k,v in cls.__dict__.items() if not k.startswith('_')}) return inner
class Foo: def __init__(self): self._x = 42 @call(property) class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
I think this looks perfectly nice, actually.
That was my first thought too. The use of the "class" keyword was a bit of a shock at first, but it grew on me, especially in this context of @property (ie, if I think "@property defines a class with appropriate behavior and installs instances on objects of the enclosing class"). I'm having trouble interpreting the function name "call", though. Am I missing something obvious ("Angelico" doesn't sound Dutch to me, though :-), or maybe there's a better (longer) name for it? Also, can call be expected to DTRT for anything *but* property?
On Tue, Dec 1, 2015 at 2:16 PM, Stephen J. Turnbull <stephen@xemacs.org> wrote:
I'm having trouble interpreting the function name "call", though. Am I missing something obvious ("Angelico" doesn't sound Dutch to me, though :-), or maybe there's a better (longer) name for it?
Also, can call be expected to DTRT for anything *but* property?
No, it's not Dutch... but my maternal grandparents were Dutch, if that helps? (My parents were both born here in Australia, but my grandparents are split two-and-two Dutch and Sicilian. So I get to eat garlic like nobody's business, and then pretend to understand Python. Maybe.) The semantics of the function I've given are simple: Pass it a callable and a block of assignments (implemented as a class body), and it calls the callable with the assignments as keyword arguments. So it'll work for anything that accepts keyword arguments - which in Python is nearly anything. It doesn't necessarily have to carry callables: import json @call(json.loads) class config: with open("config.json") as _f: # Leading underscore for non-arguments s = _f.read() encoding = "UTF-8" def object_hook(d): if "imag" in d and "real" in d and len(d)==2: return complex(d["real"], d["imag"]) return d parse_int = float import pprint pprint.pprint(config) Incidentally, this works in Python 2 as well as Python 3. I found out by typoing the command and seeing a bunch of u'...' strings in the pprint output - no other difference. ChrisA
Class scopes definitely feel like a good match -- they are a way of saying "evaluate all of these expression, pass the resulting locals to a custom function, and bind the result of that function to the classname". Usually the function is type(), which constructs a new class, but by setting a custom metaclass we can avoid creating a class just to wrap the scope: class PropertyMetaclass(type): def __new__(cls, name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__')) class Foo(object): class myprop(metaclass=PropertyMetaclass): def get(self): return 1 def set(self, v): pass __doc__ = 1 f = Foo() print(f.myprop) The "class myprop(metaclass=PropertyClass)" line is pretty ugly though. On Mon, Nov 30, 2015 at 6:41 PM, David Mertz <mertz@gnosis.cx> wrote:
On Mon, Nov 30, 2015 at 6:14 PM, Chris Angelico <rosuav@gmail.com> wrote:
def call(func): def inner(cls): return func(**{k:v for k,v in cls.__dict__.items() if not k.startswith('_')}) return inner
class Foo: def __init__(self): self._x = 42 @call(property) class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
I think this looks perfectly nice, actually. I was just trying to work out almost the same thing but using a `def x()` rather than `class f` as the nesting construct. I think Chris' is better though. I think I might want to define something like:
make_property = call(property)
class Foo: def __init__(self): self._x = 42 @make_property class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Nov 30, 2015, at 19:21, Kevin Modzelewski via Python-ideas <python-ideas@python.org> wrote:
Class scopes definitely feel like a good match -- they are a way of saying "evaluate all of these expression, pass the resulting locals to a custom function, and bind the result of that function to the classname". Usually the function is type(), which constructs a new class, but by setting a custom metaclass we can avoid creating a class just to wrap the scope:
Is there really a harm in creating a class? A property is a type, and the obvious way to simulate it in Python rather than C (as shown by the sample code in the HOWTO) is with a class statement. Besides, if you're creating many thousands of properties in a loop, the time and space cost of property creation is probably the least of your worries. Again, maybe that isn't true for other types of decorators this feature might be useful for, but without having any examples to think about, it's hard to guess...
class PropertyMetaclass(type): def __new__(cls, name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__'))
I still don't get the benefit of having a metaclass or constructor function or wrapper function or anything else, instead of just making property take a class instead of four functions. The latter is significantly nicer on the user side, and only a tiny bit more verbose in the implementation of property, and easier to understand. Unless there are other decorators where they wouldn't be true, or so many potentially useful one-shot decorators that defining them all a little more succinctly is worth the cost, why add the extra layer?
Tangent here: Is this potentially a good use case for Nick Coghlan's PEP 403 (@in clauses) and/or PEP 3150 (statement-local namespaces with "given:")? At first glance, the obvious way to use PEP 3150 doesn't look beautiful: class Spam: x = property(**?.__dict__) given: """x""" def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x But maybe there's a way to make this nicer?
On Nov 30, 2015, at 20:37, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
On Nov 30, 2015, at 19:21, Kevin Modzelewski via Python-ideas <python-ideas@python.org> wrote:
Class scopes definitely feel like a good match -- they are a way of saying "evaluate all of these expression, pass the resulting locals to a custom function, and bind the result of that function to the classname". Usually the function is type(), which constructs a new class, but by setting a custom metaclass we can avoid creating a class just to wrap the scope:
Is there really a harm in creating a class?
A property is a type, and the obvious way to simulate it in Python rather than C (as shown by the sample code in the HOWTO) is with a class statement.
Besides, if you're creating many thousands of properties in a loop, the time and space cost of property creation is probably the least of your worries.
Again, maybe that isn't true for other types of decorators this feature might be useful for, but without having any examples to think about, it's hard to guess...
class PropertyMetaclass(type): def __new__(cls, name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__'))
I still don't get the benefit of having a metaclass or constructor function or wrapper function or anything else, instead of just making property take a class instead of four functions. The latter is significantly nicer on the user side, and only a tiny bit more verbose in the implementation of property, and easier to understand. Unless there are other decorators where they wouldn't be true, or so many potentially useful one-shot decorators that defining them all a little more succinctly is worth the cost, why add the extra layer? _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
Hmm I could have done a bit better with my example. class defs can work with any callable as a "metaclass", so creating an actual metaclass was overkill: def property_wrapper(name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__')) class Foo(object): class myprop(metaclass=property_wrapper): def get(self): return 1 def set(self, v): pass __doc__ = 1 I wasn't suggesting it for performance reasons, just that there's already an API for "call a function with the locals defined in this scope" that we can use directly, rather than using a different wrapper of the underlying API (aka class creation). But I think from a readability standpoint it's much nicer to wrap a normal classdef with a "@make_property" decorator rather than doing it via a metaclasss. I think this could be different if there was a simpler way to use a metaclass (especially, a way that wasn't so class-related). Here's an example of what it could look like: scope(property_wrapper) myprop: def get(self): return 1 The thing that's nice is that "scope(X) Y" can be just a simple transformation to "class Y(metaclass=X)". Anyway, I'm not trying to seriously suggest this as the way to go, but just trying to say that if you want to apply a function to the locals defined in a scope, that feature already exists even if it is ugly to use :) On Mon, Nov 30, 2015 at 8:37 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
On Nov 30, 2015, at 19:21, Kevin Modzelewski via Python-ideas < python-ideas@python.org> wrote:
Class scopes definitely feel like a good match -- they are a way of
saying "evaluate all of these expression, pass the resulting locals to a custom function, and bind the result of that function to the classname". Usually the function is type(), which constructs a new class, but by setting a custom metaclass we can avoid creating a class just to wrap the scope:
Is there really a harm in creating a class?
A property is a type, and the obvious way to simulate it in Python rather than C (as shown by the sample code in the HOWTO) is with a class statement.
Besides, if you're creating many thousands of properties in a loop, the time and space cost of property creation is probably the least of your worries.
Again, maybe that isn't true for other types of decorators this feature might be useful for, but without having any examples to think about, it's hard to guess...
class PropertyMetaclass(type): def __new__(cls, name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__'))
I still don't get the benefit of having a metaclass or constructor function or wrapper function or anything else, instead of just making property take a class instead of four functions. The latter is significantly nicer on the user side, and only a tiny bit more verbose in the implementation of property, and easier to understand. Unless there are other decorators where they wouldn't be true, or so many potentially useful one-shot decorators that defining them all a little more succinctly is worth the cost, why add the extra layer?
On Nov 30, 2015, at 21:28, Kevin Modzelewski <kmod@dropbox.com> wrote:
Hmm I could have done a bit better with my example. class defs can work with any callable as a "metaclass", so creating an actual metaclass was overkill:
def property_wrapper(name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__'))
class Foo(object): class myprop(metaclass=property_wrapper): def get(self): return 1 def set(self, v): pass __doc__ = 1
But even this is still more complicated than just changing the property type's initializer to take a class instead of a bunch of functions (that is, making property a normal class decorator instead of making it a weird multiple-function decorator and then writing a separate wrapper that lets you pass a class as if it were a normal class decorator)? And again, what's the benefit from this extra complexity? Unless you have a whole lot of decorators written that all need this exact same transformation, you're just abstracting out an arbitrary part of the logic that doesn't seem to fit any natural grain.
I wasn't suggesting it for performance reasons, just that there's already an API for "call a function with the locals defined in this scope" that we can use directly, rather than using a different wrapper of the underlying API (aka class creation). But I think from a readability standpoint it's much nicer to wrap a normal classdef with a "@make_property" decorator rather than doing it via a metaclasss. I think this could be different if there was a simpler way to use a metaclass (especially, a way that wasn't so class-related). Here's an example of what it could look like:
scope(property_wrapper) myprop: def get(self): return 1
That's still a lot less readable than this: @property class myprop: def get(self): return 1
The thing that's nice is that "scope(X) Y" can be just a simple transformation to "class Y(metaclass=X)". Anyway, I'm not trying to seriously suggest this as the way to go, but just trying to say that if you want to apply a function to the locals defined in a scope, that feature already exists even if it is ugly to use :)
On Mon, Nov 30, 2015 at 8:37 PM, Andrew Barnert <abarnert@yahoo.com> wrote: On Nov 30, 2015, at 19:21, Kevin Modzelewski via Python-ideas <python-ideas@python.org> wrote:
Class scopes definitely feel like a good match -- they are a way of saying "evaluate all of these expression, pass the resulting locals to a custom function, and bind the result of that function to the classname". Usually the function is type(), which constructs a new class, but by setting a custom metaclass we can avoid creating a class just to wrap the scope:
Is there really a harm in creating a class?
A property is a type, and the obvious way to simulate it in Python rather than C (as shown by the sample code in the HOWTO) is with a class statement.
Besides, if you're creating many thousands of properties in a loop, the time and space cost of property creation is probably the least of your worries.
Again, maybe that isn't true for other types of decorators this feature might be useful for, but without having any examples to think about, it's hard to guess...
class PropertyMetaclass(type): def __new__(cls, name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__'))
I still don't get the benefit of having a metaclass or constructor function or wrapper function or anything else, instead of just making property take a class instead of four functions. The latter is significantly nicer on the user side, and only a tiny bit more verbose in the implementation of property, and easier to understand. Unless there are other decorators where they wouldn't be true, or so many potentially useful one-shot decorators that defining them all a little more succinctly is worth the cost, why add the extra layer?
On Tue, Dec 1, 2015 at 1:01 AM, Andrew Barnert via Python-ideas < python-ideas@python.org> wrote: [...]
And again, what's the benefit from this extra complexity? Unless you have a whole lot of decorators written that all need this exact same transformation, you're just abstracting out an arbitrary part of the logic that doesn't seem to fit any natural grain.
I'm assuming this recurring desire to improve on the property decorator is because there are several other languages where a compact way to declare getters and setters is part of the language syntax, and it usually takes the form of an indented block containing some functions. But how important is this really? I did a quick count on a fairly big and complex code base I happened to have sitting around. It has 10x more classes than properties, and only a tiny fraction of those use the @x.setter notation. If that's the norm I'm not sure we need more. -- --Guido van Rossum (python.org/~guido)
On the other hand, if we're willing to put up with some ugliness in the *implementation*, the *notation* can be fairly clean (and avoid the creation of a class object): class Example: def __init__(self): self._x = 0.0 self._y = 0.0 class x(Property): def get(self): return self._x def set(self, value): self._x = float(value) class y(Property): def get(self): return self._y def set(self, value): self._y = float(value) Notice there's no explicit mention of metaclasses here. The magic is that Property is a class with a custom metaclass. The implementation could be as simple as this: class MetaProperty(type): """Metaclass for Property below.""" def __new__(cls, name, bases, attrs): if name == 'Property' and attrs['__module__'] == cls.__module__: # Defining the 'Property' class. return super().__new__(cls, name, bases, attrs) else: # Creating a property. Avoid creating a class at all. # Return a property instance. assert bases == (Property,) return property(attrs.get('get'), attrs.get('set'), attrs.get('delete'), attrs.get('__doc__')) class Property(metaclass=MetaProperty): """Inherit from this to define a read-write property.""" -- --Guido van Rossum (python.org/~guido)
Nice idea and as concise as other languages do. I would appreciate a stdlib-provided 'Property'. Meta classes always make me feel that I need to bury them deeeeep down in some library. As properties are considered standard repetory these days (as you mentioned in the last post), I feel stdlib is the place. Best, Sven On 01.12.2015 17:22, Guido van Rossum wrote:
On the other hand, if we're willing to put up with some ugliness in the *implementation*, the *notation* can be fairly clean (and avoid the creation of a class object):
class Example: def __init__(self): self._x = 0.0 self._y = 0.0
class x(Property): def get(self): return self._x def set(self, value): self._x = float(value)
class y(Property): def get(self): return self._y def set(self, value): self._y = float(value)
Notice there's no explicit mention of metaclasses here. The magic is that Property is a class with a custom metaclass. The implementation could be as simple as this:
class MetaProperty(type): """Metaclass for Property below."""
def __new__(cls, name, bases, attrs): if name == 'Property' and attrs['__module__'] == cls.__module__: # Defining the 'Property' class. return super().__new__(cls, name, bases, attrs) else: # Creating a property. Avoid creating a class at all. # Return a property instance. assert bases == (Property,) return property(attrs.get('get'), attrs.get('set'), attrs.get('delete'), attrs.get('__doc__'))
class Property(metaclass=MetaProperty): """Inherit from this to define a read-write property."""
-- --Guido van Rossum (python.org/~guido <http://python.org/%7Eguido>)
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 2015-12-01 08:22, Guido van Rossum wrote:
On the other hand, if we're willing to put up with some ugliness in the *implementation*, the *notation* can be fairly clean (and avoid the creation of a class object):
I like this way of doing it. It's more fluid than the other proposed solutions and is instantly understandable. I don't even think the implementation is ugly; it's just a bit complex. But I think using complex machinery on the back end to support a nice clean interface for users is a good idea. What would be the point of all those fancy things like metaclasses if we can't leverage them in cases like this? :-) I do see one possible hiccup, though: it won't be possible to use zero-argument super() for a subclass to override a superclass getter/setter, because the magic __class__ variable won't correctly point to the real class (i.e., the enclosing class in which the property inner class is defined). And trying to use the two-argument super() may be confusing for the same reason. I think trying to use super() with properties is already a somewhat fraught endeavor (e.g., http://bugs.python.org/issue14965), but it's worth thinking about how these approaches would work if someone wants overridable property getters/setters. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown
I actually like this approach better than a syntactic way. The only downside is that custom decorators don't get this addition, but then the recipe can be used and re-used for those cases. +1 Thanks everyone who replied and offered their solutions, I appreciate it :) From: guido@python.org Date: Tue, 1 Dec 2015 08:22:30 -0800 To: abarnert@yahoo.com Subject: Re: [Python-ideas] Multiple arguments for decorators CC: python-ideas@python.org On the other hand, if we're willing to put up with some ugliness in the *implementation*, the *notation* can be fairly clean (and avoid the creation of a class object): class Example: def __init__(self): self._x = 0.0 self._y = 0.0 class x(Property): def get(self): return self._x def set(self, value): self._x = float(value) class y(Property): def get(self): return self._y def set(self, value): self._y = float(value) Notice there's no explicit mention of metaclasses here. The magic is that Property is a class with a custom metaclass. The implementation could be as simple as this: class MetaProperty(type): """Metaclass for Property below.""" def __new__(cls, name, bases, attrs): if name == 'Property' and attrs['__module__'] == cls.__module__: # Defining the 'Property' class. return super().__new__(cls, name, bases, attrs) else: # Creating a property. Avoid creating a class at all. # Return a property instance. assert bases == (Property,) return property(attrs.get('get'), attrs.get('set'), attrs.get('delete'), attrs.get('__doc__')) class Property(metaclass=MetaProperty): """Inherit from this to define a read-write property.""" -- --Guido van Rossum (python.org/~guido) _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
I think I can also confirm that setters **usually** not needed in Python. On 01.12.2015 16:48, Guido van Rossum wrote:
On Tue, Dec 1, 2015 at 1:01 AM, Andrew Barnert via Python-ideas <python-ideas@python.org <mailto:python-ideas@python.org>> wrote: [...]
And again, what's the benefit from this extra complexity? Unless you have a whole lot of decorators written that all need this exact same transformation, you're just abstracting out an arbitrary part of the logic that doesn't seem to fit any natural grain.
I'm assuming this recurring desire to improve on the property decorator is because there are several other languages where a compact way to declare getters and setters is part of the language syntax, and it usually takes the form of an indented block containing some functions.
But how important is this really? I did a quick count on a fairly big and complex code base I happened to have sitting around. It has 10x more classes than properties, and only a tiny fraction of those use the @x.setter notation. If that's the norm I'm not sure we need more. -- --Guido van Rossum (python.org/~guido <http://python.org/%7Eguido>)
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 02.12.2015 00:34, Greg Ewing wrote:
Sven R. Kunze wrote:
I think I can also confirm that setters **usually** not needed in Python.
I think that depends on the kind of code you're writing. In PyGUI I make heavy use of properties, and most of them have both getters and setters.
Maybe you are right. On the other hand, one could dispense with setters by using different means. Out of curiosity: what PyGUI are you referring to? Google gives me several distinct projects. Best, Sven
On 3/12/2015 7:22 a.m., Sven R. Kunze wrote:
Out of curiosity: what PyGUI are you referring to? Google gives me several distinct projects.
This one: http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/ Although I suspect the same thing would apply to most GUI libraries for Python, at least if they layer a substantial amount of Python code on top of something else. -- Greg
On December 2, 2015 6:52:29 PM CST, Greg <greg.ewing@canterbury.ac.nz> wrote:
On 3/12/2015 7:22 a.m., Sven R. Kunze wrote:
Out of curiosity: what PyGUI are you referring to? Google gives me several distinct projects.
This one:
http://www.cosc.canterbury.ac.nz/greg.ewing/python_gui/
Although I suspect the same thing would apply to most GUI libraries for Python, at least if they layer a substantial amount of Python code on top of something else.
I was under the impression that that library was no longer maintained, considering that Gtk support has been in the works since 2011... -- Sent from my Nexus 5 with K-9 Mail. Please excuse my brevity.
What worries me is that all we're looking at is the case of the @property decorator. That decorator just creates a descriptor. Why not just class Foo(object): class myprop(object): def __get__(self): return 1 def __set__(self, value): pass It would seem far more logical to tell peopele to read up on descriptors, instead of telling them: "Here's some complicated thing you can use that generates something quite simple under the hood.". Is there any other use case which would benefit greatly from the 'add locals to some scope' idea? Probably, but in that case I would suggest discussing these cases separately. On Mon, Nov 30, 2015 at 07:21:57PM -0800, Kevin Modzelewski via Python-ideas wrote:
Class scopes definitely feel like a good match -- they are a way of saying "evaluate all of these expression, pass the resulting locals to a custom function, and bind the result of that function to the classname". Usually the function is type(), which constructs a new class, but by setting a custom metaclass we can avoid creating a class just to wrap the scope:
class PropertyMetaclass(type): def __new__(cls, name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__'))
class Foo(object): class myprop(metaclass=PropertyMetaclass): def get(self): return 1 def set(self, v): pass __doc__ = 1
f = Foo() print(f.myprop)
The "class myprop(metaclass=PropertyClass)" line is pretty ugly though.
On Mon, Nov 30, 2015 at 6:41 PM, David Mertz <mertz@gnosis.cx> wrote:
On Mon, Nov 30, 2015 at 6:14 PM, Chris Angelico <rosuav@gmail.com> wrote:
def call(func): def inner(cls): return func(**{k:v for k,v in cls.__dict__.items() if not k.startswith('_')}) return inner
class Foo: def __init__(self): self._x = 42 @call(property) class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
I think this looks perfectly nice, actually. I was just trying to work out almost the same thing but using a `def x()` rather than `class f` as the nesting construct. I think Chris' is better though. I think I might want to define something like:
make_property = call(property)
class Foo: def __init__(self): self._x = 42 @make_property class x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On 1 December 2015 at 17:51, Sjoerd Job Postmus <sjoerdjob@sjec.nl> wrote:
What worries me is that all we're looking at is the case of the @property decorator. That decorator just creates a descriptor. Why not just
class Foo(object): class myprop(object): def __get__(self): return 1 def __set__(self, value): pass
Those aren't the signatures of the descriptor methods. From https://docs.python.org/3/reference/datamodel.html#descriptors: object.__get__(self, instance, owner) object.__set__(self, instance, value) object.__delete__(self, instance) The trick with property is that it hides the "self" that refers to the descriptor object itself, as well as the "owning class" reference in __get__, leading to the simplified property protocol where the individual functions are written as instance methods of the class *containing* the property, and retrieving the descriptor from the class will just give you the descriptor object. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On 2015-12-01, Nick Coghlan wrote:
The trick with property is that it hides the "self" that refers to the descriptor object itself,
Yes, but if the descriptor object is a class, then there is no "self" because it's called as a static method. The missing owner parameter is the only problem when it's called in the way that the descriptor howto says it is called [type(b).__dict__['x'].__get__(b, type(b))]. It doesn't actually work as written, of course, since it doesn't have any way of knowing myprop is meant to be a descriptor. But the self parameter isn't the issue. Incidentally, I'm not sure I understand what the owner parameter is for, and what purpose it can be useful for which it isn't also needed on the setter.
On 2 December 2015 at 01:29, Random832 <random832@fastmail.com> wrote:
Incidentally, I'm not sure I understand what the owner parameter is for, and what purpose it can be useful for which it isn't also needed on the setter.
__get__ is the only case where the descriptor can override retrieval via the *class* in addition to via the instance. That case shows up as the instance being "None" in the call to __get__:
class ShowDescr: ... def __get__(self, instance, owner): ... print(self, instance, owner, sep="\n") ... class C: ... x = ShowDescr() ... C.x <__main__.ShowDescr object at 0x7f7ddcf3f400> None <class '__main__.C'> C().x <__main__.ShowDescr object at 0x7f7ddcf3f400> <__main__.C object at 0x7f7ddcf3f4a8> <class '__main__.C'>
For __set__ and __del__, setting and deleting via the class isn't something a descriptor stored on that class can affect (it needs to be on the metaclass instead) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
The proposal would be strengthened by more examples. Currently, @property can do something very similar to what your proposal offers, so this is only a small improvement.
While searching for more examples, it struck me that I wouldn't want to allow only methods to be passed, but I would want any value (for the name and/or docstring, mainly). For example, consider the following code: class Foo: @attribute def bar(self): return 42 bar.__doc__ = "The meaning of life, the universe, and everything." I would benefit from being able to pass in the doc at the same time, as well. That is a poor example though, here is a better one: @total_decorate(handler=custom_handler, name="special_cased")class Foo: <...> Or, as I do sometimes: @total_decorate()class Foo: <...> This would benefit from being able to make that a single call (given some changes to the code) and avoid the ugly parens, that I see quite often. This example could be extended to reprlib.recursive_repr too. I must admit that I currently lack in examples, I'll try to provide more tomorrow. I took property because it's simple and everyone knows about it (pretty much).
I personally don't like this, the colon there looks weird to me. And you're just ignoring the first method's name and passing it positional, unlike the others which are named. I like the general idea you're bringing though, but it could use a tweak or three imo.
Agreed that the colon looks weird, but I don't know of any better way to spell it. This was just a half-way house to the thought that followed, though.
I'm not particularly attached to any syntax, all my suggestions are only that - suggestions.
I don't think it can, because there's no way for a context manager to say "tell me about all name bindings in this block". The nearest I can think of is a nested class definition, which I can make work, but it's definitely ugly:
Well, you store all the names in the previous frame in __enter__, and then do your stuff in __exit__. It *is* hacky though.
On Monday, November 30, 2015 6:14 PM, Chris Angelico <rosuav@gmail.com> wrote:
On Tue, Dec 1, 2015 at 1:01 PM, Emanuel Barry <vgr255@live.ca> wrote:
The proposal would be strengthened by more examples. Currently, @property can do something very similar to what your proposal offers, so this is only a small improvement.
Agreed. And it's not clear why other decorators can't just do something similar to what @property does.
But it's sounding here more like you're creating a block of code. And that, to my mind, suggests that it should be indented.
Agreed. The way to group things in Python is with an indented suite; trying to read three tiny things at the same level as being controlled by something above all of them isn't too terrible, but if there are three big things, or eight tiny things?
class Foo:
def __init__(self): self._x = 42 with @property as x: def fget(self): return self._x def fset(self, value): self._x = value def fdel(self): del self._x
This groups the three functions, and their names would be available to use as keyword arguments. It would be rather different from current with-block semantics, though.
I think this is a bad idea from the start. Only functions and classes have scopes; normal suite do not. If you change that to add "... except the suite of a with statement whose context manager is a decorator", that's no longer a simple rule you can hold in your head.
The nearest I can think of is a nested class definition, which I can make work, but it's definitely ugly:
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 Sure, @call slightly simplifies those 4 lines of boilerplate at the start of Property.__init__, and does the same for every other decorator that you define to be used with @call--but at the cost of making every _use_ of every such decorator uglier, and making things more complex to think through. Is it really worth it? Actually, that's not a rhetorical question; it's hard to guess without seeing more examples beyond @property...
On Tue, Dec 1, 2015 at 1:52 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
I think this is a bad idea from the start. Only functions and classes have scopes; normal suite do not. If you change that to add "... except the suite of a with statement whose context manager is a decorator", that's no longer a simple rule you can hold in your head.
And list comprehensions. They have a new nested scope, even though you can't see the function call. Everywhere else that you see a 'for' loop, it's exactly the same semantics as any other assignment - but if it's inside square brackets, it's a special form of local name that doesn't extend past the brackets. This would be the same - a hidden function call that creates a nested scope. ChrisA
On Nov 30, 2015, at 18:58, Chris Angelico <rosuav@gmail.com> wrote:
On Tue, Dec 1, 2015 at 1:52 PM, Andrew Barnert <abarnert@yahoo.com> wrote: I think this is a bad idea from the start. Only functions and classes have scopes; normal suite do not. If you change that to add "... except the suite of a with statement whose context manager is a decorator", that's no longer a simple rule you can hold in your head.
And list comprehensions.
Comprehensions define functions, so it's the same rule; you just have to know that functions can be defined three ways (def statement, lambdas expression, or comprehension) rather than just two. Sure, that's not _ideally_ simple, but that hardly seems a reason to make it even _less_ simple. Also, comprehensions don't have a suite--you can't define arbitrary new variables or use global/nonlocal statements or anything else that would make you have to think carefully about scoping. Most importantly, comprehensions don't have a suite that looks exactly the same as another kind of suite introduced by the same keyword that doesn't define a scope, except on this one special case where it does.
On Tue, Dec 1, 2015 at 3:30 PM, Andrew Barnert <abarnert@yahoo.com> wrote:
Comprehensions define functions, so it's the same rule; you just have to know that functions can be defined three ways (def statement, lambdas expression, or comprehension) rather than just two. Sure, that's not _ideally_ simple, but that hardly seems a reason to make it even _less_ simple.
Which I don't really like. It makes sense for generator expressions, but list comprehensions don't *look* like they introduce a new sub-scope. A 'with' block does look like it creates a new scope, but its binding leaks out (usually giving you a closed file object or something). Sure, I can understand why things are the way they are, but it's not intuitive. You have to dig into things a bit to grok it.
Most importantly, comprehensions don't have a suite that looks exactly the same as another kind of suite introduced by the same keyword that doesn't define a scope, except on this one special case where it does.
Agreed. And while I called the class-based system "ugly" to start with, I'm coming around to it more and more - especially since it works in current versions of Python, rather than demanding core interpreter changes. It's not the most intuitive use of syntax either (the 'class' block isn't really creating a class at all - it creates a function parameter list), but it isn't as bad as I thought it was. ChrisA
On Mon, Nov 30, 2015 at 11:38 PM Chris Angelico <rosuav@gmail.com> wrote:
And while I called the class-based system "ugly" to start with, I'm coming around to it more and more - especially since it works in current versions of Python, rather than demanding core interpreter changes. It's not the most intuitive use of syntax either (the 'class' block isn't really creating a class at all - it creates a function parameter list), but it isn't as bad as I thought it was.
One advantage of the ``@property`` syntax is that one can show a new Pythonista how to create read-only properties without needing to explain too many new concepts. Using ``@foo.setter`` sometimes requires a bit more hand-waving. Is there a way to use the metaclass and class-in-a-class as Kevin Modzelewski wrote, but hiding some of the complexity behind a decorator?
Suppose you were able to write: @somedecorator as name: <suite> and have it be equivalent to @somedecorator: class name: <suite> Then you could say @property as foo: def get(self): ... def set(self, value): ... -- Greg
On Dec 1, 2015, at 15:22, Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Suppose you were able to write:
@somedecorator as name: <suite>
and have it be equivalent to
@somedecorator: class name: <suite>
Do you mean equivalent to this? @somedecorator class name: <suite> If so: what's the problem with what we already have? There's no double indenting going on, or anything else ugly or obtrusive, when spelled properly. And I don't see why making it easier to write a class where it's harder for the reader to tell you've done so is an aid to readability. Also, it means the "foo" is no longer in the usual place (def /class statement or assignment), so now we have to all learn to scan three different places where attributes can get named instead of just two, and the new one (unlike the existing two) only appears in classes, not at module or local scope.
On 1 December 2015 at 12:52, Andrew Barnert via Python-ideas <python-ideas@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: class Foo: def __init__(self): self._x = 42 @property def x(self): return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x The main objection I recall being raised against the class based approach in that previous discussion is that it handles the "self" reference in the property implementation methods in a confusing way: the "self" refers to an instance of the class containing the property definition, *not* to an instance of the class containing the methods. By contrast, when you use the "property/setter/deleter" pattern or the original non-decorator based pattern, all of the individual methods are written as normal methods, with the "self" referring to an instance of the class that contains the method definition as usual. Any approach based on defining a new indented suite has the same problem (in that it makes it harder for a human reader to figure out the correct referent for the "self" parameters), but using a class statement specifically has the problem of "no, not *this* class, *that* class". 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. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
On Dec 1, 2015, at 01:56, Nick Coghlan <ncoghlan@gmail.com> wrote:
On 1 December 2015 at 12:52, Andrew Barnert via Python-ideas <python-ideas@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)...
On Tue, Dec 1, 2015 at 1:01 PM, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
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.
Metaclasses were probably also obscure.
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)...
Indeed, this is quite simple: "property x:" is pretty much syntactic sugar for "class x(metaclass=property_wrapper)", with a simple function as the metaclass, as Kevin suggested: def property_wrapper(name, bases, attrs): return property(attrs.get('get'), attrs.get('set'), attrs.get('del'), attrs.get('__doc__')) If the syntax was a bit more generic – if "property" would not be a keyword but the name of a callable – this could solve the "higher order function accepting multiple distinct functions as inputs" problem, or "creating something other than a class from a suite/namespace".
On 1 December 2015 at 22:01, Andrew Barnert <abarnert@yahoo.com> wrote:
On Dec 1, 2015, at 01:56, Nick Coghlan <ncoghlan@gmail.com> wrote:
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.
Class decorators and getter/setter/deleter were both added in 2.6/3.0 Property API additions: http://bugs.python.org/issue1416 Class decorators: https://www.python.org/dev/peps/pep-3129/
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.)
Java had had inner classes for over a decade by the time 2.6 & 3.0 were released.
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).
Python itself had had dynamic class creation since the beginning, though, and new-style classes in 2.2 doubled down on that. Digging around a bit more, I found one reference to Guido pointing out his dislike for the nested class based approach was in the context of Steven Bethard's old "make" statement PEP: https://www.python.org/dev/peps/pep-0359/ Which also lead me to rediscovering why this particular idea of using a class with to class decorator to define a property sounded familiar: https://mail.python.org/pipermail/python-dev/2005-October/057350.html :)
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.
I think folks are more familiar with the use of class decorators in general (since they've been around for several years now), but I also think there's still a general expectation that any defined methods should behave like normal instance methods.
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.
Yep :) It's an interesting language design conundrum (hence why a range of folks have been tinkering with various forms of the question for years), but in *practical* terms it doesn't matter a great deal. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (18)
-
Andrew Barnert
-
Brendan Barnwell
-
Cem Karan
-
Chris Angelico
-
David Mertz
-
Emanuel Barry
-
Greg
-
Greg Ewing
-
Guido van Rossum
-
Kevin Modzelewski
-
Michael Selik
-
Nick Coghlan
-
Petr Viktorin
-
Random832
-
Ryan Gonzalez
-
Sjoerd Job Postmus
-
Stephen J. Turnbull
-
Sven R. Kunze