Subclass Python list to Validate New Items

2019-02-18 06:46发布

I want a python list which represents itself externally as an average of its internal list items, but otherwise behaves as a list. It should raise a TypeError if an item is added that can't be cast to a float.

The part I'm stuck on is raising TypeError. It should be raised for invalid items added via any list method, like .append, .extend, +=, setting by slice, etc.

Is there a way to intercept new items added to the list and validate them?

I tried re-validating the whole list in __getattribute__, but when its called I only have access to the old version of the list, plus it doesn't even get called initialization, operators like +=, or for slices like mylist[0] = 5.

Any ideas?

6条回答
一纸荒年 Trace。
2楼-- · 2019-02-18 07:03

The array.array class will take care of the float part:

class AverageList(array.array):
    def __new__(cls, *args, **kw):
        return array.array.__new__(cls, 'd')
    def __init__(self, values=()):
        self.extend(values)
    def __repr__(self):
        if not len(self): return 'Empty'
        return repr(math.fsum(self)/len(self))

And some tests:

>>> s = AverageList([1,2])
>>> s
1.5
>>> s.append(9)
>>> s
4.0
>>> s.extend('lol')
Traceback (most recent call last):
  File "<pyshell#117>", line 1, in <module>
    s.extend('lol')
TypeError: a float is required
查看更多
smile是对你的礼貌
3楼-- · 2019-02-18 07:10

Here's how to create a subclass using the MutableSequence abstract base class in the collections module as its base class (not fully tested -- an exercise for the reader ;-):

import collections

class AveragedSequence(collections.MutableSequence):
    def _validate(self, x):
        try: return float(x)
        except: raise TypeError("Can't add {} to AveragedSequence".format(x))
    def average(self):  return sum(self._list) / len(self._list)
    def __init__(self, arg):  self._list = [self._validate(v) for v in arg]
    def __repr__(self):  return 'AveragedSequence({!r})'.format(self._list)
    def __setitem__(self, i, value):  self._list[i] = self._validate(value)
    def __delitem__(self, i):  del self._list[i]
    def insert(i, value):  return self._list.insert(i, self._validate(value))
    def __getitem__(self, i):  return self._list[i]
    def __len__(self):  return len(self._list)
    def __iter__(self):  return iter(self._list)
    def __contains__(self, item):  return item in self._list

if __name__ == '__main__':
    avgseq = AveragedSequence(range(10))
    print avgseq
    print avgseq.average()
    avgseq[2] = 3
    print avgseq
    print avgseq.average()
    # ..etc
查看更多
我只想做你的唯一
4楼-- · 2019-02-18 07:12

The general approach would be to create your own class inheriting vom list and overwriting the specific methods like append, extend etc. This will probably also include magic methods of the Python list (see this article for details: http://www.rafekettler.com/magicmethods.html#sequence).

For validation, you will need to overwrite __setitem__(self, key, value)

查看更多
Fickle 薄情
5楼-- · 2019-02-18 07:17

There are 7 methods of the list class that add elements to the list and would have to be checked. Here's one compact implementation:

def check_float(x):
    try:
        f = float(x)
    except:
        raise TypeError("Cannot add %s to AverageList" % str(x))

def modify_method(f, which_arg=0, takes_list=False):
    def new_f(*args):
        if takes_list:
            map(check_float, args[which_arg + 1])
        else:
            check_float(args[which_arg + 1])
        return f(*args)
    return new_f

class AverageList(list):
    def __check_float(self, x):
        try:
            f = float(x)
        except:
            raise TypeError("Cannot add %s to AverageList" % str(x))

    append = modify_method(list.append)
    extend = modify_method(list.extend, takes_list=True)
    insert = modify_method(list.insert, 1)
    __add__ = modify_method(list.__add__, takes_list=True)
    __iadd__ = modify_method(list.__iadd__, takes_list=True)
    __setitem__ = modify_method(list.__setitem__, 1)
    __setslice__ = modify_method(list.__setslice__, 2, takes_list=True)
查看更多
姐就是有狂的资本
6楼-- · 2019-02-18 07:27

Actually the best answer may be: don't.

Checking all objects as they get added to the list will be computationally expensive. What do you gain by doing those checks? It seems to me that you gain very little, and I'd recommend against implementing it.

Python doesn't check types, and so trying to have a little bit of type checking for one object really doesn't make a lot of sense.

查看更多
Deceive 欺骗
7楼-- · 2019-02-18 07:30

Inherit from MutableSequence and implement the methods it requires as well as any others that fall outside of the scope of Sequences alone -- like the operators here. This will allow you to change the operator manipulations for list-like capabilities while automatically generating iterators and contains capabilities.

If you want to check for slices btw you need to do isinstance(key, slice) in your __getitem__ (and/or __setitem__) methods. Note that a single index like myList[0] is not a slice request, but a single index and myList[:0] is an actual slice request.

查看更多
登录 后发表回答