I would like to be able to run this function without needing to add .elements to the end. For instance, if seta=MySet([1,2,3]) and setb=MySet([1,10,11]), I can run setc=seta.intersection(setb.elements), but not without the .elements. How can I run it without needing to type .elements?
class MySet:
def __init__(self, elements):
self.elements=elements
def intersection(self, other_set):
self.other_set=other_set
new_set = []
for j in other_set:
if j in self.elements:
new_set.append(j)
new_set.sort()
return new_set
Easily, all you have to do is access the .elements in the function. No __repr__ required.
class MySet:
def __init__(self, elements):
self.elements=elements
def intersection(self, setb):
other_set = setb.elements
new_set = []
for j in other_set:
if j in self.elements:
new_set.append(j)
new_set.sort()
return new_set
Make your set an iterable by defining __iter__:
class MySet:
def __init__(self, elements):
self.elements=elements
def intersection(self, other_set):
...
def __iter__(self):
return iter(self.elements)
# Or for implementation hiding, so the iterator type of elements
# isn't exposed:
# yield from self.elements
Now iteration over an instance of MySet seamlessly iterates the elements it contains.
I'd strongly suggest looking at the collections.abc module; you're clearly trying to build a set-like object, and getting the basic behaviors in place is easiest by using collections.abc.Set (or collections.abc.MutableSet) as your base class.
Related
Just learning about properties and setters in python, and seems fair enough when we have a mutable attribute. But what happens when I want to validate a .append() on a list for example? In the below, I can validate the setting of the attribute and it works as expected. But I can bypass its effect by simply appending to get more cookies onto the tray...
class CookieTray:
def __init__(self):
self.cookies = []
#property
def cookies(self):
return self._cookies
#cookies.setter
def cookies(self, cookies):
if len(cookies) > 8:
raise ValueError("Too many cookies in the tray!")
self._cookies = cookies
if __name__ == '__main__':
tray = CookieTray()
print("Cookies: ", tray.cookies)
try:
tray.cookies = [1,1,0,0,0,1,1,0,1] # too many
except Exception as e:
print(e)
tray.cookies = [1,0,1,0,1,0]
print(tray.cookies)
tray.cookies.append(0)
tray.cookies.append(0)
tray.cookies.append(1) # too many, but can still append
print(tray.cookies)
Silly example, but I hope it illustrates my question. Should I just be avoiding the setter and making a "setter" method, like add_cookie(self, cookie_type) and then do my validation in there?
The setter only applies when assigning to the attribute. As you've seen mutating the attribute bypasses this.
To apply the validation to the object being mutated we can use a custom type. Here's an example which just wraps a normal list:
import collections
class SizedList(collections.abc.MutableSequence):
def __init__(self, maxlen):
self.maxlen = maxlen
self._list = []
def check_length(self):
if len(self._list) >= self.maxlen:
raise OverflowError("Max length exceeded")
def __setitem__(self, i, v):
self.check_length()
self._list[i] = v
def insert(self, i, v):
self.check_length()
self._list.insert(i, v)
def __getitem__(self, i): return self._list[i]
def __delitem__(self, i): del self._list[i]
def __len__(self): return len(self._list)
def __repr__(self): return f"{self._list!r}"
When overriding container types collections.abc can be useful - we can see the abstract methods that must be implemented: __getitem__, __setitem__, __delitem__, __len__, and insert in this case. All of them are delegated to the list object that's being wrapped, with the two that add items having the added length check.
The repr isn't needed, but makes it easier to check the contents - once again just delegating to the wrapped list.
With this you can simply replace the self.cookies = [] line with self.cookies = SizedList(8).
You would need to create a ValidatingList class that overrides list.append, something like this:
class ValidatingList(list):
def append(self, value):
if len(self) >= 8:
raise ValueError("Too many items in the list")
else:
super().append(value)
You then convert your cookies to a ValidatingList:
class CookieTray:
def __init__(self):
self.cookies = []
#property
def cookies(self):
return self._cookies
#cookies.setter
def cookies(self, cookies):
if len(cookies) > 8:
raise ValueError("Too many cookies in the tray!")
self._cookies = ValidatingList(cookies)
class Array:
def __init__(self):
self.list = []
def add(self, num):
self.list.append(num)
a = Array()
a.add(1).add(2)
I would like to add number 1, 2 to self.list like this.
How can I implement?
After your insertion returns the instance itself for second operation, then you will have instance itself so you can perform add operation:
def add(self, num):
self.list.append(num)
return self
Return the object itself
def add(self, num):
self.list.append(num)
return self
As an alternative approach, why not just let your add method take a list of values as input? Seems like it would be easier to use like that
def add(self, vals):
self.list += vals
So now you can
a.add([1,2])
Instead of
a.add(1).add(2)
OK so my actual code is somewhat elaborate but I am illustrating the problem that I am having with the following example code:
I have a class that has a list as one of its instance variable. I want the class to be an iterable and return the next element in the list when next is called in the for loop.
So I have as follows:
class SimplaWannaBeIteratable(object):
def __init__(self, list_to_iter, **kwargs)
self._list = list_to_iter
self._item = None
#... other code to initialize
def __iter__(self):
return self
def next(self):
self._item= next(self._list)
return self._item
def current(self):
#So that other uses cases have the access to the current member
return self._current
However if I do the following:
iter_item = SimplaWannaBeIteratable([1,2,3,4,5])
for item in iter_item:
return item
I get:
list object is not an iterator.
If I change the next as follows:
def next(self):
self._item= next(iter((self._list)))
return self._item
I get infinite output.
Can anyone tell me what I need to do to accomplish the task I want to do and why the code above is not working?
From what I understand every time next is called the iterator object associated with the list is called and its next is return. so why can't my list find its iterator?
You need an iterator to iterator over a list. A list itself is not an iterator so you cannot call next() on it.
class SimplaWannaBeIteratable(object):
def __init__(self, list_to_iter, **kwargs):
self._list = list_to_iter
self._item = None
def __iter__(self):
self._iter = iter(self._list) # create/initialize the iterator
return self
def __next__(self): # using the Python 3.x name
self._item = next(self._iter) # use the iterator
return self._item
# ...
You are calling next on self._list, which is a list, not an iterator. next only advances iterators, it does not set up an iterator from an iterable.
def __init__(self, ...):
# ...
self._iterator = iter(self._list)
def next(self):
self._item = next(self._iterator)
return self._item
Regarding your edit, you are getting an infinite recursion because you are calling next on a fresh iterator each time, rather than the same iterator. So you are losing the state of the iterator. Again, see my example above, which sets up the iterator once.
The __next__ special method that you are trying to implement is used to control iteration over a container-like class at each progressive step. If you do not need this functionality and simply want to make your class iterable, omit the method and return iter(self._list) from __iter__:
class SimplaWannaBeIteratable(object):
def __init__(self, list_to_iter, **kwargs):
self._list = list_to_iter
self._item = None
def __iter__(self):
return iter(self._list)
def current(self):
return self._current
Demo:
>>> iter_item = SimplaWannaBeIteratable([1,2,3,4,5])
>>> for item in iter_item:
... item
...
1
2
3
4
5
>>>
I have a tuple of python objects, from which I need a list of objects with no duplicates, using set() (this check for duplicate objects is to be done on an attribute.). This code will give a simple illustration:
class test:
def __init__(self, t):
self.t = t
def __repr__(self):
return repr(self.t)
def __hash__(self):
return self.t
l = (test(1), test(2), test(-1), test(1), test(3), test(2))
print l
print set(l)
However, it did not work. I can do it on an iteration over l, but any idea why set() is not working? Here is the official documentation.
From the documentation you linked to:
The set classes are implemented using dictionaries. Accordingly, the
requirements for set elements are the same as those for dictionary
keys; namely, that the element defines both __eq__() and __hash__().
To be more specific, if a == b then your implementation must be such that hash(a) == hash(b). The reverse is not required.
Also, you should probably call hash in __hash__ to handle long integers
class Test:
def __init__(self, t):
self.t = t
def __repr__(self):
return repr(self.t)
def __hash__(self):
return hash(self.t)
def __eq__(self, other):
return isinstance(other, Test) and self.t == other.t
Small nit picks:
Your implementation of __eq__ doesn't give the other object a chance to run its own __eq__. The class must also consider its members as immutable as the hash must stay constant. You don't want to break your dicts, do you?
class Test:
def __init__(self, t):
self._t = t
#property
def t(self):
return self._t
def __repr__(self):
return repr(self._t)
def __hash__(self):
return hash(self._t)
def __eq__(self, other):
if not isinstance(other, Test):
return NotImplemented # don't know how to handle `other`
return self.t == other.t
I am interested in using the python list object, but with slightly altered functionality. In particular, I would like the list to be 1-indexed instead of 0-indexed. E.g.:
>> mylist = MyList()
>> mylist.extend([1,2,3,4,5])
>> print mylist[1]
output should be: 1
But when I changed the __getitem__() and __setitem__() methods to do this, I was getting a RuntimeError: maximum recursion depth exceeded error. I tinkered around with these methods a lot but this is basically what I had in there:
class MyList(list):
def __getitem__(self, key):
return self[key-1]
def __setitem__(self, key, item):
self[key-1] = item
I guess the problem is that self[key-1] is itself calling the same method it's defining. If so, how do I make it use the list() method instead of the MyList() method? I tried using super[key-1] instead of self[key-1] but that resulted in the complaint TypeError: 'type' object is unsubscriptable
Any ideas? Also if you could point me at a good tutorial for this that'd be great!
Thanks!
Use the super() function to call the method of the base class, or invoke the method directly:
class MyList(list):
def __getitem__(self, key):
return list.__getitem__(self, key-1)
or
class MyList(list):
def __getitem__(self, key):
return super(MyList, self).__getitem__(key-1)
However, this will not change the behavior of other list methods. For example, index remains unchanged, which can lead to unexpected results:
numbers = MyList()
numbers.append("one")
numbers.append("two")
print numbers.index('one')
>>> 1
print numbers[numbers.index('one')]
>>> 'two'
Instead, subclass integer using the same method to define all numbers to be minus one from what you set them to. Voila.
Sorry, I had to. It's like the joke about Microsoft defining dark as the standard.
You can avoid violating the Liskov Substitution principle by creating a class that inherits from collections.MutableSequence, which is an abstract class. It would look something like this:
def indexing_decorator(func):
def decorated(self, index, *args):
if index == 0:
raise IndexError('Indices start from 1')
elif index > 0:
index -= 1
return func(self, index, *args)
return decorated
class MyList(collections.MutableSequence):
def __init__(self):
self._inner_list = list()
def __len__(self):
return len(self._inner_list)
#indexing_decorator
def __delitem__(self, index):
self._inner_list.__delitem__(index)
#indexing_decorator
def insert(self, index, value):
self._inner_list.insert(index, value)
#indexing_decorator
def __setitem__(self, index, value):
self._inner_list.__setitem__(index, value)
#indexing_decorator
def __getitem__(self, index):
return self._inner_list.__getitem__(index)
def append(self, value):
self.insert(len(self) + 1, value)
class ListExt(list):
def extendX(self, l):
if l:
self.extend(l)