Enhanced property decorator

D

Daniel

I've often been frustrated by the inability of the built-in property
descriptor to handle anything other than a read-only property when
used as a decorator. Furthermore, read/write/delete properties take
their doc-string and property definition at a non-intuitive and
awkward place (after the getter/setter/delter functions). The
following are three possible solutions to this problem (inspired by
message http://groups.google.com/group/comp.lang.python/msg/9a56da7ca8ceb7c7).
I don't like the solution in that thread because it uses apply() which
will go away in Python 3.

Solution 1: new built-in function/descriptor

def prop(func):
funcs = dict(enumerate(func()))
return property(funcs[0], funcs[1], funcs.get(2), func.__doc__)

class Test(object):
@prop
def test():
"""test doc string"""
def fget(self):
return self._test
def fset(self, value):
self._test = value
def fdel(self):
del self._test
return fget, fset, fdel

Of course the name (prop) could be changed... I couldn't think of
anything more concise.

Pros:
(1) encapsulation of property logic inside function namespace,
preventing clutter in class namespace.
(2) doc string appears in a more natural place, before getter/setter/
delter logic, as in classes and functions.

Cons:
(1) additional built-in name.
(2) duplication/boilerplate in return line (DRY violation).


Solution 2: Enhance the built-in property descriptor, so if it
receives a generator function taking no arguments as its first and
only argument it treats the generated result similar to the above prop
function. Example:

import types

def property_(fget, fset=None, fdel=None, doc=None):
if fget is not None and fset is None and fdel is None and doc is
None \
and fget.func_code.co_argcount == 0:
# is there a way to detect a generator function without
calling it?
gen = fget()
if isinstance(gen, types.GeneratorType):
result = list(gen)
assert len(result) == 1, "<oops! generated wrong number of
results>"
funcs = dict(enumerate(result[0]))
doc = fget.__doc__
fget = funcs[0]
fset = funcs[1]
fdel = funcs.get(2)
# normal property logic follows
return property(fget, fset, fdel, doc)

class Test(object):
@property_
def test():
"""test doc string"""
def fget(self):
return self._test
def fset(self, value):
self._test = value
def fdel(self):
del self._test
yield fget, fset, fdel

Pros:
(1) overloaded use of existing property descriptor prevents built-in
clutter.
(2) encapsulation of property logic inside function namespace,
preventing clutter in class namespace.
(3) doc string appears in a more natural place, before getter/setter/
delter logic, as in classes and functions.

Cons:
(1) possible unexpected behavior when zero-argument function (fget) is
called.
(2) duplication/boilerplate in yield line (DRY violation).

It would take two "errors" to get the "unexpected" behavior in this
second solution. First, property getters normally (always?) take a
'self' argument. Second, the function must be a generator yielding a
single result (not a common type of property getter). It would be best
to have some way of knowing if the function is in fact a generator
before calling it since that would result in the least amount of
surprise.


Solution 3: Possibly the most controversial, but arguably the most
elegant solution:

class Test(object):
@property
def test():
"""test doc string"""
yield(self):
return self._test
yield(self, value):
self._test = value
yield(self):
del self._test

This option would require new syntax for the yield keyword, which
would yield an unnamed function object (similar to a lambda object).
The important part is that the no-argument generator function passed
to property would yield up to three callables, which would be used in
order as the getter/setter/delter for the property. The doc string for
the property would be the doc string from the generator function. An
implementation of the additional property logic needed for this
solution is postponed until there is sufficient interest.

Pros:
(1) overloaded use of existing property descriptor prevents built-in
clutter.
(2) encapsulation of property logic inside function namespace,
preventing clutter in class namespace.
(3) doc string appears in a more natural place, before getter/setter/
delter logic, as in classes and functions.

Cons:
difficult to implement?

Of course, more error checking would be necessary if any these
enhancements were added to the standard library.

Thoughts?

~ Daniel
 
B

Benjamin

I've often been frustrated by the inability of the built-in property
descriptor to handle anything other than a read-only property when
used as a decorator. Furthermore, read/write/delete properties take
their doc-string and property definition at a non-intuitive and
awkward place (after the getter/setter/delter functions). The
following are three possible solutions to this problem (inspired by
messagehttp://groups.google.com/group/comp.lang.python/msg/9a56da7ca8ceb7c7).
I don't like the solution in that thread because it uses apply() which
will go away in Python 3.

I didn't read the rest of the thread, but I think Python 2.6 may have
want you want:

class A(object):

@property
def my_prop(): return self._prop

@my_prop.setter
def my_prop(prop): self._prop = prop

@my_prop.deleter
def my_prop(): del self._prop
 
D

Daniel

... I think Python 2.6 may have
want you want:

class A(object):

    @property
    def my_prop(): return self._prop

    @my_prop.setter
    def my_prop(prop): self._prop = prop

    @my_prop.deleter
    def my_prop(): del self._prop

Hmm, interesting. I wonder if it suppports setting the doc-string in a
similar way? I'll have to look into that. Thanks for pointing this
out.

~ Daniel
 
B

Benjamin

Hmm, interesting. I wonder if it suppports setting the doc-string in a
similar way? I'll have to look into that. Thanks for pointing this
out.

It takes the getter's docstring as usual.
 
G

Gabriel Genellina

I've often been frustrated by the inability of the built-in property
descriptor to handle anything other than a read-only property when
used as a decorator. Furthermore, read/write/delete properties take
their doc-string and property definition at a non-intuitive and
awkward place (after the getter/setter/delter functions). The
following are three possible solutions to this problem (inspired by
message http://groups.google.com/group/comp.lang.python/msg/9a56da7ca8ceb7c7).
I don't like the solution in that thread because it uses apply() which
will go away in Python 3.

Solution 1: new built-in function/descriptor

def prop(func):
funcs = dict(enumerate(func()))
return property(funcs[0], funcs[1], funcs.get(2), func.__doc__)

class Test(object):
@prop
def test():
"""test doc string"""
def fget(self):
return self._test
def fset(self, value):
self._test = value
def fdel(self):
del self._test
return fget, fset, fdel

Of course the name (prop) could be changed... I couldn't think of
anything more concise.

Pros:
(1) encapsulation of property logic inside function namespace,
preventing clutter in class namespace.
(2) doc string appears in a more natural place, before getter/setter/
delter logic, as in classes and functions.

Cons:
(1) additional built-in name.
(2) duplication/boilerplate in return line (DRY violation).

(Some days late, sorry...) I like the variant below, based on your code. It avoids issue (2) by using locals(). Works with Python 3.0 too - and don't use tricks like sys._getframe or sys.settrace:

def defproperty(func):
impls = func()
return property(doc=func.__doc__, **impls)

class Test(object):
@defproperty
def test():
"""test doc string"""
def fget(self):
return self._test
def fset(self, value):
self._test = value
def fdel(self):
del self._test
return locals()
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top