Alex Martelli wrote, in part:
FWIT and ignoring the small typo on the inner def statement (the
missing ':'), the example didn't work as I (and possibily others) might
expect. Namely it doesn't make function f() a bound method of
instances of class A, so calls to it don't receive an automatic 'self''
argument when called on instances of class A.
This is fairly easy to remedy use the standard new module thusly:
import new
class A(object):
def __init__(self, n):
self.data = n
def f(self, x = self.data):
print x
self.f = new.instancemethod(f, self, A)
This change underscores the fact that each instance of class A gets a
different independent f() method. Despite this nit, I believe I
understand the points Alex makes about the subject (and would agree).
Or as Alex mentioned, a custom descriptor etc is possible, and can also
protect against replacing f by simple instance attribute assignment
like inst.f = something, or do some filtering to exclude non-function
assignments etc., e.g., (not sure what self.data is really
needed for, but we'll keep it):
BTW, note that self.data initially duplicates the default value,
but self.data per se is not used by the function (until the instance
method is replace by one that does, see further on)
... def __init__(self, inst_fname):
... self.inst_fname= inst_fname
... def __get__(self, inst, cls=None):
... if inst is None: return self
... return inst.__dict__[self.inst_fname].__get__(inst, cls) # return bound instance method
... def __set__(self, inst, val):
... if not callable(val) or not hasattr(val, '__get__'): # screen out some impossible methods
... raise AttributeError, '%s may not be replaced by %r' % (self.inst_fname, val)
... inst.__dict__[self.inst_fname] = val
...
The above class defines a custom descriptor that can be instatiated as a class
variable of a given name. When that name is thereafter accessed as an attribute
of an instance of the latter class (e.g. A below), the decriptor __get__ or __set__
methods will be called (the __set__ makes it a "data" descriptor, which intercepts
instance attribute assignment.
... def __init__(self, n):
... self.data = n
... def f(self, x = self.data):
... print x
... self.__dict__['f'] = f # set instance attr w/o triggering descriptor
... f = BindInstMethod('f')
...
<bound method A.f of <__main__.A object at 0x02EF3B0C>>
Note that a.f is dynamically bound at the time of a.f access, not
retrieved as a prebound instance method.
Since the default is an independent duplicate of a.data
a call with no arg produces the original default: this arg overrides the default
Try to change a.f Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 10, in __set__
AttributeError: f may not be replaced by 'sabotage f'
Now use a function, which should be accepted (note: a function, not an instance method) 'not original data 5not original data 5'
That was self.data*2 per the lambda we just assigned to a.f
BTW, the assignment is not directly to the instance attribute.
It goes via the descriptor __set__ method.
>>> a.data = 12
>>> a.f() 24
>>> b = A('bee')
>>> b.f
>>> b.f() bee
>>> b.f('not bee') not bee
>>> b.data 'bee'
>>> b.data = 'no longer bee'
>>> b.f() bee
>>> b.data 'no longer bee'
>>> b.f = lambda self: ' -- '.join([self.data]*3)
>>> b.data 'no longer bee'
>>> b.data = 'ha'
>>> b.f() 'ha -- ha -- ha'
>>> b.f = lambda self, n='default of n':n
>>> b.data 'ha'
>>> b.f(123) 123
>>> b.f() 'default of n'
>>> a.f()
24
Now let's add another name that can be used on instances like f Traceback (most recent call last):
File "<stdin>", line 1, in ?
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in __get__
KeyError: 'g'
No instance method b.g defined yet (A.__init__ only defines f)
Make one with a default
If we bypass method assignment via the descriptor, we can sapotage it:
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 10, in __set__
AttributeError: g may not be replaced by 'sabotage'
That was rejected
but,
>>> a.__dict__['g'] = 'sabotage' # this will bypass the descriptor
>>> a.g
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in __get__
AttributeError: 'str' object has no attribute '__get__'
The descriptor couldn't form a bound method since 'sabotage' was not
a function or otherwise suitable.
But we can look at the instance attribute directly:
'sabotage'
We could define __delete__ in the descriptor too, but didn't so
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: __delete__
We could have made the descriptor return a.g if unable to form a bound method,
but then you'd probably want to permit arbitrary assignment to the descriptor-controlled
attributes too ;-)
Regards,
Bengt Richter