Enhanced property decorator

Discussion in 'Python' started by Daniel, Aug 26, 2008.

  1. Daniel

    Daniel Guest

    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
    Daniel, Aug 26, 2008
    #1
    1. Advertising

  2. Daniel

    Benjamin Guest

    On Aug 25, 8:45 pm, Daniel <> wrote:
    > 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
    Benjamin, Aug 26, 2008
    #2
    1. Advertising

  3. Daniel

    Daniel Guest

    On 25 Aug, 21:52, Benjamin <> wrote:
    > ... 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
    Daniel, Aug 26, 2008
    #3
  4. Daniel

    Benjamin Guest

    On Aug 25, 9:00 pm, Daniel <> wrote:
    > On 25 Aug, 21:52, Benjamin <> wrote:
    >
    > > ... 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.


    It takes the getter's docstring as usual.
    >
    > ~ Daniel
    Benjamin, Aug 27, 2008
    #4
  5. En Mon, 25 Aug 2008 22:45:36 -0300, Daniel <> escribió:

    > 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()

    --
    Gabriel Genellina
    Gabriel Genellina, Sep 2, 2008
    #5
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. glomde
    Replies:
    5
    Views:
    513
    glomde
    Mar 29, 2007
  2. Zaza
    Replies:
    0
    Views:
    612
  3. Rafe
    Replies:
    12
    Views:
    471
    Peter Otten
    Oct 29, 2008
  4. Phlip
    Replies:
    2
    Views:
    276
    Phlip
    Oct 23, 2010
  5. Laurent
    Replies:
    6
    Views:
    505
    Laurent
    Nov 11, 2011
Loading...

Share This Page