Style guide for subclassing built-in types?

J

Jane Austine

Please see the following code:
--------------------------------
class rev_wrap(object):
def __init__(self,l):
self.l=l
def __getitem__(self,i):
return self.l[-i-1]

class rev_subclass(list):
def __getitem__(self,i):
return list.__getitem__(self,-i-1)

if __name__=='__main__':
l=rev_wrap([1,2,3])
assert l[0]==3
assert list(l)==[3,2,1]

l=rev_subclass([1,2,3])
assert l[0]==3
assert list(l)==[3,2,1]

I know there is "reversed" and "reverse" but that's not the point. The
code fails at the last line.

Now that UserList is deprecated(not recommended) I suppose subclassing
built-in types are preferable than wrapping them. How do I properly
change the behaviours of built-in types? I think I have to override
__iter__ and next to pass the last line. Is it a good style? If so,
what is the most recommended way of implementing them?

Thank you in advance.

Jane
 
J

janeaustine50

Jane said:
Please see the following code:
--------------------------------
class rev_wrap(object):
def __init__(self,l):
self.l=l
def __getitem__(self,i):
return self.l[-i-1]

class rev_subclass(list):
def __getitem__(self,i):
return list.__getitem__(self,-i-1)

if __name__=='__main__':
l=rev_wrap([1,2,3])
assert l[0]==3
assert list(l)==[3,2,1]

l=rev_subclass([1,2,3])
assert l[0]==3
assert list(l)==[3,2,1]

Oh... I forgot one. assert l==[3,2,1] at this point doesn't pass
either. "print l" outputs the wrong one([1,2,3]) as well.
 
J

janeaustine50

Fuzzyman said:
I guess print is using the __repr__ (or __str__ ?) methods of lsit -
which you will need to override as well.

Regards,

Fuzzy
http://www.voidspace.org.uk/python/index.shtml

Thank you but the problem is that I have to express my intention in
duplicate places -- __iter__(along with "next"), __str__, __eq__ and so
on.

p.s. the reason I'm not sticking to reversed or even reverse : suppose
the size of the list is huge.
 
N

Nick Coghlan

p.s. the reason I'm not sticking to reversed or even reverse : suppose
the size of the list is huge.

Reversed is an iterator - it does NOT copy the list. In other words, reversed
already does pretty much what you want.

Cheers,
Nick.
 
F

Fuzzyman

Thank you but the problem is that I have to express my intention in
duplicate places -- __iter__(along with "next"), __str__, __eq__ and so
on.

If you are sublassing the built in types I guess you will have to
change all methods that have changed..

an alternative wuld be to not subclass object and override __getattr__
:

class rev_wrap:
def __init__(self,l):
self.l=l
def __getitem__(self,i):
return self.l[-i-1]
def __getattr__(self, attr):
return getattr(self.l, attr)

Regards,

Fuzzy
http://www.voidspace.org.uk/python/index.shtml
 
K

Kent Johnson

p.s. the reason I'm not sticking to reversed or even reverse : suppose
the size of the list is huge.

reversed() returns an iterator so list size shouldn't be an issue.

What problem are you actually trying to solve?

Kent
 
J

janeaustine50

Kent said:
reversed() returns an iterator so list size shouldn't be an issue.

What problem are you actually trying to solve?

Kent

Oh, you are right.

Actually, it's more complicated than simple reversion. The list order
should be somewhat "twisted" and the list is big.

For example,

[1,2,3,4,5,6,7,8,9,10]

--> [10,9,8,7,6,1,2,3,4,5]

so __getitem__(self,i) => __getitem__(self,-i-1) if i<len(size)/2,
otherwise __getitem__(self,i-len(size)/2)

I'd like to have TwistedList class that takes in an original list and
pretends as if it is twisted actually. However, I have to have
duplicate codes here and there to make it act like a "list", say assert
twisted_list == [10,9,...] and for each in twisted_list and etc.
 
M

Michael Spencer

Kent said:
suppose


reversed() returns an iterator so list size shouldn't be an issue.

What problem are you actually trying to solve?

Kent


Oh, you are right.

Actually, it's more complicated than simple reversion. The list order
should be somewhat "twisted" and the list is big.

For example,

[1,2,3,4,5,6,7,8,9,10]

--> [10,9,8,7,6,1,2,3,4,5]

so __getitem__(self,i) => __getitem__(self,-i-1) if i<len(size)/2,
otherwise __getitem__(self,i-len(size)/2)

I'd like to have TwistedList class that takes in an original list and
pretends as if it is twisted actually. However, I have to have
duplicate codes here and there to make it act like a "list", say assert
twisted_list == [10,9,...] and for each in twisted_list and etc.
If you want a twisted 'view' of an existing list, then a wrapper makes most sense.

If, however, you only need the twisted version, why not simply override
list.__init__ (and extend, append etc... as required):
... def __init__(self, iterable):
... list.__init__(self, iterable[::-1])
...
>>> l = rev_list([1,2,3])
>>> l
[3, 2, 1]

Michael
 
J

janeaustine50

Michael said:
Kent said:
(e-mail address removed) wrote:

p.s. the reason I'm not sticking to reversed or even reverse :
suppose

the size of the list is huge.

reversed() returns an iterator so list size shouldn't be an issue.

What problem are you actually trying to solve?

Kent


Oh, you are right.

Actually, it's more complicated than simple reversion. The list order
should be somewhat "twisted" and the list is big.

For example,

[1,2,3,4,5,6,7,8,9,10]

--> [10,9,8,7,6,1,2,3,4,5]

so __getitem__(self,i) => __getitem__(self,-i-1) if i<len(size)/2,
otherwise __getitem__(self,i-len(size)/2)

I'd like to have TwistedList class that takes in an original list and
pretends as if it is twisted actually. However, I have to have
duplicate codes here and there to make it act like a "list", say assert
twisted_list == [10,9,...] and for each in twisted_list and etc.
If you want a twisted 'view' of an existing list, then a wrapper makes most sense.

If, however, you only need the twisted version, why not simply override
list.__init__ (and extend, append etc... as required):
... def __init__(self, iterable):
... list.__init__(self, iterable[::-1])
...
l = rev_list([1,2,3])
l
[3, 2, 1]

Michael

Thank you but your advice doesn't fit in my case since I want to keep
the memory usage and the initial time minimum. iterable[::-1] would
build another list and it would take big memory and time during
reversing if iterable were huge. (and the "iterable" wouldn't be
garbage-collected because I want to keep a reference to it)
 
J

Just

Thank you but your advice doesn't fit in my case since I want to keep
the memory usage and the initial time minimum. iterable[::-1] would
build another list and it would take big memory and time during
reversing if iterable were huge. (and the "iterable" wouldn't be
garbage-collected because I want to keep a reference to it)

If your list contains numbers (or lists of numbers), consider using
NumPy (Numeric) or Numarray, in which seq[::-1] will actually return a
"view", and not a copy.

Just
 
S

Serge Orlov

Thank you but your advice doesn't fit in my case since I want to keep
the memory usage and the initial time minimum. iterable[::-1] would
build another list and it would take big memory and time during
reversing if iterable were huge. (and the "iterable" wouldn't be
garbage-collected because I want to keep a reference to it)

You need to implement __iter__ method to pass your assert statement:
def __iter__(self):
return reversed(self)

With regards to style guide:

1. write empty subclass
class rev_subclass(list):
pass

2. print dir(rev_subclass) and write unit tests for every method. There
are 41 of them :) There is also __doc__ attribute you need to override.

3. Implement all the methods of rev_subclass to pass the tests.

Serge.
 
N

Nick Coghlan

Thank you but your advice doesn't fit in my case since I want to keep
the memory usage and the initial time minimum. iterable[::-1] would
build another list and it would take big memory and time during
reversing if iterable were huge. (and the "iterable" wouldn't be
garbage-collected because I want to keep a reference to it)

1. Do you have benchmark results or a mathematical analysis to show that
duplicating the list uses too much memory, or is too slow to startup?

2. Do you have quantitative definitions for "too much" and "too slow", and
rationale to back up those numbers?

3. Do you have a benchmark to determine if attempting to reduce memory
consumption and start-up time has a detrimental effect on run-time performance?

If the answer to any of the above questions is 'no', then just do something like:

from itertools import islice, chain
def twisted_iter(orig):
halfway, skip_middle = divmod(len(orig), 2)
fwditr = islice(iter(orig), halfway + skip_middle, None)
revitr = islice(reversed(orig), halfway, None)
return chain(revitr, fwditr)

Py> from itertools import islice, chain
Py> def twisted_iter(orig):
.... halfway, skip_middle = divmod(len(orig), 2)
.... fwditr = islice(iter(orig), halfway + skip_middle, None)
.... revitr = islice(reversed(orig), halfway, None)
.... return chain(revitr, fwditr)
....
Py> list(twisted_iter(range(10)))
[4, 3, 2, 1, 0, 5, 6, 7, 8, 9]
Py> list(twisted_iter(range(11)))
[5, 4, 3, 2, 1, 0, 6, 7, 8, 9, 10]

Since twisted_iter is actually a highly-optimised twisted view of the original
list, you may want to just leave the original list alone, and create
twisted_iter's when you want them.

However, if you mainly use the twisted view, and only occasionally want the
original view, then you can twist it once, store the result, discard the
original, and twist it again to get the original back:

Py> list(twistediter(list(twistediter(range(10)))))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Py> list(twistediter(list(twistediter(range(11)))))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Those benchmarks I mentioned earlier will let you know which approach is best.

No-optimisations-without-measurements-'ly,
Nick.
 

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

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top