Can you easily create a list-like object in python that uses something like a descriptor for its elements?

I am trying to write an interface that somewhat abstracts out another interface.

The bottom interface is somewhat incompatible with what it requires, sometimes id, and sometimes names. I am trying to hide details like these.

I want to create a list-like object that allows you to add names to it, but internally store the identifier associated with those names.

Preferably, I would like to use something like descriptors for class attributes, except that they work on list items instead. That is, a function (for example, __get__ ) is called for everything that is added to the list to convert it to an identifier that I want to keep inside, and another function (for example, __set__ ) to return objects (which provide convenient methods) instead of the actual identifier when trying to retrieve items from a list.

So that I can do something like this:

 def get_thing_id_from_name(name): # assume that this is more complicated return other_api.get_id_from_name_or_whatever(name) class Thing(object) def __init__(self, thing_id): self.id = thing_id self.name = other_api.get_name_somehow(id) def __eq__(self, other): if isinstance(other, basestring): return self.name == other if isinstance(other, Thing): return self.thing_id == other.thing_id return NotImplemented tl = ThingList() tl.append('thing_one') tl.append('thing_two') tl[1] = 'thing_three' print tl[0].id print tl[0] == 'thing_one' print tl[1] == Thing(3) 

The documentation recommends that you specify 17 methods (not including the constructor) for an object that acts as a mutable sequence. I don't think subclassing list will help me at all. It seems to me that I should be able to achieve this by simply defining a getter and setter somewhere.

UserList seems to be depreciating (though in python3? I use 2.7, though).

Is there a way to achieve this, or something similar, without having to redefine so much functionality?

+4
source share
1 answer

Yo you don’t need to redefine all list methods - __setitem__, __init__ and \ append should be enough - you can have an insert and some others. You can write __setitem__ and __getitem__ to call the __set__ and __get__ methods in the special "Thing" class, just like the descriptors do.

Here is a quick example - maybe something like what you want:

 class Thing(object): def __init__(self, thing): self.value = thing self.name = str(thing) id = property(lambda s: id(s)) #... def __repr__(self): return "I am a %s" %self.name class ThingList(list): def __init__(self, items): for item in items: self.append(item) def append(self, value): list.append(self, Thing(value)) def __setitem__(self, index, value): list.__setitem__(self, index, Thing(value)) 

Example:

 >>> a = ThingList(range(3)) >>> a.append("three") >>> a [I am a 0, I am a 1, I am a 2, I am a three] >>> a[0].id 35242896 >>> 

- change -

The OP commented: "I really hoped that there would be an opportunity to get all the functionality from the list - adding, expanding, fragments, etc., and just need to redefine the behavior of get / set item."

So, the way it is - you really need to redefine all the relevant methods in this way. But if we want to avoid just a large number of code plates with many functions that perform almost the same thing, new, overridden methods can be generated dynamically - all we need is a decorator to change ordinary objects to Things for all operations that set values:

 class Thing(object): # Prevents duplicating the wrapping of objects: def __new__(cls, thing): if isinstance(thing, cls): return thing return object.__new__(cls, thing) def __init__(self, thing): self.value = thing self.name = str(thing) id = property(lambda s: id(s)) #... def __repr__(self): return "I am a %s" %self.name def converter(func, cardinality=1): def new_func(*args): # Pick the last item in the argument list, which # for all item setter methods on a list is the one # which actually contains the values if cardinality == 1: args = args[:-1] + (Thing(args[-1] ),) else: args = args[:-1] + ([Thing(item) for item in args[-1]],) return func(*args) new_func.func_name = func.__name__ return new_func my_list_dict = {} for single_setter in ("__setitem__", "append", "insert"): my_list_dict[single_setter] = converter(getattr(list, single_setter), cardinality=1) for many_setter in ("__setslice__", "__add__", "__iadd__", "__init__", "extend"): my_list_dict[many_setter] = converter(getattr(list, many_setter), cardinality="many") MyList = type("MyList", (list,), my_list_dict) 

And it works like this:

 >>> a = MyList() >>> a [] >>> a.append(5) >>> a [I am a 5] >>> a + [2,3,4] [I am a 5, I am a 2, I am a 3, I am a 4] >>> a.extend(range(4)) >>> a [I am a 5, I am a 0, I am a 1, I am a 2, I am a 3] >>> a[1:2] = range(10,12) >>> a [I am a 5, I am a 10, I am a 11, I am a 1, I am a 2, I am a 3] >>> 
+3
source

Source: https://habr.com/ru/post/1390684/


All Articles