@decorator syntax is sugar, but for what exactly?

X

xtian

Avner Ben said:
The "property" call resembles both classmethod, staticmethod and
instancemethod, but cannot be eliminated using the new function
decorator syntax, because of its m:1 nature - one property binds
together a getter, a setter etc., where staticmethod etc. change the
status of one function in one way.
[snip]

Talking about properties, I like the C# way of defining them, which is
straightforward and readable. The property begins like a method, but has
no argument list and includes a getter function with no arguments and a
setter function with one argument. Adapted to Python, it would look
something like:

class hasProperty:
def __init__(self,aProperty='')
self.aProperty = aProperty
def AProperty:
def get(self):
return self.aProperty
def set(self,value):
self.aProperty = value
obj = hasProperty()
obj.AProperty = 'test'
print obj.AProperty

I'm not sure that this application of the new syntax is much worse
than what you've got... (it *is* a bit hacky in that it's calling the
function it's wrapping, but there you go).
return property(*f())
def __init__(self):
self._foo = 1

@property_
def foo():
def get(self):
print "get"
return self._foo
def set(self, val):
print "set"
self._foo = val
return get, set

get
3


What do people think of something like this?

Cheers,
xtian
 
X

xtian

[some badly-formatted code]

Oops - sorry about the nasty layout there - another example of why tabs are evil. ;)

This is what I meant:

def property_(f):
return property(*f())

class DecorationTest(object):
def __init__(self):
self._foo = 1

@property_
def foo():
def get(self):
print "get"
return self._foo
def set(self, val):
print "set"
self._foo = val
return get, set


t = DecorationTest()
print t.foo
t.foo = 3
print t.foo


xtian
 
A

Avner Ben

xtian said:
[snip]

def property_(f):
return property(*f())

Where has the property Without Underscore come from?
[snip]

t = DecorationTest()
print t.foo
t.foo = 3
print t.foo

Will you please explain how the assignment and retrieval work?

Avner.
 
A

Avner Ben

Mark said:
[snip]
I, personally, don't like the idea of overloading def in this way. To
me, 'def' defines something that looks and acts like a function, just
like 'class' defines something that looks and acts like a class, or
'while' defines something which looks and acts like a loop. AProperty is
does not act like a function, so using def would be misleading.

Properties do not look like functions but are implemented as functions.
there is no requirement that a variable must hide behind a property, or
that it Must allow both read and write access.
[Ship]
property a:
def __init__( s, v):
__set__( s, v)

Why property constructor?
Taking this to the extreme, all existing statement types (class, def,
print, del, etc.) could be recast as these generalized statements. We
could even allow subclassing of existing statements to modify their
behavior (for example, class_def subclassing def to make the new
function a class method). Strong Kung-Fu indeed. Scary, but strong.

I recall reading somewhere That the Beta language allowed subclassing
anything
Obviously, creating new control constructs is not something we'd want to
do every day, as it can be a great way to obfuscate code beyond all hope
of understanding--but the same is true of meta-classes. Just because it
*could* be abused doesn't mean that it would be.

IMO creating New Control constructs is a great and Much needed idea.

Avner.
 
X

xtian

Avner Ben said:
xtian said:
[snip]

def property_(f):
return property(*f())

Where has the property Without Underscore come from?

property has been a builtin since 2.2, along with classmethod and
staticmethod - it's part of the new-style classes guff.

See: http://www.python.org/2.2/descrintro.html#property
(You'll probably want to read the whole thing.)

[snip]

t = DecorationTest()
print t.foo
t.foo = 3
print t.foo

Will you please explain how the assignment and retrieval work?

Avner.

The key to it is that the property call makes foo a descriptor, with
__get__ and __set__ methods that use the getter and setter we passed
in. That's what triggers the attribute-getting and -setting methods.

The descriptor API is a big part of the type/class unification. here's
some more information: http://www.python.org/peps/pep-0252.html
(I haven't actually finished reading it myself - if my explanation's a
bit garbled, ignore it and read the link. :)

Hope that helps!
xtian
 
J

Jeff Shannon

When I see f( x ), I think that f is a function bound by def (or an
extremely close relative, such as class or staticmethod), and that I can
grep for it somewhere, either in the source code or the library
reference. I also think, rightly or wrongly, that f *was* bound,
*before* I needed it, and *does not change over time*.

When I see apply( f, x ), I think that f varies over time, and is some
sort of a callback or plugin or whatever, and is *not* the name of an
actual function bound by def.

Um, I think you're missing the point, here. The apply() has nothing to
do with whether func() is bound by def or by assignment -- both cases
given here are intended to allow the easy passing of variable argument
lists to functions.

In other words, you're thinking of calling a function through a function
pointer, but what's actually being shown is passing a pointer to a
parameter struct. The old style requires a separate function [apply()]
to be called that dereferences the parameter struct and feeds parameters
to the called function one-by-one, while the new style dereferences the
parameter struct in-place.

If you're going to compare this to any real C/C++ construct, it's much
closer to the issue of (C) pass-by-pointer vs. (C++) pass-by-reference
syntax, but even that's a really distant stretch.

Jeff Shannon
Technician/Programmer
Credit International
 
G

gohaku

In case it has not been brought up before...

@decorator reminds me too much of Perl arrays.
 
D

Dan Sommers

Dan Sommers wrote:
Um, I think you're missing the point, here. The apply() has nothing to
do with whether func() is bound by def or by assignment -- both cases
given here are intended to allow the easy passing of variable argument
lists to functions.

I think you're right: I have at least two issues all wound up together
here. My background is such that if you need different argument lists,
then you need different functions. It's amazing how flexible something
as outwardly simple as apply can be.

I will reduce my original notion to f( x ) screams "this is pretty much
the same all the time," but apply indicates something more interesting.

Thanks for being so patient with this old timer. :)

Dan
 
S

Steven Bethard

Andrew Durdin said:
Well, I think I agree with the principle of limiting the expressions
to dotted names. Some of the possibilities if you allow arbitrary
expressions are pretty hairy:

"""
Things someone might want to do, ordered roughly from most reasonable
to least reasonable ;)
@foo().bar()
@foo or bar
@mydecorators['foo']
@lambda f: foo(f) or bar(f)
"""
(from http://mail.python.org/pipermail/python-dev/2004-August/046673.html)

I don't remember who, but someone already mentioned that you can do
all of these things with the limited syntax if you really want to,
either with operator.* or eval:

@eval("foo().bar()")
@eval("foo or bar")
@operator.getitem(mydecorators, 'foo')
@eval("lambda f: foo(f) or bar(f)")

There are probably ways to avoid some of the evals, but I haven't
really thought too hard about it.

Steve
 
D

David Eppstein

I don't remember who, but someone already mentioned that you can do
all of these things with the limited syntax if you really want to,
either with operator.* or eval:

@eval("foo().bar()")
@eval("foo or bar")
@operator.getitem(mydecorators, 'foo')
@eval("lambda f: foo(f) or bar(f)")

A little more cleanly like this?

def id(x): return x

@id(foo().bar())
@id(foo or bar)
@id(mydecorators['foo'])
@id(lambda f: foo(f) or bar(f))
 
S

Skip Montanaro

Roy> Does the proposed mechanism support something like (to use one of
Roy> Dan's exmaples, written with two different syntaxen):

...

Yes (using a class instead of a module simply for convenience):

class martha:
memo = {}

@staticmethod
def memoize(func):
if func not in martha.memo:
martha.memo[func] = {}
def _inner(*args, **kwds):
items = kwds.items()
items.sort()
items = tuple(items)
key = (args, items)
try:
val = martha.memo[key]
except KeyError:
val = func(*args, **kwds)
martha.memo[key] = val
return val
return _inner

@martha.memoize
def fib(n):
assert n >= 0
print n
if n <= 1:
return 1
return n + fib(n-1)

print fib(5)
print fib(4)

Running that yields this output:

5
4
3
2
1
15
10

Skip
 
X

xtian

Skip Montanaro said:
class martha:
memo = {}

@staticmethod
def memoize(func):
if func not in martha.memo:
martha.memo[func] = {}
def _inner(*args, **kwds):
items = kwds.items()
items.sort()
items = tuple(items)
key = (args, items)
try:
val = martha.memo[key]
except KeyError:
val = func(*args, **kwds)
martha.memo[key] = val
return val
return _inner

@martha.memoize
def fib(n):
assert n >= 0
print n
if n <= 1:
return 1
return n + fib(n-1)

print fib(5)
print fib(4)

Running that yields this output:

5
4
3
2
1
15
10

Skip


Minor typo - I think the references to martha.memo[key] in _inner
should be to martha.memo[func][key], enabling you to prevent different
memoized functions' results from clashing when they were called with
the same parameters.

You can check this by adding another memoized function:

@martha.memoize
def fact(n):
assert n >= 0
print n
if n <= 1:
return 1
return n * fact(n-1)

print "fact(5) = %d" % fact(5)

If you add that to the end of the file, it'll give the wrong answer
without the change.

Sorry Skip - obviously you know this, which is why you've got the
martha.memo[func] = {} line there. I just thought it was worth
pointing out in case someone put it into their toolkit and then got
bit much later on when they memoized a second function that was called
with the same arguments.

It's a nice example of a useful decorator though - the syntax is
definitely growing on me, especially in a syntax-highlighted editor
where the def is more prominent because of colouring.

Thanks,
xtian
 
A

Arthur

Yes (using a class instead of a module simply for convenience):

class martha:
memo = {}

@staticmethod
def memoize(func):
if func not in martha.memo:
martha.memo[func] = {}
def _inner(*args, **kwds):
items = kwds.items()
items.sort()
items = tuple(items)
key = (args, items)
try:
val = martha.memo[key]
except KeyError:
val = func(*args, **kwds)
martha.memo[key] = val
return val
return _inner

@martha.memoize
def fib(n):
assert n >= 0
print n
if n <= 1:
return 1
return n + fib(n-1)

print fib(5)
print fib(4)

Running that yields this output:

5
4
3
2
1
15
10

Skip

Is it me, or this a chilling argument agaisnt where things are going.

I understand it is illustrative, and nobdy is advertiging it as
anything else.

But when there is a lot less going on than meets the eye ...

not good.

Art
 
S

Skip Montanaro

Yes (using a class instead of a module simply for convenience):
...

Art> Is it me, or this a chilling argument agaisnt where things are
Art> going.

I'm not sure what you're getting at. Nobody said a decorator's
implementation had to be simple. It can, after all, perform pretty
arbitrary transformations.

Art> But when there is a lot less going on than meets the eye ...

Again, I'm confused. Look at just the fib() definition:

@martha.memoize
def fib(n):
assert n >= 0
print n
if n <= 1:
return 1
return n + fib(n-1)

The decorator says the function will be memoized. That memoizing an
arbitrary function's values using a dict isn't entirely straightforward
shouldn't prevent people from being able to easily memoize functions whose
outputs only depend on their inputs. By analogy, I use urllib.urlopen() all
the time without completely understanding all the ins and outs of what it
does.

Perhaps the decorator would be better named "idempotent"?

Or were you objecting to something else?

Skip
 
M

Mark Bottjer

Avner said:
Properties do not look like functions but are implemented as functions.
there is no requirement that a variable must hide behind a property, or
that it Must allow both read and write access.

Agreed. I was providing what I thought might be a common case. The fact
that properties can distill functionality into something that looks like
a variable is partly why they're so powerful.
[Ship] property a:
def __init__( s, v):
__set__( s, v)

Why property constructor?

Why not? It's as good a way as any to give a variable-backed property an
initial value. Setting it directly would break the encapsulation of the
property.
I recall reading somewhere That the Beta language allowed subclassing
anything

Yep. Because Beta has no classes, but rather works on a prototype
system. Everything is an object. The "class" of an object is the
interface it supports at that particular time. To create a new object,
clone an existing one, and modify it to taste.

Seriously powerful. Seriously hard to keep track of. Neat language.

-- Mark
 
S

Skip Montanaro

Minor typo - I think the references to martha.memo[key] in _inner should
be to martha.memo[func][key], enabling you to prevent different memoized
functions' results from clashing when they were called with the same
parameters.

Thanks for the correction.

Skip
 

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

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top