Defining properties - a use case for class decorators?
Greg Ewing wrote:
... the standard way of defining properties at the moment leaves something to be desired, for all the same reasons that have led to @-decorators.
Guido write:
... make that feeling more concrete. ...
With decorators there was a concrete issue: the modifier trailed after the function body, in a real sense "hiding" from the reader. I don't see such an issue with properties.
For me, the property declaration (including the function declarations) is too verbose, and ends up hiding the rest of the class. My ideal syntax would look something like: # Declare "x" to name a property. Create a storage slot, # with the generic get/set/del/doc. (doc == "property x"?) property(x) # Change the setter, possibly in a subclass property(x) set: if x<5: __x = x If I don't want anything special on the get, I shouldn't have to add any "get" boilerplate to my code. An alternative might be slots=[x, y, z] to automatically create default properties for x, y, and z, while declaring that instances won't have arbitrary fields. That said, I'm not sure the benefit is enough to justify the extra complications, and your suggestion of allowing strings for method names may be close enough. I agree that the use of strings is awkward, but ... probably no worse than using them with __dict__ today. -jJ
Jim Jewett wrote:
That said, I'm not sure the benefit is enough to justify the extra complications, and your suggestion of allowing strings for method names may be close enough. I agree that the use of strings is awkward, but ... probably no worse than using them with __dict__ today.
An idea that was kicked around on c.l.p a long while back was "statement local variables", where you could define some extra names just for a single simple statement: x = property(get, set, delete, doc) given: doc = "Property x (must be less than 5)" def get(self): try: return self._x except AttributeError: self._x = 0 return 0 def set(self, value): if value >= 5: raise ValueError("value too big") self._x = x def delete(self): del self._x As I recall, the idea died due to problems with figuring out how to allow the simple statement to both see the names from the nested block and modify the surrounding namespace, but prevent the names from the nested block from affecting the surrounding namespace after the statement was completed. Another option would be to allow attribute reference targets when binding function names: x = property("Property x (must be less than 5)") def x.get(instance): try: return instance._x except AttributeError: instance._x = 0 return 0 def x.set(instance, value): if value >= 5: raise ValueError("value too big") instance._x = x def x.delete(instance): del instance._x Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.blogspot.com
At 11:59 PM 10/18/2005 +1000, Nick Coghlan wrote:
An idea that was kicked around on c.l.p a long while back was "statement local variables", where you could define some extra names just for a single simple statement:
x = property(get, set, delete, doc) given: doc = "Property x (must be less than 5)" def get(self): try: return self._x except AttributeError: self._x = 0 return 0 ...
As I recall, the idea died due to problems with figuring out how to allow the simple statement to both see the names from the nested block and modify the surrounding namespace, but prevent the names from the nested block from affecting the surrounding namespace after the statement was completed.
Haskell's "where" statement does this, but the block *doesn't* modify the surrounding namespace; it's strictly local. With those semantics, the Python translation of the above could just be something like: def _tmp(): doc = "blah" def get(self): # etc. # ... return property(get,set,delete,doc) x = _tmp() Which works great except for the part that co_lnotab won't let you identify that "return" line as being the original expression line, due to the monotonically-increasing bit. ;) Note that a "where" or "given" statement like this could make it a little easier to drop lambda.
Phillip J. Eby wrote:
Note that a "where" or "given" statement like this could make it a little easier to drop lambda.
I think the "lambda will disappear in Py3k" concept might have been what triggered the original 'where' statement discussion. The idea was to be able to lift an arbitrary subexpression out of a function call or assignment statement without having to worry about affecting the surrounding namespace, and without distracting attention from the original statement. Basically, let a local refactoring *stay* local. The discussion wandered fairly far afield from that original goal though. One reason it fell apart was trying to answer the seemingly simple question "What would this print?": def f(): a = 1 b = 2 print 1, locals() print 3, locals() given: a = 2 c = 3 print 2, locals() print 4, locals() Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.blogspot.com
At 07:47 PM 10/19/2005 +1000, Nick Coghlan wrote:
Phillip J. Eby wrote:
Note that a "where" or "given" statement like this could make it a little easier to drop lambda.
I think the "lambda will disappear in Py3k" concept might have been what triggered the original 'where' statement discussion.
The idea was to be able to lift an arbitrary subexpression out of a function call or assignment statement without having to worry about affecting the surrounding namespace, and without distracting attention from the original statement. Basically, let a local refactoring *stay* local.
The discussion wandered fairly far afield from that original goal though.
One reason it fell apart was trying to answer the seemingly simple question "What would this print?":
def f(): a = 1 b = 2 print 1, locals() print 3, locals() given: a = 2 c = 3 print 2, locals() print 4, locals()
It would print "SyntaxError", because the 'given' or 'where' clause should only work on an expression or assignment statement, not print. :) In Python 3000, where print is a function, it should print the numbers in sequence, with 1+4 showing the outer locals, and 2+3 showing the inner locals (not including 'b', since b is not a local variable in the nested block). I don't see what's hard about the question, if you view the block as syntax sugar for a function definition and invocation on the right hand side of an assignment. Of course, if you assume it can occur on *any* statement (e.g. print), I suppose things could seem more hairy.
Nick Coghlan <ncoghlan@gmail.com> wrote:
Jim Jewett wrote:
That said, I'm not sure the benefit is enough to justify the extra complications, and your suggestion of allowing strings for method names may be close enough. I agree that the use of strings is awkward, but ... probably no worse than using them with __dict__ today.
An idea that was kicked around on c.l.p a long while back was "statement local variables", where you could define some extra names just for a single simple statement:
x = property(get, set, delete, doc) given: doc = "Property x (must be less than 5)" def get(self): try: return self._x except AttributeError: self._x = 0 return 0 def set(self, value): if value >= 5: raise ValueError("value too big") self._x = x def delete(self): del self._x
As I recall, the idea died due to problems with figuring out how to allow the simple statement to both see the names from the nested block and modify the surrounding namespace, but prevent the names from the nested block from affecting the surrounding namespace after the statement was completed.
You wouldn't be able to write to the surrounding namespace, but a closure would work fine for this. def Property(fcn): ns = fcn() return property(ns.get('get'), ns.get('set'), ns.get('delete'), ns.get('doc')) class foo(object): @Property def x(): doc = "Property x (must be less than 5)" def get(self): try: return self._x except AttributeError: self._x = 0 return 0 def set(self, value): if value >= 5: raise ValueError("value too big") self._x = value def delete(self): del self._x return locals() In an actual 'given:' statement, one could create a local function namespace with the proper func_closure attribute (which is automatically executed), then execute the lookup of the arguments to the statement in the 'given:' line from this closure, but assign to surrounding scope. Then again, maybe the above function and decorator approach are better. An unfortunate side-effect of with statement early-binding of 'as VAR' is that unless one works quite hard at mucking about with frames, the following has a wholly ugly implementation (whether or not one cares about the persistance of the variables defined within the block, you still need to modify x when you are done, which may as well cause a cleanup of the objects defined within the block...if such things are possible)... with Property as x: ...
Another option would be to allow attribute reference targets when binding function names:
*shivers at the proposal* That's scary. It took me a few minutes just to figure out what the heck that was supposed to do. - Josiah
Josiah Carlson wrote:
Another option would be to allow attribute reference targets when binding function names:
*shivers at the proposal* That's scary. It took me a few minutes just to figure out what the heck that was supposed to do.
Yeah, I think it's a concept with many, many more downsides than upsides. A "given" or "where" clause based solution would be far easier to read: x.get = f given: def f(): pass A given clause has its own problems though (the out-of-order execution it involves being the one which seems to raise the most hackles). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia --------------------------------------------------------------- http://boredomandlaziness.blogspot.com
participants (4)
-
Jim Jewett -
Josiah Carlson -
Nick Coghlan -
Phillip J. Eby