An alternative way of defining properties (warning: contains creative abuse of the class statement)

The @property.getter and @property.setter decorators are clever, but they have the disadvantage that you end up writing the name of the property no less than 5 times, all of which have to match. Thinking there must be a better way, I came up with this: def Property(name, bases, dict): return property(dict.get('get'), dict.get('set')) which allows you to write class Test: class foo(metaclass = Property): def get(self): print("Getting foo") return self._foo def set(self, x): print("Setting foo to", x) self._foo = x test = Test() test.foo = 42 print(test.foo) Output: Setting foo to 42 Getting foo 42 -- Greg

(Not sure why this is on python-ideas - wouldn't python-list be more appropriate? Keeping it where it is for now though.) On Thu, Jul 2, 2020 at 5:14 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I quite like the use of class blocks as namespaces, but I'm not a fan of metaclassing. A decorated class is way cleaner in the source code. How about this? def Property(ns): return property(*[getattr(ns, k, None) for k in ("fget", "fset", "fdel", "__doc__")]) class Test: @Property class foo: """Docstring for foo""" def fget(self): print("Getting foo") return self._foo def fset(self, x): print("Setting foo to", x) self._foo = x Same output as yours, and it's easy to support fdel, and a docstring. ChrisA

On Thu, Jul 2, 2020 at 9:34 AM Chris Angelico <rosuav@gmail.com> wrote:
(Not sure why this is on python-ideas - wouldn't python-list be more appropriate? Keeping it where it is for now though.)
As someone not familiar with the other lists...why? It's a proposal of an idea that could use some debate. Isn't this the perfect place?

On Thu, Jul 2, 2020 at 7:06 PM Alex Hall <alex.mojaki@gmail.com> wrote:
What's the idea being discussed? AIUI there's no need or request to change the language/stdlib, but maybe I'm misreading. This is a cute showcase of how a class namespace can be used to gather multiple functions together and build a property with them; it already works without any changes. So it's a great thing to discuss, but with no changes being proposed, it's not really an idea for the language. On python-list, there'll be more people, many of whom wouldn't think to hang out here, but could still appreciate (a) this cool way of doing properties, and (b) the more general notion that a decorated class or metaclass can be used for this kind of very powerful namespacing. ChrisA

On Thu, Jul 2, 2020 at 8:38 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Let's get concrete then. Suppose that the property constructor recognized a special one-argument form: if it has an fget attribute, it fetches its details from the fget, fset, fdel, and __doc__ attributes of that object. That way, it can be used in three ways: a standard constructor that does all the work with up to four arguments, the decorator form (which is the same as the one-arg form initially, but then you can use its setter decorator), and the class form. class Test: x = property(lambda self: 42, lambda self, val: print("Setting to", val)) @property def y(self): return 42 @y.setter def y(self, val): print("Setting to", val) # the theoretical one @property class z: def fget(self): return 42 def fset(self, val): print("Setting to", val) Since functions won't normally have fset attributes, this should be fairly safe - technically it'd be backward incompatible, but it's highly unlikely to break any real-world code. I don't write a lot of properties with setters, so I can't speak to the value of this, but it is at least plausible. (If you'd prefer to use "get" or "getter" or something in place of "fget", that would work fine. I used the same name as the keyword arg to property itself, but it can be anything.) ChrisA

On Thu, Jul 02, 2020 at 11:04:40AM +0300, Serhiy Storchaka wrote:
Perhaps Greg meant to say *up to* rather than "no less". class MyClass: @property def spam(): ... @spam.setter def spam(self, value): ... @spam.deleter def spam(self): ... That makes five by my count, however I hardly ever write a deleter so three is more common, and only occassionally a setter so one is most common :-) -- Steven

On 2/07/20 9:00 pm, Steven D'Aprano wrote:
Perhaps Greg meant to say *up to* rather than "no less".
What I said is true for sufficiently small values of 5. :-) You're right that it depends on how many operations you want. For reading and writing it's 3; if you want deleting as well it's 5. But 3 is still a lot less DRY than 1. -- Greg

Coincidentally, cython has a custom, deprecated syntax for properties that is actually pretty similar, and nice: cdef class Spam: property cheese: "A doc string can go here." def __get__(self): # This is called when the property is read. ... def __set__(self, value): # This is called when the property is written. ... def __del__(self): # This is called when the property is deleted. https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#p... I don't think the benefits are worth a core python change, but great minds.. Steve On Thu, Jul 2, 2020 at 11:42 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:

On 2/07/20 10:49 pm, Stestagg wrote:
Coincidentally, cython has a custom, deprecated syntax for properties that is actually pretty similar
Not entirely a coincidence -- I invented that syntax for Pyrex, and Cython inherited it. I'm a little disappointed to hear it's been deprecated. :-( -- Greg

Haha, I had no idea! That's great. fyi, judicious use of cython allowed our python team to build python components that far outperformed their java counterparts in some real-time middle-office financial processing jobs for a really large client. So thank you. I can see why using the standard python syntax might be considered better than the more elegant custom version, but yes, it's a bit sad Steve On Thu, Jul 2, 2020 at 12:27 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:

On 2/07/20 8:04 pm, Serhiy Storchaka wrote:
It has a problem with pickling (it is solvable).
Can you elaborate? The end result is a property object just the same as you would get from using @property or calling property directly. I don't see how it can have any pickling problems beyond what properties already have.
The larger problem is with using private (double underscored) variables and super().
I don't know what you're talking about here. I didn't use any double-underscore names in my example. -- Greg

On Thu, Jul 2, 2020, 6:30 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I think what he may have meant is that if you tried accessing a double-underscore property of the outer class from the get/set methods, it won't properly de-mangle. Similarly, if you wanted to overwrite a property by using this property approach in the sub-class, but also call super for the parent's class property getter from within the get/set this wouldn't work!?

My personal feeling: I would love this idea (DRY gets me almost every time) if it weren't for that awful, terrible `class` keyword hanging out there. I wouldn't call using class this way "abuse", exactly, but it could be a potential use for an old idea raised more than once in the past: some kind of submodule or namespace definition statement: class Test: @Property ns foo: # ns a new syntax meaning a namespace, or "submodule", object """Docstring for foo""" def fget(self): print("Getting foo") return self._foo def fset(self, x): print("Setting foo to", x) self._foo = x --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler On Thu, Jul 2, 2020 at 1:46 PM Matthew Einhorn <moiein2000@gmail.com> wrote:

On 3/07/20 5:42 am, Matthew Einhorn wrote:
Ah, I see what you mean. I don't think that's a fatal problem; double-underscore names don't seem to get used very much, and if you did want to use one, it would just mean you couldn't use this particular way of defining properties.
This is a problem with properties however you create them, and the same tecnhiques apply for dealing with it. You need to find the property object from the base class and extract its getter. -- Greg

02.07.20 13:26, Greg Ewing пише:
You are right since property objects currently are not pickleable by default. But it is possible to implement pickling support for property objects which will fail with your example (and I think third-party libraries do it). The difference is that full qualified names of getter and setter differ from the full qualified name of the property. It is solvable, but it may require more complex code.
Then try to use them. It would not work. class Test2: def __init__(self): self.__foo = 1 class foo(metaclass = Property): def get(self): return self.__foo Test2().foo class Test3Base: @property def foo(self): return 1 class Test3(Test3Base): class foo(metaclass = Property): def get(self): return super().foo + 1 Test3().foo

(Not sure why this is on python-ideas - wouldn't python-list be more appropriate? Keeping it where it is for now though.) On Thu, Jul 2, 2020 at 5:14 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I quite like the use of class blocks as namespaces, but I'm not a fan of metaclassing. A decorated class is way cleaner in the source code. How about this? def Property(ns): return property(*[getattr(ns, k, None) for k in ("fget", "fset", "fdel", "__doc__")]) class Test: @Property class foo: """Docstring for foo""" def fget(self): print("Getting foo") return self._foo def fset(self, x): print("Setting foo to", x) self._foo = x Same output as yours, and it's easy to support fdel, and a docstring. ChrisA

On Thu, Jul 2, 2020 at 9:34 AM Chris Angelico <rosuav@gmail.com> wrote:
(Not sure why this is on python-ideas - wouldn't python-list be more appropriate? Keeping it where it is for now though.)
As someone not familiar with the other lists...why? It's a proposal of an idea that could use some debate. Isn't this the perfect place?

On Thu, Jul 2, 2020 at 7:06 PM Alex Hall <alex.mojaki@gmail.com> wrote:
What's the idea being discussed? AIUI there's no need or request to change the language/stdlib, but maybe I'm misreading. This is a cute showcase of how a class namespace can be used to gather multiple functions together and build a property with them; it already works without any changes. So it's a great thing to discuss, but with no changes being proposed, it's not really an idea for the language. On python-list, there'll be more people, many of whom wouldn't think to hang out here, but could still appreciate (a) this cool way of doing properties, and (b) the more general notion that a decorated class or metaclass can be used for this kind of very powerful namespacing. ChrisA

On Thu, Jul 2, 2020 at 8:38 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
Let's get concrete then. Suppose that the property constructor recognized a special one-argument form: if it has an fget attribute, it fetches its details from the fget, fset, fdel, and __doc__ attributes of that object. That way, it can be used in three ways: a standard constructor that does all the work with up to four arguments, the decorator form (which is the same as the one-arg form initially, but then you can use its setter decorator), and the class form. class Test: x = property(lambda self: 42, lambda self, val: print("Setting to", val)) @property def y(self): return 42 @y.setter def y(self, val): print("Setting to", val) # the theoretical one @property class z: def fget(self): return 42 def fset(self, val): print("Setting to", val) Since functions won't normally have fset attributes, this should be fairly safe - technically it'd be backward incompatible, but it's highly unlikely to break any real-world code. I don't write a lot of properties with setters, so I can't speak to the value of this, but it is at least plausible. (If you'd prefer to use "get" or "getter" or something in place of "fget", that would work fine. I used the same name as the keyword arg to property itself, but it can be anything.) ChrisA

On Thu, Jul 02, 2020 at 11:04:40AM +0300, Serhiy Storchaka wrote:
Perhaps Greg meant to say *up to* rather than "no less". class MyClass: @property def spam(): ... @spam.setter def spam(self, value): ... @spam.deleter def spam(self): ... That makes five by my count, however I hardly ever write a deleter so three is more common, and only occassionally a setter so one is most common :-) -- Steven

On 2/07/20 9:00 pm, Steven D'Aprano wrote:
Perhaps Greg meant to say *up to* rather than "no less".
What I said is true for sufficiently small values of 5. :-) You're right that it depends on how many operations you want. For reading and writing it's 3; if you want deleting as well it's 5. But 3 is still a lot less DRY than 1. -- Greg

Coincidentally, cython has a custom, deprecated syntax for properties that is actually pretty similar, and nice: cdef class Spam: property cheese: "A doc string can go here." def __get__(self): # This is called when the property is read. ... def __set__(self, value): # This is called when the property is written. ... def __del__(self): # This is called when the property is deleted. https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#p... I don't think the benefits are worth a core python change, but great minds.. Steve On Thu, Jul 2, 2020 at 11:42 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:

On 2/07/20 10:49 pm, Stestagg wrote:
Coincidentally, cython has a custom, deprecated syntax for properties that is actually pretty similar
Not entirely a coincidence -- I invented that syntax for Pyrex, and Cython inherited it. I'm a little disappointed to hear it's been deprecated. :-( -- Greg

Haha, I had no idea! That's great. fyi, judicious use of cython allowed our python team to build python components that far outperformed their java counterparts in some real-time middle-office financial processing jobs for a really large client. So thank you. I can see why using the standard python syntax might be considered better than the more elegant custom version, but yes, it's a bit sad Steve On Thu, Jul 2, 2020 at 12:27 PM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:

On 2/07/20 8:04 pm, Serhiy Storchaka wrote:
It has a problem with pickling (it is solvable).
Can you elaborate? The end result is a property object just the same as you would get from using @property or calling property directly. I don't see how it can have any pickling problems beyond what properties already have.
The larger problem is with using private (double underscored) variables and super().
I don't know what you're talking about here. I didn't use any double-underscore names in my example. -- Greg

On Thu, Jul 2, 2020, 6:30 AM Greg Ewing <greg.ewing@canterbury.ac.nz> wrote:
I think what he may have meant is that if you tried accessing a double-underscore property of the outer class from the get/set methods, it won't properly de-mangle. Similarly, if you wanted to overwrite a property by using this property approach in the sub-class, but also call super for the parent's class property getter from within the get/set this wouldn't work!?

My personal feeling: I would love this idea (DRY gets me almost every time) if it weren't for that awful, terrible `class` keyword hanging out there. I wouldn't call using class this way "abuse", exactly, but it could be a potential use for an old idea raised more than once in the past: some kind of submodule or namespace definition statement: class Test: @Property ns foo: # ns a new syntax meaning a namespace, or "submodule", object """Docstring for foo""" def fget(self): print("Getting foo") return self._foo def fset(self, x): print("Setting foo to", x) self._foo = x --- Ricky. "I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler On Thu, Jul 2, 2020 at 1:46 PM Matthew Einhorn <moiein2000@gmail.com> wrote:

On 3/07/20 5:42 am, Matthew Einhorn wrote:
Ah, I see what you mean. I don't think that's a fatal problem; double-underscore names don't seem to get used very much, and if you did want to use one, it would just mean you couldn't use this particular way of defining properties.
This is a problem with properties however you create them, and the same tecnhiques apply for dealing with it. You need to find the property object from the base class and extract its getter. -- Greg

02.07.20 13:26, Greg Ewing пише:
You are right since property objects currently are not pickleable by default. But it is possible to implement pickling support for property objects which will fail with your example (and I think third-party libraries do it). The difference is that full qualified names of getter and setter differ from the full qualified name of the property. It is solvable, but it may require more complex code.
Then try to use them. It would not work. class Test2: def __init__(self): self.__foo = 1 class foo(metaclass = Property): def get(self): return self.__foo Test2().foo class Test3Base: @property def foo(self): return 1 class Test3(Test3Base): class foo(metaclass = Property): def get(self): return super().foo + 1 Test3().foo
participants (8)
-
Alex Hall
-
Chris Angelico
-
Greg Ewing
-
Matthew Einhorn
-
Ricky Teachey
-
Serhiy Storchaka
-
Stestagg
-
Steven D'Aprano