I want to extend the UserList class (https://docs.python.org/3/library/collections.html#collections.UserList) with new attributes like so:
class GroceryList(UserList):
def __init__(self, initlist, max_item_weight, max_item_volume):
super().__init__(initlist)
self.max_item_weight = max_item_weight
self.max_item_volume = max_item_volume
Now if I slice GroceryList, I lose max_item_weight and max_item_volume attributes so I override the __getitem__ method:
def __getitem__(self, i):
if isinstance(i, slice):
return self.__class__(self.data[i], self.max_item_weight, self.max_item_volume)
else:
return self.data[i]
How can I override the UserList __getitem__ method so that it is more generic and I don't have to override the __getitem__ method for every new type of list I create, e.g. GroceryList, ToolsList, NonEdibleList... because I want to keep the attributes even after slicing my derived lists.
You can make a copy of the object and change only the self.data variable. Something like the following:
from copy import deepcopy
class UserList2(UserList):
def __getitem__(self, i):
if isinstance(i, slice):
obj = deepcopy(self)
obj.data = self.data[i]
return obj
else:
return self.data[i]
Then you can inherit from this class in the same manner as you would from UserList. for e.g.,
class GroceryList(UserList2):
def __init__(self, initlist, max_item_weight, max_item_volume):
super().__init__(initlist)
self.max_item_weight = max_item_weight
self.max_item_volume = max_item_volume
l1 = GroceryList([1,2,3,4,5,6,7,8,9], 7, 6)
print(l1[3:7], l1[3:7].max_item_weight, l1[3:7].max_item_volume)
gives [4, 5, 6, 7] 7 6 as output where list is the sliced data and 7,6 are the original attributes.
Another approach is as following but this requires some extra bookeeping and using keyword arguments when inheriting.
class UserList2(UserList):
def __init__(self, initlist, **kwargs):
super().__init__(initlist)
self.constants = kwargs.keys()
for k, v in kwargs.items():
self.__setattr__(k, v)
def __getitem__(self, i):
if isinstance(i, slice):
return self.__class__(self.data[i], **{k: self.__getattribute__(k) for k in self.constants})
else:
return self.data[i]
Same example then changes to:
class GroceryList(UserList2):
def __init__(self, initlist, max_item_weight, max_item_volume):
super().__init__(initlist, max_item_weight=max_item_weight, max_item_volume=max_item_volume)
l1 = GroceryList([1,2,3,4,5,6,7,8,9], 7, 6)
print(l1[3:7], l1[3:7].max_item_weight, l1[3:7].max_item_volume)
which gives the same output.
I am a Javascript engineer and am switching into a JS/Python role. Working on some easy leetcodes to get some quick Python practice.
I'm looking to create a LinkedList here and perhaps I am coming at it from a JS mindset?
Error:
AttributeError: type object 'LinkedListNode' has no attribute 'value'
utils.py
# LinkedList Methods
def createLinkedList(arr):
head = createLinkedListNode(None, arr.pop(0))
def populateList(arr, prevNode):
if arr:
node = createLinkedListNode(None, arr.pop(0))
prevNode.next = node
if arr:
populateList(arr, node)
populateList(arr, head)
return head
def createLinkedListNode(next, value):
class LinkedListNode:
def __init__(self):
self.next = next
self.value = value
return LinkedListNode
deleteNode.py
from python.utils import createLinkedList, linkedListToArray
useCase1 = [4, 5, 1, 9]
linkedList = createLinkedList(useCase1)
^ linkedList.value doesn't exist?
Some misunderstandings with python classes:
The class LinkedListNode should not defined in function.
Return LinkedListNode is actually returning the class itself, but not the Instance. To return the instance, you have to call the class. return LinkedListNode()
Using next as instance variable is not ideal. next is an iteration function in python, so when you set self.next = next, you are actually assigning the function to self.next
If you want to set a variable, for example self.next_value = next_value, you should put next_value as a parameter of __init__ function, like def __init__(self, next_value)
Here is a simple demo of Linked List:
class LinkedList:
def __init__(self, value):
self.value = value
self.next_value = None
def __iter__(self):
yield self.value
if self.next_value is not None:
yield from self.next_value
# else raise StopIteration
def __getitem__(self, index):
if index == 0:
return self.value
else:
return self.next_value[index-1]
# recursively get the next value
def __str__(self):
return str(self.value) + ' -> ' + str(self.next_value)
def __len__(self):
if self.next_value is None:
return 1
else:
return 1 + len(self.next_value)
# recursively get the length
def append(self, value):
if self.next_value is None:
self.next_value = LinkedList(value, self)
else:
self.next_value.append(value)
a = LinkedList(2)
a.append(1)
a.append(3)
for num in a:
print(num, end=", ")
print()
print(a[1])
print(a)
print(len(a))
Output:
2, 1, 3,
1
2 -> 1 -> 3 -> None
3
createLinkedListNode() returns the LinkedListNode class itself, not an instance of the class.
Why are you defining classes and functions inside of other functions? That's an odd way of doing things.
How do you make class to iterate by its internal numpy array :
Just idea which does not work :
class ABC:
def __init__(self):
self.ary = np.zeros(50)
def __iter__(self): return np.nditer(self.ary)
def next(self): ...??..
Also how to make assignment work too :
abc = ABC()
abc[5] = 12
abc[7:9] 0
From documentation,
iterator.__next__():
Return the next item from the container. If there are no further items, raise the StopIteration exception. This method corresponds to the tp_iternext slot of the type structure for Python objects in the Python/C API.
For setting and getting values for container class, you need to implement __getitem__ and __setitem__.
For your sample code
class ABC():
def __init__(self):
self.ary = np.zeros(50)
self.index = self.ary.shape[0]
def __iter__(self):
return np.nditer(self.ary)
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
def _check_indx(self, idx):
if abs(idx) >= self.ary.shape[0]:
raise IndexError(f"Invalid Index {idx} for array with shape {self.ary.shape}")
def __setitem__(self, idx, value):
self._check_indx(idx)
self.ary[idx] = value
def __getitem__(self, idx):
self._check_indx(idx)
return self.ary[idx]
In python, I have a class with a method that returns a generator:
class foo():
data = [1, 2, 3]
def mygen(self):
for d in self.data:
yield d
instance = foo()
print([i for i in instance.mygen()])
But I can't reverse this:
print([i for i in reversed(instance.mygen())])
TypeError: 'generator' object is not reversible
So I thought I could implement a class which returns a generator when calling __iter__, like this
class foo():
data = [1, 2, 3]
def mygen(self):
return _ReversibleIterator(self)
class _ReversibleIterator(object):
def __init__(self, obj):
self.obj = obj
def __iter__(self):
for d in obj.data:
yield d
def __reversed__(self):
for d in reversed(obj.data):
yield d
But I think this isn't quite the same, because the _ReversibleIterator class doesn't have a next() method.
So what is the pythonic way to create a class method that returns an iterator that can be reversed()?
(Obviously I'm just using [1,2,3] as an example. The real thing to iterate over is less trivially reversible)
According to the docs, reversed must have one of two things to work with: a __reversed__ method OR a __len__ and a __getitem__ method. If you think about it, this makes sense because most generators can't support reversed because they generate results on the fly: they don't know what the next, let alone the last element is going to be. However, if you know its length and have random-access to any index, it can be reversed.
class foo():
data = [1, 2, 3]
def mygen(self):
return _ReversibleIterator(self)
class _ReversibleIterator(object):
def __init__(self, obj):
self.obj = obj
self.index = 0
def __iter__(self):
self.index = 0
return self
def __reversed__(self):
return reversed(self.obj.data)
def __next__(self):
try:
el = self.obj.data[self.index]
except IndexError:
raise StopIteration
self.index += 1
return el
or
class _ReversibleIterator(object):
def __init__(self, obj):
self.obj = obj
self.index = 0
def __iter__(self):
self.index = 0
return self
def __len__(self):
return len(self.obj.data)
def __getitem__(self, i):
return self.obj.data[i]
def __next__(self):
try:
el = self[self.index]
except IndexError:
raise StopIteration
self.index += 1
return el
By the way, if you would like, you can replace for d in whatever: yield d with yield from whatever.
I have a class object that stores some properties that are lists of other objects. Each of the items in the list has an identifier that can be accessed with the id property. I'd like to be able to read and write from these lists but also be able to access a dictionary keyed by their identifier. Let me illustrate with an example:
class Child(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Teacher(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Classroom(object):
def __init__(self, children, teachers):
self.children = children
self.teachers = teachers
classroom = Classroom([Child('389','pete')],
[Teacher('829','bob')])
This is a silly example, but it illustrates what I'm trying to do. I'd like to be able to interact with the classroom object like this:
#access like a list
print classroom.children[0]
#append like it's a list
classroom.children.append(Child('2344','joe'))
#delete from like it's a list
classroom.children.pop(0)
But I'd also like to be able to access it like it's a dictionary, and the dictionary should be automatically updated when I modify the list:
#access like a dict
print classroom.childrenById['389']
I realize I could just make it a dict, but I want to avoid code like this:
classroom.childrendict[child.id] = child
I also might have several of these properties, so I don't want to add functions like addChild, which feels very un-pythonic anyway. Is there a way to somehow subclass dict and/or list and provide all of these functions easily with my class's properties? I'd also like to avoid as much code as possible.
An indexed list class:
class IndexedList(list):
def __init__(self, items, attrs):
super(IndexedList,self).__init__(items)
# do indexing
self._attrs = tuple(attrs)
self._index = {}
_add = self._addindex
for obj in self:
_add(obj)
def _addindex(self, obj):
_idx = self._index
for attr in self._attrs:
_idx[getattr(obj, attr)] = obj
def _delindex(self, obj):
_idx = self._index
for attr in self._attrs:
try:
del _idx[getattr(obj,attr)]
except KeyError:
pass
def __delitem__(self, ind):
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._delindex(obj)
return list.__delitem__(self, ind)
def __delslice__(self, i, j):
for ind in xrange(i,j):
self.__delitem__(ind)
def __getitem__(self, ind):
try:
return self._index[ind]
except KeyError:
return list.__getitem__(self, ind)
def __getslice__(self, i, j):
return IndexedList(list.__getslice__(self, i, j))
def __setitem__(self, ind, new_obj):
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._delindex(obj)
self._addindex(new_obj)
return list.__setitem__(ind, new_obj)
def __setslice__(self, i, j, newItems):
_get = self.__getitem__
_add = self._addindex
_del = self._delindex
newItems = list(newItems)
# remove indexing of items to remove
for ind in xrange(i,j):
_del(_get(ind))
# add new indexing
if isinstance(newList, IndexedList):
self._index.update(newList._index)
else:
for obj in newList:
_add(obj)
# replace items
return list.__setslice__(self, i, j, newList)
def append(self, obj):
self._addindex(obj)
return list.append(self, obj)
def extend(self, newList):
newList = list(newList)
if isinstance(newList, IndexedList):
self._index.update(newList._index)
else:
_add = self._addindex
for obj in newList:
_add(obj)
return list.extend(self, newList)
def insert(self, ind, new_obj):
# ensure that ind is a numeric index
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._addindex(new_obj)
return list.insert(self, ind, new_obj)
def pop(self, ind=-1):
# ensure that ind is a numeric index
try:
obj = list.__getitem__(self, ind)
except (IndexError, TypeError):
obj = self._index[ind]
ind = list.index(self, obj)
self._delindex(obj)
return list.pop(self, ind)
def remove(self, ind_or_obj):
try:
obj = self._index[ind_or_obj]
ind = list.index(self, obj)
except KeyError:
ind = list.index(self, ind_or_obj)
obj = list.__getitem__(self, ind)
self._delindex(obj)
return list.remove(self, ind)
which can be used as:
class Child(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Teacher(object):
def __init__(self, id, name):
self.id = id
self.name = name
class Classroom(object):
def __init__(self, children, teachers):
self.children = IndexedList(children, ('id','name'))
self.teachers = IndexedList(teachers, ('id','name'))
classroom = Classroom([Child('389','pete')], [Teacher('829','bob')])
print classroom.children[0].name # -> pete
classroom.children.append(Child('2344','joe'))
print len(classroom.children) # -> 2
print classroom.children[1].name # -> joe
print classroom.children['joe'].id # -> 2344
print classroom.children['2344'].name # -> joe
p = classroom.children.pop('pete')
print p.name # -> pete
print len(classroom.children) # -> 1
Edit: I had made a mistake in some of the exception-handling (catching KeyError instead of IndexError); it is fixed. I will add some unit-testing code. If you run into any further errors, please let me know!
You could subclass the collections.OrderedDict class. For example:
import collections
class Child(object):
def __init__(self, id, name):
self.id = id
self.name = name
def __repr__(self):
return 'Child(\'%s\', \'%s\')' % (self.id, self.name)
class MyOrderedDict(collections.OrderedDict):
def __init__(self, *args, **kwds):
super(MyOrderedDict, self).__init__()
if len(args) > 0:
for i in args[0]:
super(MyOrderedDict, self).__setitem__(i.id, i)
def __getitem__(self, key):
if isinstance(key, int):
return super(MyOrderedDict, self).__getitem__(self.keys()[key])
if isinstance(key, slice):
return [super(MyOrderedDict, self).__getitem__(k) for k in self.keys()[key]]
return super(MyOrderedDict, self).__getitem__(key)
def append(self, item):
super(MyOrderedDict, self).__setitem__(item.id, item)
def pop(self, key = None, default = object()):
if key is None:
return self.popitem()
return super(MyOrderedDict, self).pop(self.keys()[key], default = default)
class Classroom(object):
def __init__(self, children):
self.children = MyOrderedDict(children)
classroom = Classroom([Child('389', 'pete')])
print repr(classroom.children[0])
classroom.children.append(Child('2344', 'joe'))
print repr(classroom.children.pop(0))
print repr(classroom.children['2344'])
print repr(classroom.children[0:1])
This code outputs:
Child('389', 'pete')
Child('389', 'pete')
Child('2344', 'joe')
[Child('2344', 'joe')]
Maybe this is some code you wanted to avoid, but for small scale objects performance should be tolerable. I think it is at least within your constraint: I'd also like to avoid as much code as possible.
class Classroom(object):
""" Left out the teachers part for simplicity """
def __init__(self, children):
self.children = children
self._child_by_id = {}
#property
def child_by_id(self):
""" Return a dictionary with Child ids as keys and Child objects
as corresponding values.
"""
self._child_by_id.clear()
for child in self.children:
self._child_by_id[child.id] = child
return self._child_by_id
This will be always up-to-date, since it is computed on the fly.
A little more optimized version could look like this:
...
#property
def child_by_id(self):
scbi, sc = self._child_by_id, self.children
scbi.clear()
for child in sc:
scbi[child.id] = child
return scbi
Here's another variant:
class Classroom(object):
def __init__(self, objects):
for obj in objects:
self.add(obj)
def add(self, obj):
name = obj.__class__.__name__ + "ById"
if name not in self.__dict__:
self.__dict__[name] = {}
self.__dict__[name][obj.id] = obj
def remove(self, obj):
name = obj.__class__.__name__ + "ById"
del self.__dict__[name][obj.id]
def listOf(self, name):
return self.__dict__[name + "ById"].values()
classroom = Classroom([Child('389','pete'),
Teacher('829','bob')])
print classroom.ChildById['389']
classroom.ChildById['123'] = Child('123', 'gabe')
print classroom.listOf('Child')
classroom.remove(classroom.listOf('Teacher')[0])
print classroom.TeacherById
It lets you get inconsistent by allowing you to do classroom.ChildById['123'] = Teacher('456', 'gabe') but it might be good enough to do what you're looking for.