Re: Bypassing __getattribute__ for attribute access



Adam Donahue a écrit :
Bruno,

I appreciate your attempt to answer my questions below, although I
think my main point was lost amongst all your commentary and
assumptions. :^)

Possibly. I sometimes tend to get a bit verbose !-)

I'm not inexperienced,

Obviously not.

but I take the blame for
the rambling initial post, though, which probably lead to the
confusion.

So let me be more direct:
>
From reading it seems that, indeed, __getattribute__ handles calling
attribute.__get__( obj, type(obj ), which does the binding of the
attribute to the object.

Yes.

That is:

<thingy>.<attribute>

in turn is a call to

<thingy>.__getattribute__( '<attribute>' ) <1>

Yes.

The default body of __getattribute__ in turn fetches the associated
value (by traversing the class hierarchy if necessary) and examines
its type in order to determine what to return.

Yes.

Let's assume the ultimate value associated with the attribute (key) is
v.

If type(v) is a function,

If v supports the descriptor protocol (which is the case for function objects and property objects) AND v is a class attribute

__getattribute__ returns
v.__get__( <thingy>, type( <thingy> )

Yes.

If type(v) is, say, an integer,

If v doesn't support the descriptor protocol

__getattribute__ returns v unmolested.

Yes.

And so forth.

So:

class X( object ):

... a = 1

You understand that 'a' is a class attribute, don't you ?

... class b( object ): pass
... def c( self ): pass
...

X.a

1

X.b

<class '__main__.b'>

X.c

<unbound method X.c>

x = X()
x.a

1

Returns type(x).a

x.b

<class '__main__.b'>

idem

x.c

<bound method X.c of <__main__.X object at 0x81b2b4c>>

Yes.

If my interpretation is correct, the X.c's __getattribute__ call knows
the attribute reference is via a class,

Yes

and thus returns an unbound
method

It returns c.__get__(None, X). Which is implemented to return an unbound method if the first arg is None.

(though it does convert the value to a method).

__getattribute__ doesn't convert anything here - and FWIW, doesn't care if c is a function or property or whatnot. The only thing it looks for is if c has a __get__ method.

Likewise,
x.c's __getattribute__ returns the value as a method bound to the x
instance.

Yes, because then __getattribute__ returns x.c.__get__(x, X), which, since c is a function, returns a bound method.

How does __getattribute__ knows the calling context. Its first
argument is the attribute name from what I can tell, not the object
calling it.

Really ?-)

__getattribute__ is itself a function and an attribute of a class (and FWIW, it's signature is __getattribute__(self, name)). So when itself looked up, it returns a method object, which when called passes the 'target' object as first arg.

You already know that Python's classes are objects (instances of type 'type'). So you should by now understand how __getattribute__ knows it's target. Got it ? Yes, right: when looking up attributes on a class, it's the class's class (IOW: the type) __getattribute__ method that is invoked !-)

Is this correct so far?

cf above.

Moving on to __get__. Assume:

class X( object ):
def foo(self):
print `self`
x = X()

Then:

x.foo()

Is similar (perhaps the same as) to:

X.foo.__get__( x, X )()

Almost but not quite. It's:

x.foo() <=> X.foo(x) <=> X.foo.im_func.__get__(x, X)()

It's a bit less confusing when the function is defined outside the class:

def bar(self):
print self

X.bar = bar

x = X()

Now you have:

x.bar() <=> X.bar(x) <=> bar.__get__(x, X)()


(__getattribute__ performs the transformation automatically when one
references via the . operator.)

And so one can do:

class X( object ):

... x = 1

Here, x is a class attribute.


def set_x( self, x ): self.x = x

Now this creates an instance attribute that will shadow the class attribute.

...

x = X()
set_x.__get__( x, X )( 5 )
x.x

5

So far, so good.

The logical next question then is how does one best add a new method
to this class so that future references to x.set_x() and X.set_x will
properly resolve? It seems the answer would be to somehow add to
X.__dict__ a new value, with key 'set_x', value the function set_x.

Yes. Which is very simply done by just binding set_x to X.set_x. Just like I did above. Dynamically adding methods to classes is pretty straightforward, the tricky point is to dynamically add methods to instances, since the descriptor protocol is only triggered for class attributes. But you obviously found how to do it using func.__get__(obj, type(obj)) !-)

From there on the . operator I assume would perform the binding to X
or x as needed on-the-fly.

Yes.

NB: please some guru around correct me if I said something wrong (Alex ? Tim ? Fredrick ? If you hear me ?)
.



Relevant Pages

  • Re: A re-announce on GCs defects
    ... There is delay between the wanted destruction and the actual destruction. ... It's bad for CPU/Resource intensive but memory cheap objects. ... Weak references refer to references who do ... When all strong references to a target go out of their lifetime, ...
    (microsoft.public.dotnet.languages.csharp)
  • Fwd: Please Forward: Ruby Quiz Submission
    ... string then back into a count array. ... beyond the range of the target and source, I would munge one or more ... the target contains a direct representation of the pangram search ... def to_counts ...
    (comp.lang.ruby)
  • A re-announce on GCs defects
    ... The need for weak reference makes the destruction delay logically incorrect. ... Weak references refer to references who do ... When all strong references to a target go out of their lifetime, ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Mindboggling Scope Issue
    ... def inner(): ... variable references in the function definition are only ... > def cancel(): ... whether you've seen much other Tkinter code, ...
    (comp.lang.python)
  • Re: sms overriding my specification of target volume
    ... I only use volume references for target datasets. ... and SMP/E land in SMS managed pools. ... sms overriding my specification of target volume ...
    (bit.listserv.ibm-main)

Loading