Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #11993 > unrolled thread
| Started by | John O'Hagan <research@johnohagan.com> |
|---|---|
| First post | 2011-08-22 15:04 +1000 |
| Last post | 2011-08-23 13:26 +1000 |
| Articles | 7 — 2 participants |
Back to article view | Back to comp.lang.python
Adding modified methods from another class without subclassing John O'Hagan <research@johnohagan.com> - 2011-08-22 15:04 +1000
Re: Adding modified methods from another class without subclassing Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-08-22 15:27 +1000
Re: Adding modified methods from another class without subclassing John O'Hagan <research@johnohagan.com> - 2011-08-22 23:08 +1000
Re: Adding modified methods from another class without subclassing Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-08-23 17:25 +1000
Re: Adding modified methods from another class without subclassing John O'Hagan <research@johnohagan.com> - 2011-08-24 21:09 +1000
Re: Adding modified methods from another class without subclassing John O'Hagan <research@johnohagan.com> - 2011-08-23 10:55 +1000
Re: Adding modified methods from another class without subclassing Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-08-23 13:26 +1000
| From | John O'Hagan <research@johnohagan.com> |
|---|---|
| Date | 2011-08-22 15:04 +1000 |
| Subject | Adding modified methods from another class without subclassing |
| Message-ID | <mailman.301.1313989495.27778.python-list@python.org> |
I have a class like this:
class MySeq():
def __init__(self, *seq, c=12):
self.__c = c
self.__pc = sorted(set([i % __c for i in seq]))
self.order = ([[self.__pc.index(i % __c), i // __c] for i in seq])
#other calculated attributes
@property
def pitches(self):
return [self.__pc[i[0]] + i[1] * self.__c for i in self.order]
#other methods
The "pitches" attribute initially reconstructs the "seq" arguments but can be modified by writing to the "order" attribute.
The "pitches" attribute represents the instances and as such I found myself adding a lot of methods like:
def __getitem__(self, index):
return self.pitches[index]
def __len__(self):
return len(self.pitches)
def __iter__(self):
return iter(self.pitches)
def __repr__(self):
return str(self.pitches)
and so on, and calling a lot of list methods on the "pitches" attribute of MySeq instances elsewhere. I thought of making MySeq a subclass of list with "pitches" as its contents, but then I would have had to override a lot of methods case-by-case, for example to ensure that any alterations to "pitches" were reflected in the other calculated attributes.
So I wrote this function which takes a method, modifies it to apply to an instance attribute, and takes care of any quirks:
def listmeth_to_attribute(meth, attr):
def new_meth(inst, *args):
#ensure comparison operators work:
args = [getattr(i, attr) if isinstance(i, inst.__class__)
else i for i in args]
reference = getattr(inst, attr)
test = reference[:]
result = meth(test, *args)
#ensure instance is reinitialised
#if attribute has been changed:
if test != reference:
inst.__init__(*test)
#ensure slices are of same class
if isinstance(result, meth.__objclass__):
result = inst.__class__(*result)
return result
return new_meth
and this decorator to apply this function to all the list methods and add them to MySeq:
def add_mod_methods(source_cls, modfunc, modfunc_args, *overrides):
"""Overides = any methods in target to override from source"""
def decorator(target_cls):
for name, meth in vars(source_cls).items():
if name not in dir(target_cls) or name in overrides:
setattr(target_cls, name, modfunc(meth, *modfunc_args))
return target_cls
return decorator
a kind of DIY single inheritance, used like this:
@add_mod_methods(list, listmeth_to_attribute, ('pitches',), '__repr__')
class MySeq():
.....
Now I can call list methods transparently on MySeq instances, like subclassing but without all the overriding. If this works it will simplify a lot of code in my project.
But the fact that I haven't seen this approach before increases the likelihood it may not be a good idea. I can almost hear the screams of "No, don't do that!" or the sound of me slapping my forehead when someone says "Why don't you just...". So before I put it in, I'd appreciate any comments, warnings, criticisms, alternatives etc..
Regards,
John
[toc] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2011-08-22 15:27 +1000 |
| Message-ID | <4e51e8c9$0$29981$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #11993 |
On Mon, 22 Aug 2011 03:04 pm John O'Hagan wrote:
> The "pitches" attribute represents the instances and as such I found
> myself adding a lot of methods like:
>
> def __getitem__(self, index):
> return self.pitches[index]
>
> def __len__(self):
> return len(self.pitches)
Looks like a call for (semi-)automatic delegation!
Try something like this:
# Untested
class MySeq(object):
methods_to_delegate = ('__getitem__', '__len__', ...)
pitches = ... # make sure pitches is defined
def __getattr__(self, name):
if name in self.__class__.methods_to_delegate:
return getattr(self.pitches, name)
return super(MySeq, object).__getattr__(self, name)
# will likely raise AttributeError
> But the fact that I haven't seen this approach before increases the
> likelihood it may not be a good idea. I can almost hear the screams of
> "No, don't do that!"
The general technique is called delegation and is perfectly legitimate.
http://c2.com/cgi/wiki?WhatIsDelegation
http://en.wikipedia.org/wiki/Delegation_(programming)
--
Steven
[toc] | [prev] | [next] | [standalone]
| From | John O'Hagan <research@johnohagan.com> |
|---|---|
| Date | 2011-08-22 23:08 +1000 |
| Message-ID | <mailman.314.1314018545.27778.python-list@python.org> |
| In reply to | #11994 |
On Mon, 22 Aug 2011 15:27:36 +1000
Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:
> On Mon, 22 Aug 2011 03:04 pm John O'Hagan wrote:
>
> > The "pitches" attribute represents the instances and as such I found
> > myself adding a lot of methods like:
> >
> > def __getitem__(self, index):
> > return self.pitches[index]
> >
> > def __len__(self):
> > return len(self.pitches)
>
>
> Looks like a call for (semi-)automatic delegation!
>
> Try something like this:
>
>
> # Untested
> class MySeq(object):
> methods_to_delegate = ('__getitem__', '__len__', ...)
> pitches = ... # make sure pitches is defined
> def __getattr__(self, name):
> if name in self.__class__.methods_to_delegate:
> return getattr(self.pitches, name)
> return super(MySeq, object).__getattr__(self, name)
> # will likely raise AttributeError
Thanks, this looks promising. I didn't know about __getattr__ or delegation. This example doesn't seem to work as is for special methods beginning with "__" (e.g.: "TypeError: object of type 'MyList' has no len()"). It seems that __getattr__ is not called for special methods. Also, it doesn't immediately suggest to me a way of modifying method calls (maybe __setattr__?). But it's certainly a neater way to get methods to operate on the attribute. I'm looking into it, and delegation generally.
However, I don't understand what the super call is doing. If the method isn't delegated, shouldn't it just fall back to getattr(self, name)?
Thanks and regards,
John
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2011-08-23 17:25 +1000 |
| Message-ID | <4e5355e2$0$29965$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #12025 |
On Mon, 22 Aug 2011 11:08 pm John O'Hagan wrote:
> On Mon, 22 Aug 2011 15:27:36 +1000
> Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:
[...]
>> # Untested
>> class MySeq(object):
>> methods_to_delegate = ('__getitem__', '__len__', ...)
>> pitches = ... # make sure pitches is defined
>> def __getattr__(self, name):
>> if name in self.__class__.methods_to_delegate:
>> return getattr(self.pitches, name)
>> return super(MySeq, object).__getattr__(self, name)
>> # will likely raise AttributeError
>
> Thanks, this looks promising. I didn't know about __getattr__ or
> delegation. This example doesn't seem to work as is for special methods
> beginning with "__" (e.g.: "TypeError: object of type 'MyList' has no
> len()"). It seems that __getattr__ is not called for special methods.
Ah yes, that would be a problem.
This recipe may help.
http://code.activestate.com/recipes/252151-generalized-delegates-and-proxies/
> Also, it doesn't immediately suggest to me a way of modifying method calls
> (maybe __setattr__?).
What do you mean, "modifying method calls"?
__getattr__ doesn't know whether the method retrieved modifies the instance
or not. That's irrelevant.
__setattr__ is called when you say
instance.attribute = value
> But it's certainly a neater way to get methods to
> operate on the attribute. I'm looking into it, and delegation generally.
>
> However, I don't understand what the super call is doing. If the method
> isn't delegated, shouldn't it just fall back to getattr(self, name)?
getattr(self, name) will just call self.__getattr__(name) again, which will
call getattr, and so on... leading to RecursionError.
--
Steven
[toc] | [prev] | [next] | [standalone]
| From | John O'Hagan <research@johnohagan.com> |
|---|---|
| Date | 2011-08-24 21:09 +1000 |
| Message-ID | <mailman.385.1314184178.27778.python-list@python.org> |
| In reply to | #12065 |
On Tue, 23 Aug 2011 17:25:22 +1000
Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:
> On Mon, 22 Aug 2011 11:08 pm John O'Hagan wrote:
>
> > On Mon, 22 Aug 2011 15:27:36 +1000
> > Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:
> [...]
> >> # Untested
> >> class MySeq(object):
> >> methods_to_delegate = ('__getitem__', '__len__', ...)
> >> pitches = ... # make sure pitches is defined
> >> def __getattr__(self, name):
> >> if name in self.__class__.methods_to_delegate:
> >> return getattr(self.pitches, name)
> >> return super(MySeq, object).__getattr__(self, name)
> >> # will likely raise AttributeError
> >
[...]
>
> > Also, it doesn't immediately suggest to me a way of modifying method calls
> > (maybe __setattr__?).
>
> What do you mean, "modifying method calls"?
That was a rather badly-worded reference to a function in my original post which modified the action of list methods in ways specific to the new class. As I said, I re-read the docs on __getattr__, and now __setattr__, and I get what they do now.
Thanks for your pointers, I'll get back to work.
Regards,
John
[toc] | [prev] | [next] | [standalone]
| From | John O'Hagan <research@johnohagan.com> |
|---|---|
| Date | 2011-08-23 10:55 +1000 |
| Message-ID | <mailman.335.1314060935.27778.python-list@python.org> |
| In reply to | #11994 |
On Mon, 22 Aug 2011 23:08:50 +1000
John O'Hagan <research@johnohagan.com> wrote:
> On Mon, 22 Aug 2011 15:27:36 +1000
> Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:
[..]
> > Looks like a call for (semi-)automatic delegation!
> >
> > Try something like this:
> >
> >
> > # Untested
> > class MySeq(object):
> > methods_to_delegate = ('__getitem__', '__len__', ...)
> > pitches = ... # make sure pitches is defined
> > def __getattr__(self, name):
> > if name in self.__class__.methods_to_delegate:
> > return getattr(self.pitches, name)
> > return super(MySeq, object).__getattr__(self, name)
> > # will likely raise AttributeError
>
[..]
>
> However, I don't understand what the super call is doing. If the method isn't delegated, shouldn't it just fall back to getattr(self, name)?
On reading the __getattr__ docs properly, I see that AttributeError is what should generally happen.
> Thanks and regards,
>
> John
>
[toc] | [prev] | [next] | [standalone]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2011-08-23 13:26 +1000 |
| Message-ID | <4e531de6$0$29977$c3e8da3$5496439d@news.astraweb.com> |
| In reply to | #12058 |
On Tue, 23 Aug 2011 10:55 am John O'Hagan wrote:
>> > # Untested
>> > class MySeq(object):
>> > methods_to_delegate = ('__getitem__', '__len__', ...)
>> > pitches = ... # make sure pitches is defined
>> > def __getattr__(self, name):
>> > if name in self.__class__.methods_to_delegate:
>> > return getattr(self.pitches, name)
>> > return super(MySeq, object).__getattr__(self, name)
>> > # will likely raise AttributeError
>>
> [..]
>>
>> However, I don't understand what the super call is doing. If the method
>> isn't delegated, shouldn't it just fall back to getattr(self, name)?
>
> On reading the __getattr__ docs properly, I see that AttributeError is
> what should generally happen.
Which is what the call to super will accomplish, but if the behaviour ever
changes (including the error message given), you won't have to change your
class.
--
Steven
[toc] | [prev] | [standalone]
Back to top | Article view | comp.lang.python
csiph-web