Extending properties to sequence-like behavior

I know the property() built-in allows you to override the behavior of attribute access in a class to call arbitrary functions where the syntax looks like a basic assign or lookup. This is handy when you're importing an external API and want to make it look more Pythonic. Is there a simple way to extend this so that item lookup or assignment can be similarly overridden? For instance, if the object I'm importing (say, a drop-down box in a GUI toolkit) has methods called GetAllItems, SetAllItems, GetItemAtPos, and SetItemAtPos, I want to be able to translate: return box.Items -> return box.GetAllItems() box.Items = itemlist -> box.SetAllItems(itemlist) return box.Items[idx] -> return box.GetItemAtPos(idx) box.Items[idx] = new_item -> box.SetItemAtPos(idx, new_item) Just using property() doesn't quite work; it handles the "set all" and "get all" operations easily enough, and "get item" works for any interface that it would be sane to attempt this on by just getting the appropriate slice of the list returned by "get all" (although this may be inefficient compared to calling the real "get item" function), but "set item" doesn't work because it calls "get all" and then assigns to a slot in the list returned by that function. The best solution I've come up with so far is to create an object (of a class I'll call ListProp) and have the "get" function of the property return this object. Then I can override the __getitem__ and __setitem__ methods to call the "get item" and "set item" functions. The "get all" function no longer works exactly as above, though; box.Items would instead return the ListProp object. This can be solved by using __call__ on the ListProp as the "get all" method; then the only change is to use box.Items() for the "get all" function. I've attached some scratch code here (Python 2.7) demonstrating the basic idea. Is there a better way to do this? Dan Baker

On Apr 5, 2011, at 10:31 PM, Dan Baker wrote:
I don't see any other way. You're solution is probably the only workable approach. Since the [idx] call occurs after the box.Items attribute lookup, the box.Items lookup needs to return some object that can a subsequent [idx] call using __getitem__ or __setitem__:
Thanks for the interesting post. Nice work in figuring this all out. Raymond

I would translate it to box.items -> box.GetAllItems() box.items = itemlist -> box.SetAllItems(itemlist) box.item[idx] -> box.GetItemAtPos(idx) box.item[idx] = new_item -> box.SetItemAtPos(idx, new_item) http://dpaste.com/529172/ Yes you have 2 properties now but it is fairly easy to remember that items is always getting or setting all of them and item is always getting or setting a single one

On Wed, Apr 6, 2011 at 3:31 PM, Dan Baker <dbaker3448@gmail.com> wrote:
I've attached some scratch code here (Python 2.7) demonstrating the basic idea. Is there a better way to do this?
Perhaps rename the returned class to SequenceProp and have it inherit from collections.MutableSequence? Then you could override methods and use GetAllItems/SetAllItems at appropriate points (e.g. in __iter__, or when the item access methods are passed slices with start, stop and step all set to None). It will be a little more work up front, but it's the only way you're going to get full control over the way client code interacts with your external sequence objects. One of the problems you have at the moment is that other mutating methods of lists (such as .sort() and .reverse()) still aren't going to work as would be expected if "box.Items" truly was a list. Avoiding that claim and instead making it clear you only provide the smaller sequence ABC would help make that clear (and MutableSequence automatically defines many of those operations in terms of the core abstract methods, anyway). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Perhaps rename the returned class to SequenceProp and have it inherit from collections.MutableSequence?
I hadn't really looked around at the collections module before. If I'm understanding it correctly, if I implement a few of the basic methods for MutableSequence (__getitem__, __setitem__, __delitem__, and insert) I get a few of the extra list-like methods (append, extend, count, index, pop, etc.) and the iterator protocol for free. If that's the case, that sounds like a huge win. Probably wouldn't even need to do __call__ for the GetAll method anymore in most cases, I'd be able to just use "for item in box.items" and the __iter__ method handles it. Thanks for the idea. That looks like it would make access via the sequence property much more natural. Dan

On Thu, Apr 7, 2011 at 2:03 PM, Dan Baker <dbaker3448@gmail.com> wrote:
Yeah, the collections ABCs were inspired in part by the old UserDict and UserList classes - make it easier to support the broader APIs by implementing a few essential methods. Being able to do that is one of the big reasons Guido opted for standard library level ABC support over Java-style interface definitions. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Apr 5, 2011, at 10:31 PM, Dan Baker wrote:
I don't see any other way. You're solution is probably the only workable approach. Since the [idx] call occurs after the box.Items attribute lookup, the box.Items lookup needs to return some object that can a subsequent [idx] call using __getitem__ or __setitem__:
Thanks for the interesting post. Nice work in figuring this all out. Raymond

I would translate it to box.items -> box.GetAllItems() box.items = itemlist -> box.SetAllItems(itemlist) box.item[idx] -> box.GetItemAtPos(idx) box.item[idx] = new_item -> box.SetItemAtPos(idx, new_item) http://dpaste.com/529172/ Yes you have 2 properties now but it is fairly easy to remember that items is always getting or setting all of them and item is always getting or setting a single one

On Wed, Apr 6, 2011 at 3:31 PM, Dan Baker <dbaker3448@gmail.com> wrote:
I've attached some scratch code here (Python 2.7) demonstrating the basic idea. Is there a better way to do this?
Perhaps rename the returned class to SequenceProp and have it inherit from collections.MutableSequence? Then you could override methods and use GetAllItems/SetAllItems at appropriate points (e.g. in __iter__, or when the item access methods are passed slices with start, stop and step all set to None). It will be a little more work up front, but it's the only way you're going to get full control over the way client code interacts with your external sequence objects. One of the problems you have at the moment is that other mutating methods of lists (such as .sort() and .reverse()) still aren't going to work as would be expected if "box.Items" truly was a list. Avoiding that claim and instead making it clear you only provide the smaller sequence ABC would help make that clear (and MutableSequence automatically defines many of those operations in terms of the core abstract methods, anyway). Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

Perhaps rename the returned class to SequenceProp and have it inherit from collections.MutableSequence?
I hadn't really looked around at the collections module before. If I'm understanding it correctly, if I implement a few of the basic methods for MutableSequence (__getitem__, __setitem__, __delitem__, and insert) I get a few of the extra list-like methods (append, extend, count, index, pop, etc.) and the iterator protocol for free. If that's the case, that sounds like a huge win. Probably wouldn't even need to do __call__ for the GetAll method anymore in most cases, I'd be able to just use "for item in box.items" and the __iter__ method handles it. Thanks for the idea. That looks like it would make access via the sequence property much more natural. Dan

On Thu, Apr 7, 2011 at 2:03 PM, Dan Baker <dbaker3448@gmail.com> wrote:
Yeah, the collections ABCs were inspired in part by the old UserDict and UserList classes - make it easier to support the broader APIs by implementing a few essential methods. Being able to do that is one of the big reasons Guido opted for standard library level ABC support over Java-style interface definitions. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (5)
-
Dan Baker
-
Dj Gilcrease
-
Greg Ewing
-
Nick Coghlan
-
Raymond Hettinger