4
\$\begingroup\$

I needed a deque who's maxlen can be set again after it has been initialized. So, since new-style classes in Python are treated as types, I decided to create a new class borrowing from deque to accomplish this. My approach is to have an internal deque as an attribute and when maxlen is set, it replaces the internal deque with a new one, initialized to the old contents and the new maxlen.

I tried subclassing deque but it's too crude, creating a deque that's useless on top with the useful one internally. So I chose to simply have all of the wrapper class's attributes (except special ones) point to the internal deque's. However, due to how new-style classes work, the special attributes have to be handled manually. Since all I want is to override maxlen's setter, all of this seems inelegant and I'm wondering if there's a cleaner way of accomplishing this.

From what I've read here, it seems I could subclass this class in __new__ to skip overriding the special attributes, but that seems even more hairy than what I already wrote.

This is the result, stripped of extraneous comments (complete code here if you want something runnable, with tests):

# -*- coding: utf-8 -*-

from __future__ import print_function

from collections import deque


class ResizableDeque(object):
    def __init__(self, *args, **kwargs):
        self.internal = deque(*args, **kwargs)

        skip_list = [
            'maxlen'
        ] + [attr for attr in dir(deque) if attr.startswith('__') and
             attr.endswith('__')]

        for attr in dir(deque):
            if attr not in skip_list:
                setattr(self, attr, getattr(self.internal, attr))

    @property
    def maxlen(self):
        return self.internal.maxlen

    @maxlen.setter
    def maxlen(self, value):
        templist = list(self.internal)
        self.internal = deque(templist, value)

    def __str__(self):
        return self.internal.__str__()

    def __repr__(self):
        return self.internal.__repr__()

    def __getitem__(self, value):
        return self.internal.__getitem__(value)

    def __setitem__(self, index, value):
        return self.internal.__setitem__(index, value)

    # these have not been tested
    def __copy__(self):
        return self.internal.__copy__()

    def __delitem__(self, index):
        return self.internal.__delitem__(index)

    def __iadd__(self, other):
        return self.internal.__iadd__(other)

    def __len__(self):
        return self.internal.__len__()

    # not sure if overriding __sizeof__ is wise this way
    def __sizeof__(self):
        return self.__sizeof__() + self.internal.__sizeof__()

    # pretty sure this is ok
    def __format__(self, spec):
        return self.internal.__format__(spec)
\$\endgroup\$
4
  • \$\begingroup\$ I think this question better fits stackoverflow \$\endgroup\$ Commented May 6, 2016 at 12:22
  • 1
    \$\begingroup\$ @warvariuc the code works, I'm just looking for comments on whether this is the proper way of doing what I did. \$\endgroup\$ Commented May 6, 2016 at 12:25
  • 1
    \$\begingroup\$ Whatever. Try using __getattr__ or __getattribute__ magic methods to catch access to attributes and forward it to the wrapped object. \$\endgroup\$ Commented May 6, 2016 at 13:01
  • 1
    \$\begingroup\$ As a comment, as no time for a full review: I see that in __init__ stuff gets copied from .internal, but not updated when internal changes on setting maxlen. \$\endgroup\$ Commented May 6, 2016 at 16:22

1 Answer 1

1
\$\begingroup\$

Correctness?

I copied your code, and added the following testcase.

if __name__ == "__main__":
    k = ResizableDeque(maxlen=10)
    k.append(12)
    print(k)
    k.maxlen = 10
    k.append(13)
    k += [14]
    print(k)

Expected result:

deque([12], maxlen=10)
deque([12, 13, 14], maxlen=10)

But I got

deque([12], maxlen=10)
deque([12, 14], maxlen=10)

The reason is that you copy over a bunch of items in __init__, but not in the maxlen.setter.

Whitelisting vs blacklisting

In __init__ you copy several items over using a skip_list. Why not explicitly define the items you want to copy over instead?

Actually, why copy at all? Performance would be a proper reason, but then I'd suggest copying just the items that have proven to be necessary to copy (after profiling).

\$\endgroup\$
3
  • \$\begingroup\$ Excellent catch, I can't believe I missed something like this. I use a skip list instead of an inclusion list because if the implementation of deque changes (unlikely) I want the default behavior to be to alias every non-special attribute. This is because I want ResizableDeque to be like deque in every respect except how maxlen works. Having an inclusion list makes it more likely that I'll need to make changes. \$\endgroup\$ Commented May 8, 2016 at 1:54
  • \$\begingroup\$ Also, I don't intend to copy anything - from what I tested in the interpreter, that setattr() line makes all of ResizableDeque's attributes point to internal's. On the gist, I've got some lines commented out that test exactly this and the attributes test as being the same. If profiling shows this to be too intensive I might change it, but this is more about making usage of the class easy - performance of this operation shouldn't be critical for the use I intend it for. \$\endgroup\$ Commented May 8, 2016 at 1:58
  • \$\begingroup\$ I am looking into @warvariac's suggestion of using __getattr__, though that looks to me like it improves performance during instance creation but sacrifices it on lookups and conditionals during use. \$\endgroup\$ Commented May 8, 2016 at 2:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.