1

I wanted to be able to divide entire lists by integers, floats, and other lists of equal length in Python, so I wrote the following little script.

class divlist(list):
    def __init__(self, *args, **kwrgs):
        super(divlist, self).__init__(*args, **kwrgs)
        self.__cont_ = args[0]
        self.__len_ = len(args[0])

    def __floordiv__(self, other):
        """ Adds the ability to floor divide list's indices """
        if (isinstance(other, int) or isinstance(other, float)):
            return [self.__cont_[i] // other \
                for i in xrange(self.__len_)]
        elif (isinstance(other, list)):
            return [self.__cont_[i] // other[i] \
                for i in xrange(self.__len_)]
        else:
            raise ValueError('Must divide by list, int or float')

My question: How can I write this in a simpler way? Do I really need the lines self.__cont_ and self.__len_? I was looking through the list's 'magic' methods and I couldn't find one that readily held this information.

An example of calling this simple class:

>>> X = divlist([1,2,3,4])
[1, 2, 3, 4]
>>> X // 2
[0, 1, 1, 2]
>>> X // [1,2,3,4]
[1, 1, 1, 1]
>>> X // X
[1, 1, 1, 1]
bjd2385
  • 2,013
  • 4
  • 26
  • 47
  • I think that this method is very clear and concise. I doubt there's much of a way to improve it. – Davis Jul 22 '16 at 18:57
  • Style suggestion: you don't need the \ inside your comprehensions – juanpa.arrivillaga Jul 22 '16 at 20:13
  • @juanpa.arrivillaga Does it make it any less readable? I usually just add them as an extra reminder that I'm going to the next line – bjd2385 Jul 22 '16 at 20:16
  • 1
    According to [PEP 8](https://www.python.org/dev/peps/pep-0008/#maximum-line-length), implicit line continuation inside parentheses, brackets, and braces is preferred over backslash. Personally, I find backslashes hinder readability. As long as you are consistent, though, it should be ok. – juanpa.arrivillaga Jul 22 '16 at 20:26
  • @juanpa.arrivillaga Okay, this makes sense. And thanks for bringing it to my attention that it's mentioned in PEP 8 – bjd2385 Jul 22 '16 at 20:28
  • You should also read [the indentation](https://www.python.org/dev/peps/pep-0008/#indentation) section of PEP 8. I've seen multiline comprehensions written like the multi-line lists in those examples. – juanpa.arrivillaga Jul 22 '16 at 20:29
  • Fwiw: numpy does this for you. –  Jul 23 '16 at 03:04

2 Answers2

5

How can I write this in a simpler way?

By using self[i] instead of self.__cont_[i].

Do I really need the lines self.__cont_ and self.__len_?

No. Just use the regular methods of referring to a list, for example: [] and len().

As an aside, you might choose to have .__floordiv__() return a divlist instead of a list, so that you can continue to operate on the result.

class divlist(list):
    def __floordiv__(self, other):
        """ Adds the ability to floor divide list's indices """
        if (isinstance(other, int) or isinstance(other, float)):
            return [i // other for i in self]
        elif (isinstance(other, list)):
            # DANGER: data loss if len(other) != len(self) !!
            return [i // j for i,j in zip(self, other)]
        else:
            raise ValueError('Must divide by list, int or float')

X = divlist([1,2,3,4])
assert X == [1, 2, 3, 4]
assert X // 2 == [0, 1, 1, 2]
assert X // [1,2,3,4] == [1, 1, 1, 1]
assert X // X == [1, 1, 1, 1]
Iron Fist
  • 10,739
  • 2
  • 18
  • 34
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • This is pretty much what I was looking for. Thanks! – bjd2385 Jul 22 '16 at 18:59
  • You are welcome. Note that since my initial post, I have deleted `__init__` and added a note about using `zip()`. – Robᵩ Jul 22 '16 at 19:02
  • Regarding your aside, would it mean that I'd replace the line `return [i // other for i in self]` with `return divlist([i // other for i in self])`? Also, regarding your removal of `__init__`, is it just not necessary to have that? – bjd2385 Jul 22 '16 at 19:06
  • 1
    1) Very close. I'd lose the square brackets, like so: `return divlist(i // other for i in self)`. 2) `__init__` was unnecessary after I deleted the `__cont_` and `__len_` lines. If the only action of `__init__` is to call `super().__init__`, then you can just delete it. – Robᵩ Jul 22 '16 at 19:32
  • 3
    A small further enhancement: you can pass a tuple of types to `isinstance` rather than making two calls with `or`: `isinstance(other, (int, float))`. Good answer though! – Blckknght Jul 22 '16 at 20:16
3

Instead of examining the explicit types of each argument, assume that either the second argument is iterable, or it is a suitable value as the denominator for //.

def __floordiv__(self, other):
    try:
        pairs = zip(self, other)
    except TypeError:
        pairs = ((x, other) for x in self)
    return [x // y for (x, y) in pairs]

You may want to check that self and other have the same length if the zip succeeds.

chepner
  • 497,756
  • 71
  • 530
  • 681