Docorator Disected

E

El Pitonero

Martin said:
Please read the statements carefully, and try to understand the mental
model behind them. He did not say that you can pass around multiple
sets of arguments. He said that functions (not function calls, but
the functions themselves) are objects just like numbers. There is
a way of "truly" understanding this notion, and I would encourage
you to try doing so.

I have the same feeling as Martin and Bengt. That is, Ron you are still
not getting the correct picture. The fact that you have three-level
nested definition of functions is almost incidental: that's not the
important part (despite the nested scope variables.) The important part
is that you have to understand functions are objects.

Perhaps this will make you think a bit more:

x=1

if x==1:
def f(): return 'Hello'
else:
def f(): return 'Bye'

for x in range(3):
def f(x=x):
return x

Do you realize that I have introduced 5 function objects in the above
code? Do you realize that function objects could be created *anywhere*
you can write a Python statement? Whether it's inside another function,
or inside a if...else... statement, or inside a loop, doesn't matter.
Whereever you can write a Python statement, you can create a function
there. I don't know what your previous programming language is, but you
have to stop treating functions as "declarations". The "def" is an
executable statement.

Another example:

def f():
return f

g = f()()()()()()()()()()()

is perfectly valid.
 
R

Ron_Adam

Please read the statements carefully, and try to understand the mental
model behind them. He did not say that you can pass around multiple
sets of arguments. He said that functions (not function calls, but
the functions themselves) are objects just like numbers. There is
a way of "truly" understanding this notion, and I would encourage
you to try doing so.

Hello Martin,

It is interesting how sometimes what we already know, and a new
situation presented in an indirect way, can lead us to viewing an
isolated situation in a biased way.

That's pretty much the situation I've experienced here with this one
point. I already knew that functions are objects, and objects can be
passed around. My mind just wasn't clicking on this particular set of
conditions for some reason, probably because I was looking too closely
at the problem.

(Starting off as a tech, with knowledge of how microchips work, can
sometimes be a obstacle when programming in high level languages.)

I'm sure I'm not the only one who's had difficulties with this. But
I'm somewhat disappointed in myself for not grasping the concept as it
is, in this particular context, a bit sooner.

Cheers,
Ron
 
R

Ron_Adam

That phraseology doesn't sound to me like your concept space is quite isomorphic
with reality yet, sorry ;-)

You'll be happy to know, my conceptual conceptions are conclusively
isomorphic this morning. :)
It sounds like you are thinking of "multiple sets of arguments"
as an aggregate that is passed as such, and that isn't happening, as I believe El Pitonero
is trying to indicate with his parenthesized visualization below.

Well there are multiple sets of arguments, and there are multiple
functions involved. It's just a matter of how they get matched up.
Depending on what level you look at it, it could be both ways. But the
correct way to view it is in the context of the language it self, and
not the underlying byte code, c++ or assembly code.
What is happening is that an expression "foo(2)(6)" is being evaluated left to right.
First foo as a name evaluates to whatever it is bound to, which is the foo function.
Then () is the calling operator, which says evaluate the list inside the parens left to right
and call the thing you had so far, which was foo here. The arg list was just 2, so foo is called
with 2, and foo returns something, with which we will do the next operation if there is one.

Like this of course:

def foo(x):
def fee(y):
return y*x
return fee

statement: z = foo(2)(6)
becomes: z = fee(6)
becomes: z = 12

The position of the 'def fee' inside of 'def foo' isn't relevant, it's
only needed there so it can have access to foo's name space. It could
be at the top or bottom of the function it is in, and it wouldn't make
a difference.

This would be the same without the nesting:

def foo(xx):
global x
x = xx
return fee

def fee(y):
global x
return y*x

z = foo(2)(6)

So if you are seeing (2)(6) as something to pass, as opposed to a sequence of operations, I think there's
a misconception involved. Perhaps I am taking your words askew ;-)

It's not entirely a misconception. Lets see where this goes...
1 0 LOAD_NAME 0 (foo)
3 LOAD_CONST 1 (2)
6 CALL_FUNCTION 1
9 LOAD_CONST 2 (6)
12 CALL_FUNCTION 1
15 RETURN_VALUE

In this example, you have byte code that was compiled from source
code, and then an interpreter running the byte code; which in it self,
is a program written in another language to execute the byte code,
C++; which gets translated into yet another language, assembly; which
at one time would have corresponded to specific hardwired registers
and circuits,(I could go further...ie... translators... PNP...
holes...), but with modern processors, it may yet get translated still
further.

While all of this isn't relevant, it's knowledge in my mind, and
effects my view of programming sometimes.

Now take a look at the following descriptions of the above byte codes
from http://docs.python.org/lib/bytecodes.html


LOAD_NAME namei
Pushes the value associated with "co_names[namei]" onto the stack.

LOAD_CONST consti
Pushes "co_consts[consti]" onto the stack.

CALL_FUNCTION argc
Calls a function. The low byte of argc indicates the number of
positional parameters, the high byte the number of keyword parameters.
On the stack, the opcode finds the keyword parameters first. For each
keyword argument, the value is on top of the key. Below the keyword
parameters, the positional parameters are on the stack, with the
right-most parameter on top. Below the parameters, the function object
to call is on the stack.

RETURN_VALUE
Returns with TOS to the caller of the function.

*TOS = Top Of Stack.

The calling routine, puts (passes) the second set of arguments onto
the stack before calling the function returned on the stack by the
previous call.

Which is exactly how I viewed it when I referred to coming full circle
and the second sets of arguments are pass with a "stack(?)".

Or it could be said equally the functions (objects) are passed with
the stack. So both view are correct depending on the view point that
is chosen.

Cheers,
Ron
 
R

Ron_Adam

Martin v. Löwis wrote:
Perhaps this will make you think a bit more:

Now my problem is convincing the group I do know it. LOL

Another example:

def f():
return f

g = f()()()()()()()()()()()

is perfectly valid.

Good example! Yes, I realize it. As I said before I just haven't come
across this particular variation before using decorators so it wasn't
clear to me at first, it is now. :)

Read my reply to Bengt Richter.

Thanks, this has been a very interesting discussion.

Ron
 
R

Ron_Adam

No. There is no mechanism for passing multiple argument sets to
nested functions. Instead, functions are objects, which can be
assigned to variables, passed as arguments to other functions,
and returned:

Yes there is, it's the stack python uses to interpret the byte code.
But it's the same mechanism that is used for passing arguments to
sequential function calls (objects) also. The only difference is the
next function (object) is returned on the stack in the nested case.
Then the next argument is then put on to the stack (passed), before
the next function is called.

How you view this depends on the frame of reference you use, I was
using a different frame of reference, which I wasn't sure was correct
at the time, but turns out is also valid. So both view points are
valid.

In any case, I now have a complete picture of how it works. Inside,
and out. Which was my goal. :)
No, functions are objects. Notice that in step 1, the object returned
doesn't have to be a function - other things are callable, too, like
types, classes, and objects implementing __call__.

They are objects; which are data structures; containing program code &
data; which reside in memory; and get executed by, in this case, a
byte code interpreter. The interpreter executes the byte code in a
sequential manner, using a *stack* to call functions (objects), along
with their arguments.

For the record, I never had any trouble understanding the concept of
objects. I think I first started programming OOP in the mid '90's with
c++.

It was the sequence of events in the objects of the nested def
functions that I was trying to understand along with where the objects
get their arguments, which isn't obvious because of the levels of
indirect calling.

Thanks for the help Martin, it's always appreciated. :)

Cheers,
Ron
 
?

=?ISO-8859-1?Q?=22Martin_v=2E_L=F6wis=22?=

Ron_Adam said:
This would be the same without the nesting:

def foo(xx):
global x
x = xx
return fee

def fee(y):
global x
return y*x

z = foo(2)(6)

Actually, it wouldn't.

.... global x
.... x = xx
.... return fee
........ global x
.... return y*x
....48

So the global variable can be changed between the time foo returns
and the time fee is invoked. This is not the same in the nested function
case: the value of x would be bound at the time foo is called. It can
be modified inside foo, but freezes once foo returns.
It's not entirely a misconception. Lets see where this goes...

Hmm. If you think that this proves that (2)(6) is being *passed*, you
still might have a misconception. What this really does is:

0. Put foo on the stack. Stack is [value of foo]
3. Put 2 on the stack -> [value of foo, 2]
6. Call a function with one arg; invoking foo(2)
Put the result of this call back on the stack ->
[result of foo(2)]
9. Put 6 on the stack -> [result of foo(2), 6]
12. Call it, computing (result of foo(2))(6)
Put the result on the stack ->
[result of (result of foo(2))(6)]
13. Return top-of-stack, yielding foo(2)(6)

So at no point in time, (2)(6) actually exists. Instead,
when the 6 is being put onto the stack, the 2 is already gone.
It computes it one by one, instead of passing multiple sets
of arguments.
While all of this isn't relevant, it's knowledge in my mind, and
effects my view of programming sometimes.

There is nothing wrong with that. However, you really should try
to see what the interpreter actually does, instead of speculation
(of course, asking in a newsgroup is fine).
The calling routine, puts (passes) the second set of arguments onto
the stack before calling the function returned on the stack by the
previous call.

Sure - you need the arguments to a function before being able to
call the function. So there is always a set of arguments on the
stack, which internally indeed gets converted into a tuple right
before calling the function. However, at no point in time, there
are *two* sets of arguments.
Or it could be said equally the functions (objects) are passed with
the stack. So both view are correct depending on the view point that
is chosen.

Maybe I don't understand your view, when you said

# No, I did not know that you could pass multiple sets of arguments to
# nested defined functions in that manner.

However, they way I understood it, it seemed incorrect - there are
no multiple sets of arguments being passed, atleast not simultaneously.
It is, of course, possible to pass multiple sets of arguments
sequentially to multiple functions, eg.

a = len(x)
b = len(y)

Regards,
Martin
 
R

Ron_Adam

Actually, it wouldn't.

Ok, yes, besides the globals, but I figured that part is obvious so I
didn't feel I needed to mention it. The function call works the same
even though they are not nested functions.
Hmm. If you think that this proves that (2)(6) is being *passed*, you
still might have a misconception. What this really does is:

I didn't say they were passed at the same time by the stack. It just
shows my reference to *stacks* was correct, and that there's is an
underlying mechanism for calling functions and passing arguments and
functions that use the stack. I however was not yet aware (yesterday
afternoon) of just how the stack worked in this case. This was very
much a figure it out as you go exercise.

Yesterday, I had made the incorrect judgement that since the functions
are all nested inside a defined function, that I should treat them as
a group instead of individual functions. But that wasn't the correct
way of viewing it. They are in a group in that they share name space,
so I figured, (incorectly), that they shared an argument list somehow,
and those where passed to the group. The passing of the function, and
it's arguments silently was a big reason for me jumping to this
conclusion.

So my reference to:

Which is not correct, as the order of events is wrong and they do not
share a common argument list.

The correct order is:

return fee
fee(6)

with the fee(6) being evaluated after the return statement is
executed.

Another contributing factor is two days of really poor sleep. Which
probably is a bigger factor than I would like to admit. I really feel
I should have gotten it much sooner. But I did get-it, a little bit
at a time, and had a lot of terrific help along the way. :)

Maybe I don't understand your view, when you said

# No, I did not know that you could pass multiple sets of arguments to
# nested defined functions in that manner.

My views have changed as I added the missing peices to the puzzle
yesterday.

At first I didn't see how they were passed at all, in a group or
otherwise. There wasn't any one-to-one way to match the arguments up
visually like there are in a normal function call.

My next thought was they are passed as a group, to the group of
defined functions that shared the same name space. (Everyone seems to
think I'm stuck on this one.)

My Next view, yesterday afternoon, was they were passed on a stack
somehow one at a time. This last one is not necessarily incorrect from
a byte code viewpoint, but it's not the best way to view the problem.

Today I believe I have the correct view as I've said this morning. I
could be wrong yet again. I hope not though I might have to give up
programming. :/

It's interesting that I have had several others tell me they had
trouble with this too.

So it is my opinion that decorators are a little too implicit. I
think there should be a way to make them easier to use while achieving
the same objective and use.


Thanks again for the reply, :)

Cheers,
Ron
 
B

Bengt Richter

Ok, yes, besides the globals, but I figured that part is obvious so I
didn't feel I needed to mention it. The function call works the same
even though they are not nested functions.

I am afraid that is wrong. But be happy, this may be the key to what ISTM
is missing in your concept of python functions ;-)

What you don't seem to grok is what the def statement really does.
When you compile a def statement, you code, but it's not principally the code
that runs when you call the function that is being defined.

When you compile a def statement, you get code that MAKES the function being defined,
from precompiled pieces (one of which is a code object representing the defined function)
and optionally code for evaluating default argument expressions. This becomes part of the
code generated by compiling the def statement. (Default argument value expressions are
the simplest examples).

When you work interactively as above, the interactive loop both compiles and executes
chunks as you go, so both your def foo and def fee would be compiled AND executed.

If you put def fee inside the body of foo, you get fee def-code inside the body of foo,
but it doesn't get executed, because foo hasn't been called yet, though the foo def
has been compiled and executed interactively (and thus the name foo is bound to
the finished foo function, ready to be called or otherwise used.

So, no, it does not "work the same." In fact, it is a misconception to talk about
a nested fee as if it existed ready to call in the same way as foo. It doesn't
exist that way until the fee def is EXECUTED, producing the callable fee.

It's something like the distinction between relocatable object files in C
representing a function and machine code representing the function in a dll.
The execution of def is like a little make operation that links the function
pieces (and in the case of Python may dynamically generate some of the pieces
with arbitrarily complex code, including decorators etc).

def is hard to grok at first, if you're coming from languages that don't
do it Python's way.

We can use dis to see the above clearly:

Let's see what the difference in code is for a simple function
with the body

fee = 'fee string'
return fee

and the body

def fee():
return 'nested fee result'
return fee

It's going to be returning whatever fee is either way, so what we need
to compare is how fee gets bound to something:
... fee = 'fee string'
... return fee
... 2 0 LOAD_CONST 1 ('fee string')
3 STORE_FAST 0 (fee)

3 6 LOAD_FAST 0 (fee)
9 RETURN_VALUE

Now we'll look for what replaces
2 0 LOAD_CONST 1 ('fee string')
3 STORE_FAST 0 (fee)
when we do a simple nested function and return that:

... def fee():
... return 'nested fee result'
... return fee
... 2 0 LOAD_CONST 1 (<code object fee at 02EF21E0, file "<stdin>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (fee)

4 9 LOAD_FAST 0 (fee)
12 RETURN_VALUE

Note the MAKE_FUNCTION. That is dynamically going to take its argument(s) -- in this case
what the single LOAD_CONST put on the stack -- and leave the now ready-to-call function
on the stack (as a reference, not the whole thing of course). Now we have the function
reference instead of a reference to the string 'fee string' in the first example on the
stack, and the next step is STORE_FAST to bind the local name fee to whatever was on the stack.
In both cases, what happens next is to return (by reference) whatever fee is bound to.

So fee doesn't exist as such until the three bytecodes (LOAD_CONST, MAKE_FUNCTION, STORE_FAST)
in this example have been executed. To show that MAKE_FUNCTION is in general doing more
than moving a constant, give fee an argument with a default value expression. It could be
arbitrarily complicated, but we'll keep it simple:
... def fee(x, y=2*globalfun()):
... return x, y
... return fee
... 2 0 LOAD_CONST 1 (2)
3 LOAD_GLOBAL 0 (globalfun)
6 CALL_FUNCTION 0
9 BINARY_MULTIPLY
10 LOAD_CONST 2 (<code object fee at 02EF21A0, file "<stdin>", line 2>)
13 MAKE_FUNCTION 1
16 STORE_FAST 0 (fee)

4 19 LOAD_FAST 0 (fee)
22 RETURN_VALUE

Compare to what came before MAKE_FUNCTION in the previous example. The def fee ... compiled into
code to compute the argument default value expression right there. That is not part of the fee
code, that is part of the fee-making code.

If the nested function makes use of its nested environment, there is extra work in setting up
the closure variables, so MAKE_FUNCTION is replaced my MAKE_CLOSURE, but either way the creation
of fee doesn't happen until the code compiled from the def source is executed, and if the def
code is nested, it doesn't get executed until its enclosing function is called.

Notice that def foo (compiled AND executed interactively above) didn't complain about globalfun,
which is just a name I picked. That's because foo hasn't been called yet, and the def-code to
generate fee has not been executed yet. But when we do:
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 2, in foo
NameError: global name 'globalfun' is not defined

That's not from fee, that's from trying to create a fee default argument
as part of creating fee dynamically.

Now supplying globalfun, we can make it succeed:
>>> import time
>>> def globalfun(): return '[%s]'%time.ctime() ...
>>> foo()
>>> foo()(111, 222) (111, 222)
>>> foo()(333) (333, '[Mon Apr 04 22:31:23 2005][Mon Apr 04 22:31:23 2005]')
>>> foo()(333)
(333, '[Mon Apr 04 22:31:37 2005][Mon Apr 04 22:31:37 2005]')

Note that the times changed, because we execute foo() and therefore def fee
multiple times but if we capture the fee output as f1 and f2, the times
are captured in fee's default arg according to when def fee was executed
and then that belongs to fee and won't change when fee is called.
(111, 222)
That generated a new fee with a new default time, but didn't use the default
(333, '[Mon Apr 04 22:32:48 2005][Mon Apr 04 22:32:48 2005]')
That used the default. Now we will make instances of fee and
bind them to f1 and f2, pausing a little between so the times
should be different.

Now calling f1 or f2 is calling different fees with different,
(but constant because we are not calling foo again) defaults:
>>> f1(1) (1, '[Mon Apr 04 22:32:55 2005][Mon Apr 04 22:32:55 2005]')
>>> f1(1)
(1, '[Mon Apr 04 22:32:55 2005][Mon Apr 04 22:32:55 2005]')

And different, but repeated:
>>> f2(1) (1, '[Mon Apr 04 22:33:03 2005][Mon Apr 04 22:33:03 2005]')
>>> f2(1)
(1, '[Mon Apr 04 22:33:03 2005][Mon Apr 04 22:33:03 2005]')

Now get foo to make another fee and call it without even storing it: (1, '[Mon Apr 04 22:33:40 2005][Mon Apr 04 22:33:40 2005]')


[...]
Today I believe I have the correct view as I've said this morning. I
could be wrong yet again. I hope not though I might have to give up
programming. :/
Don't give up. It would be boring if it were all instantly clear.
The view is better after an enjoyable hike, and some of the flowers
along the way may turn out prettier than whatever the vista at the
top may be ;-)

For this part of the trail, just grok that def is executable,
not just the thing def's execution produces ;-)
It's interesting that I have had several others tell me they had
trouble with this too.

So it is my opinion that decorators are a little too implicit. I
think there should be a way to make them easier to use while achieving
the same objective and use.
Maybe the above will help make functions and decorators a little easier
to understand.

HTH

Regards,
Bengt Richter
 
R

Ron_Adam

I am afraid that is wrong. But be happy, this may be the key to what ISTM
is missing in your concept of python functions ;-)

The expression in the form of "function(args)(args)" is the same
pattern in two "different" cases, which was all that I was trying to
say. Not that the exact process of the two different cases were the
same.
So, no, it does not "work the same." In fact, it is a misconception to talk about
a nested fee as if it existed ready to call in the same way as foo. It doesn't
exist that way until the fee def is EXECUTED, producing the callable fee.

Ok, I'm going to have to be more careful in how I phrase things I
think, I tend to over-genralize a bit. I said they were "the same",
but meant similar, a mistake in wording, but not in my understanding.

But this is a good point. In my example the calling expression does
not yet know who the next tuple of arguments will go to until foo
returns it. That part is the same, but as you point out in a nested
scope foo defines fee then returns it. And in the non nested example
fee is already defined before foo is called. And they must use
globals to communicate because they are not share the same name space.
They differ because fee is temporary, in the nested version, only
existing until the expression foo(arg)(arg) is evaluated. It never
gets assigned a name in foo's parent name space. Do I have that
correct?

We can use dis to see the above clearly:

Love the byte code walk through, Thanks. Is there a resource that
goes in depth on python byte code and the compiler? I haven't been
able to find much on it on google.
import time
def globalfun(): return '[%s]'%time.ctime() ...
foo()
foo()(111, 222) (111, 222)
foo()(333) (333, '[Mon Apr 04 22:31:23 2005][Mon Apr 04 22:31:23 2005]')
foo()(333)
(333, '[Mon Apr 04 22:31:37 2005][Mon Apr 04 22:31:37 2005]')

I like your idea of using time stamps to trace code! :)

[...]
Today I believe I have the correct view as I've said this morning. I
could be wrong yet again. I hope not though I might have to give up
programming. :/
Don't give up. It would be boring if it were all instantly clear.
The view is better after an enjoyable hike, and some of the flowers
along the way may turn out prettier than whatever the vista at the
top may be ;-)

I won't give up, at most I would take a break, but I love programming
too much to give it up. ;-)

Maybe the above will help make functions and decorators a little easier
to understand.

I understand functions, sometimes it's difficult to describe just what
it is I don't understand yet, and sometimes I fool myself by jumping
to an invalid conclusion a little too quickly. But I do this for
enjoyment and learning, so I'm not constrained by the need to not make
mistakes, (those are just part of learning in my oppinion), as I would
if my job depended on it. However it's a little frustrating when my
inability to write well, gets in the way of expressing myself
accurately.

But a few questions remain...

When a @decorator statement is found, How does the compiler handle it?

Let me see if I can figure this out...using dis. :)
@deco1
def func2(f2):
return f2
return func2(f1)
1 0 LOAD_FAST 0 (d1)
3 RETURN_VALUE 2 0 LOAD_GLOBAL 0 (deco1)
3 LOAD_CONST 1 (<code object func2 at
00B45CA0, file "<pyshell#11>", line 2>)
6 MAKE_FUNCTION 0
9 CALL_FUNCTION 1
12 STORE_FAST 1 (func2)

5 15 LOAD_FAST 1 (func2)
18 LOAD_FAST 0 (f1)
21 CALL_FUNCTION 1
24 RETURN_VALUE

I'm not sure how to interpret this... Line 5 and below is the return
expression. The part above it is the part I'm not sure about.

Is the first CALL_FUNCTION calling deco1 with the result of the
defined functions reference, as it's argument? Then storing the result
of deco1 with the name func2?

If so the precompiler/parser is replacing the @deco1 with a call to
the deco1 function like this.

deco1( (def func2(f2):return f2) )

But this causes an illegal syntax error on the def statement. So you
can't do it directly. Or is there yet another way to view this? :)

Cheers,
Ron
 
B

Bengt Richter

The expression in the form of "function(args)(args)" is the same
pattern in two "different" cases, which was all that I was trying to
say. Not that the exact process of the two different cases were the
same.


Ok, I'm going to have to be more careful in how I phrase things I
think, I tend to over-genralize a bit. I said they were "the same",
but meant similar, a mistake in wording, but not in my understanding.

But this is a good point. In my example the calling expression does
not yet know who the next tuple of arguments will go to until foo
returns it. That part is the same, but as you point out in a nested
scope foo defines fee then returns it. And in the non nested example
fee is already defined before foo is called. And they must use
globals to communicate because they are not share the same name space.
They differ because fee is temporary, in the nested version, only
existing until the expression foo(arg)(arg) is evaluated. It never
gets assigned a name in foo's parent name space. Do I have that
correct? I think so.



Love the byte code walk through, Thanks. Is there a resource that
goes in depth on python byte code and the compiler? I haven't been
able to find much on it on google.
I don't know of anything other than the compiler and cpython sources.
The byte codes are not all the same from version to version, since
added language features may require new byte code operations, at least
for efficiency, and (as our postings here show ;-) explaining code
clearly is as much of a job as writing it. Fortunately, python code
is pretty readable, and there is a lot of interesting reading in

Python-2.4xxx\Lib\compiler\ and Python-2.4xxx\Lib\compiler\

on your disk if you download the source installation. Lots of
other goodies as well like demo code etc.
import time
def globalfun(): return '[%s]'%time.ctime() ...
foo()
foo()(111, 222) (111, 222)
foo()(333)
(333, '[Mon Apr 04 22:31:23 2005][Mon Apr 04 22:31:23 2005]')
foo()(333)
(333, '[Mon Apr 04 22:31:37 2005][Mon Apr 04 22:31:37 2005]')

I like your idea of using time stamps to trace code! :)

Well, I wanted to emphasize the time dimension in creation and existence
of things, so I thought to tag them.

[...]
I understand functions, sometimes it's difficult to describe just what
it is I don't understand yet, and sometimes I fool myself by jumping
to an invalid conclusion a little too quickly. But I do this for
enjoyment and learning, so I'm not constrained by the need to not make
mistakes, (those are just part of learning in my oppinion), as I would
if my job depended on it. However it's a little frustrating when my
inability to write well, gets in the way of expressing myself
accurately.
I feel very much the same ;-)
But a few questions remain...

When a @decorator statement is found, How does the compiler handle it?

Let me see if I can figure this out...using dis. :)

@deco1
def func2(f2):
return f2
return func2(f1)
That last line is a bit strange, which leads to the strangeness
you ask about below.
1 0 LOAD_FAST 0 (d1)
3 RETURN_VALUE
2 0 LOAD_GLOBAL 0 (deco1)
3 LOAD_CONST 1 (<code object func2 at
00B45CA0, file "<pyshell#11>", line 2>)
6 MAKE_FUNCTION 0
9 CALL_FUNCTION 1
12 STORE_FAST 1 (func2)

5 15 LOAD_FAST 1 (func2)
18 LOAD_FAST 0 (f1)
21 CALL_FUNCTION 1
24 RETURN_VALUE

I'm not sure how to interpret this... Line 5 and below is the return
expression. The part above it is the part I'm not sure about.

Well, it's showing the result of your python code, but the return func2(f1)
is obscuring normal decorator functionality. I suggest going from the simplest
and introducing complications stepwise. You are defining a func1 that could
be used as a decorator function, and it decorates its function argument by
explicitly calling an internally defined decorator function func2, whose internal
definition involved decorating func2 using func1. We can walk through it.
It's legal usage, it's just not the simplest example or clearest design ;-)
Is the first CALL_FUNCTION calling deco1 with the result of the
defined functions reference, as it's argument? Then storing the result
of deco1 with the name func2?

Yes, if I understand you correctly.
I'll use list notation to represent the stack, so [] is empty and
[bottom, middle, top] is a stack with references to three things.
So _after_ each line above, we get

0 -> [deco1] This is a reference to the callable function deco1
3 -> [deco1, <code object func2>] The top is what's necessary to make the func2 function
6 -> [deco1, func2] MAKE_FUNCTION pops its arg and pushes it result (func2)
9 -> [func2*] CALL_FUNCTION 1 (one arg) called deco1(func2) and stacked the decorated func2
12 -> [] func2 reference popped from stack and stored in local func2
15 -> [func2*] local func2 (I use * to indicate it's the decorated func2) pushed on stack
18 -> [func2*, f1] stack the outer f1 argument. This is for your weird func2(f1) return value expression
21 -> [f1*] CALL_FUNCTION calls the decorated func2 with f1 as argument and stacks the modified f1
24 -> [] f1* (modified f1) is popped for return value
If so the precompiler/parser is replacing the @deco1 with a call to
the deco1 function like this.

deco1( (def func2(f2):return f2) )

But this causes an illegal syntax error on the def statement. So you
can't do it directly. Or is there yet another way to view this? :)
With simple examples? ;-)

One of the original examples of decorating was to replace the
staticmethod and classmethod function calls that had to be done
after the method defs. So the simplest view goes back to

@deco
def foo(): pass

being the equivalent (except if deco raises and exception) of

def foo(): pass
foo = deco(foo)

The plain name @deco was then allowed to become a simple xxx.yyy(zzz,...) expression
returning a callable that would serve like the decorator function a bare name normally
referred to. And then the mechanism could be cascaded. I suggest looking at the
code for the simplest cascade of normal decorators. E.g., (putting the example in
the body of a function makes dis.dis easy ;-)
... @deco_outer
... @deco_inner
... def foo(): pass
...
2 0 LOAD_GLOBAL 0 (deco_outer) -> [deco_outer]
3 LOAD_GLOBAL 1 (deco_inner) -> [deco_outer, deco_inner]
6 LOAD_CONST 1 (<code object foo at 02EE4FA0, file "<stdin>", line 2>) -> [deco_outer, deco_inner, <code obj>]
9 MAKE_FUNCTION 0 -> [deco_outer, deco_inner, foo]
12 CALL_FUNCTION 1 -> [deco_outer, foo*] foo* == deco_inner(foo)
15 CALL_FUNCTION 1 -> [foo**] foo** == deco_outer(foo*)
18 STORE_FAST 0 (foo) -> [] foo = deco_outer(deco_inner(foo))
21 LOAD_CONST 0 (None) # ignore ;-)
24 RETURN_VALUE

I didn't return foo from example, so it generated code at 21 to return a None,
but you can ignore that, since the decoration ends with the binding of the function
name (foo here) done at 18 with STORE_FAST.

Regards,
Bengt Richter
 
R

Ron_Adam

I don't know of anything other than the compiler and cpython sources.
The byte codes are not all the same from version to version, since
added language features may require new byte code operations, at least
for efficiency, and (as our postings here show ;-) explaining code
clearly is as much of a job as writing it. Fortunately, python code
is pretty readable, and there is a lot of interesting reading in

Python-2.4xxx\Lib\compiler\ and Python-2.4xxx\Lib\compiler\

on your disk if you download the source installation. Lots of
other goodies as well like demo code etc.

Thanks, I've already downloaded the source as well as CVS, although I
don't have a resent version of VisualC++. I Tried the Express version
8.0 since it's free, but it fails on the library with link errors.
:-/, Not that I expected it to work since nothing I could find said
it would. Probably easier to load up linux. But I don't need a
compiler to read the source.

One of the original examples of decorating was to replace the
staticmethod and classmethod function calls that had to be done
after the method defs. So the simplest view goes back to

@deco
def foo(): pass

being the equivalent (except if deco raises and exception) of

def foo(): pass
foo = deco(foo)

The plain name @deco was then allowed to become a simple xxx.yyy(zzz,...) expression
returning a callable that would serve like the decorator function a bare name normally
referred to. And then the mechanism could be cascaded. I suggest looking at the
code for the simplest cascade of normal decorators. E.g., (putting the example in
the body of a function makes dis.dis easy ;-)

So the @decorator functionality was a very small incremental change to
the pre compiler. That also explains the nesting behavior of stacked
decorators. A small change with a worth while functionality. :)

Looks like the @decorator is pseudo function limited to a single
callable as it's body.

(experimenting with a different syntax)

@deco(a):
def function(x): pass
function = deco(a)(function)(x)


Stacked, it would be:

@deco1(a):
@deco2(b):
def function(x):
return x+1
function = deco2(a)(function)(x)
function = deco1(b)(function)(x)


Each subsequent stacked _@_ statement redefines "function" when it
exits.

If I have this correct, this would be the equivalent long hand of two
stacked _@_ expressions.


Cheers,
Ron
 

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
474,432
Messages
2,571,681
Members
48,796
Latest member
Greg L.

Latest Threads

Top