PEP318: property as decoration

Sean Ross frobozz_electric at hotmail.com
Tue Jun 10 13:58:41 EDT 2003


I think that function/method decoration does not apply easily to properties.
Defining a property like so:

def foo(self, value=None) as property:
    def fget():
        return self._foo
    def fset():
        self._foo = value

or like so:

def foo() as property(fget=fget, fset=fset):
    def fget(self):
          ...
     ...

or however it's meant to be done, seems problematic. For one thing, a
property is not strictly a method, and probably should not be defined as
such. It *uses* methods, but it is not itself a method. Or that's my
understanding (I may be way off base). Regardless, there are other issues.

The decorator syntax is meant to replace the current idiom:

def bar():
    pass
bar = staticmethod(bar)

with

def bar() as staticmethod:
    pass

where "bar" is defined as a method; that method is passed to "staticmethod";
and "bar" is then re-bound to the resulting transformed method. And for
multiple decorators, the idea is to compose a series of transformations
before re-binding "bar", like so:

def bar() as staticmethod, otherdecorator:
    pass
bar = staticmethod(otherdecorator(bar))

# or, bar = otherdecorator(staticmethod(bar)), whichever the case may be...

The issue here is that each of these decorators takes one and only one
argument.

When you wish to use a decorator that takes more than one argument you begin
to run into trouble. For instance:

Suppose we wish to make a contract decorator, in order to facilitate the
creation of eiffel-like methods. We could say:

# [1]
def eiffel() as contract:
    def fpre():
          ...
    def fpost():
          ...

or perhaps:

# [2]
def eiffel() as contract(fpre=fpre, fpost=fpost):
     def fpre():
          ...
     ...

which would be equivalent to something like the following:

def fpre():
     ...
def fpost():
     ...
def eiffel():
     ...
eiffel = contract(eiffel, fpre, fpost)

The problem is, how do we pass fpre, and fpost into contract using our new
decorator syntax?

For [1], we could say, well the interpreter will create the function eiffel,
extract the fpre and fpost nested methods somehow, and pass those methods,
along with eiffel itself, to contract for transformation. OK, but what about
when you use multiple decorators?

def eiffel() as staticmethod, contract:
     def fpre():
          ...
     ...

How will the interpreter decide which methods need to be pulled out of
eiffel's definition, and to which decorator those methods need to be passed?

If we choose [2], we a somewhat better off, but we are left with the issue
of resolving what it means to say contract(fpre, fpost). Is this called
as-is? Is it called as contract(eiffel, fpre, fpost)? Probably the latter is
what's intended. Will there be conflicts if there exists an fpre or fpost
method that has been defined outside of eiffel's scope? e.g.:

def fpre():
    ...
def eiffel() as contract(fpre, fpost):
     def fpre():
          ...
     ...

If so, perhaps it would be better to do something like this:

# [3]
def eiffel() as contract(eiffel.fpre, eiffel.fpost):
     def fpre():
          ...
     ...

Where we create the method eiffel, which has nested methods fpre, and fpost
which are accessible as attributes (something which cannot currently be
done), and then contract is interpreted using eiffel, eiffel.fpre, and
eiffel.fpost as arguments:

contract(eiffel, eiffel.fpre, eiffel.fpost)

The results of which are then bound to eiffel. Something like this could
possibly work. But you'd have to allow access to a functions nested
functions. What issues does that raise?

Anyway, moving back to properties. For property to be used cleanly as a
decorator in the way people seem to be hoping for, you would likely need to
be able to do something like [2], or [3]. So a property definition would be
something like this:

def foo() as property(foo.fget, foo.fset):
     def fget(self):
          ...
     ...

Plus you would need to change property so that it could take a functor as
its first argument, e.g.,

property(foo, foo.get, foo.set)

so that it could be used in the same manner as all other decorator methods.


Personally, the current way to define a property doesn't bother me much. The
only issue I have with it is that it clutters the class/instance scope with
expendable methods getfoo, setfoo. They are expendable in that they are not
called directly by instances of the class (although they could be!), instead
the property is used. Other than that, I'm ok with it. It's explicit and
it's pretty clear and simple to use.

def getfoo(self):
     return self._foo
def setfoo(self, value):
     self._foo = value
foo = property(getfoo, setfoo)


Still, it might be nice to take advantage of something like blocks. In this
way, you could define get/set methods for each property, using the same name
("get", "set") each time, without conflict or clutter because the defined
methods would be local to the block.

For example, the following is a hypothetical property definition using
something like a Ruby block:

def once():
     yield None

foo = once() do *args:
     def get(self):
          return self._foo
     def set(self, value):
          self._foo = value
     return property(get, set)

I'm not overly familiar with Ruby blocks, so I've no idea if something like
this would work in that langauge. But, the idea here is that the block is
associated with a generator function (as I believe they are in Ruby). When
the generator yields None, the value is passed as an argument to the
associated block, which then executes. This block has its own scope, inside
of which we define our get/set methods and return a property using those
methods. The returned property is assigned to foo. Here I'm trying to stay
as close to the idea of Ruby blocks (as I understand them) as possible, so
the generator function association is required. You could not for instance
do the following:

foo = do:
     def get(self):
          return self._foo
     def set(self, value):
          self._foo = value
     return property(get, set)

which, to my mind, would mean that you'd assigned an anonymous function,
taking zero arguments and returning a property, to foo. Although you *could*
do this:

foo = foo()

afterwards and, now, foo *would* be a property.

One issue that immediately springs to mind with this approach is that it
introduces a second way to define functions/methods. I like that "there's
preferably only one way to do things" in this language, so I'm NOT overly
fond of this idea.

Another tactic, would be to introduce "thunks", as have been discussed on
Python-Dev. I can't really go much into detail describing thunks, as I'm not
certain I've fully understood them myself. Regardless, I will try to
describe them in the terms that I mean for them to be used.

My idea of a thunk is something like the following:

variablename = functionname with [[arg,]*, [*args]?, [**kwds]?]?:
    suite

or, more simply:

var = func with x, y, z=None:
    #do stuff with x, y, z

Here "with x, y, z=None: ..." declares a thunk, call it T. This thunk is
passed to func as an argument, i.e. func(T). So the result of this construct
is equivalent to:

var = func(T)

where T is the thunk.

Inside of func, T can be called in the same way that any other first class
function would be. Taking this idea, and applying it to the definition of
properties, we could do the following:

foo = property with:
     def fget(self):
          return self._foo
     def fset(self, value):
          self._foo = value
     def fdel(self):
          del self._foo
     fdoc = "foo"
     return fget, fset, fdel, fdoc

This would be equivalent to the following:

foo = property(thunk)

where thunk is the anonymous function defined after "with:" that returns a
get,set,and del method, as well as a doc string. The method "property" could
be changed to accept a function as an argument. It could then evaluate that
function, retrieve the get,set, del methods and doc string, and construct a
property with which to bind foo. Something like this:

def myproperty(thunk):
    fget, fset, fdel, fdoc = thunk()
    return property(fget, fset, fdel, fdoc)

def propertythunk():
     def fget(self):
          return self._foo
     def fset(self, value):
          self._foo = value
     def fdel(self):
          del self._foo
     fdoc = "foo"
     return fget, fset, fdel, fdoc

class MyClass(object):
     def __init__(self):
          self._foo = "foo"

 foo = myproperty(propertythunk)

x = MyClass()
print x.foo  # "foo"
x.foo = "changed"
print x.foo  # "changed"

# BTW, the above code works, if you'd care to try it out...


I find thunk syntax appealing. Saying

foo = property with:
         suite

reads like "make 'foo' a property with the following suite", which is what
it does.

Moreover, thunks have a wider utility than just for making properties. For
instance, thunks could be used in file processing situations to ensure that
the file is closed after it is used...there's a longer, better reasoned,
discussion of this idea on Python-Dev (January/February 2003).

Anyway, these are just some ideas. Both thunks and blocks require adding new
keywords to the language, and both also add powerful, but sometimes
confusing, abilities. For the most part, I'm just fine having neither. But,
I'd probably not regret having them either, except when people start
defining there methods like so:

params = do include=[], exclude=[]:
        "returns a dictionary of parameter names and values"
        import inspect
        args, varargs, varkw, defaults =
inspect.getargvalues(inspect.stack()[1][0])
        if not include:
            include = args[:]
        ...etc...








More information about the Python-list mailing list