__getitem__ invocation in for loop - python

I am learning Python I don't get one thing. Consider this code:
class Stack:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __getitem__(self,index):
print "index",index
return self.items[index]
def __len__(self):
return len(self.items)
stack = Stack()
stack.push(2)
stack.push(1)
stack.push(0)
for item in stack:
print item
and the output
index 0
2
index 1
1
index 2
0
index 3
Why is getitem called four times?

The for loop doesn't know how to iterate over your object specifically because you have not implemented __iter__(), so it uses the default iterator. This starts at index 0 and goes until it gets an IndexError by asking for index 3. See http://effbot.org/zone/python-for-statement.htm.
Your implementation would be a lot simpler if you derived from list, by the way. You wouldn't need __init__(), pop(), or __getitem__(), and push could be just another name for append. Also, since list has a perfectly good __iter()__ method, for will know how to iterate it without going past the end of the list.
class Stack(list):
push = list.append

Related

Code of a generator that does not work properly in Python

I have the following code of an iterator. Since I have created a separate class meant to iterate through my list, if I put two consecutive for statements that I used to iterate through my object, I should not need to reset the index manually. But, on a global run, it only displays once the elements of my list, and upon multiple iterations, it stops displaying anything, so I think somewhere the resetting of the index does not happen properly. Please use simple code and simple explanations because I am a beginner.
my_list = [1,2,3]
class Iterator:
def __init__(self,seq):
self.seq = seq
def __next__(self):
if len(self.seq) > 0:
return self.seq.pop(0)
raise StopIteration
class Iterating:
def __init__(self):
pass
def __iter__(self):
return Iterator(my_list)
i_1 =Iterating()
for element in i_1:
print(element)
print()
for element in i_1:
print(element)
# why does this not work?
It's because while you're iterating over your Iterator you're deleting all elements from your self.seq thus when you try to iterate over it second time you're iterating over empty list. You could create temporary list that you'll pop from and just "refill" it every time you finish iterating for further usage.
class Iterator:
def __init__(self,seq):
self.seq = seq
self.temp_seq = self.seq.copy()
def __next__(self):
if len(self.temp_seq) > 0:
return self.temp_seq.pop(0)
self.temp_seq = self.seq.copy()
raise StopIteration
The problem is that method pop modifies list (see documentation):
def __next__(self):
if len(self.seq) > 0:
return self.seq.pop(0) # method pop(0) modifies list
raise StopIteration
When you create new instance Iterator(my_list) variable my_list is passed by reference.
To fix your could you can create a copy of my_list:
Iterator(my_list.copy())

Implementation of a Stack

I'm confused with this Stack implementation (ADT). Mainly with adding and removing options, but also by using empty dictionary, instead of empty list.
Here is my problem:
By using an empty list, removing and adding implementations are understandable:
class Stack:
def __init__(self):
self.elements = []
def add(self, item):
self.elements.append(item)
def remove(self):
self.elements.pop()
However, I'm confusing such implementations for an empty dictionary:
class D:
def __init__(self):
self.elements = {}
self.index = 0 # since only adding one element per add method
# better to use indexing for key1, then key2
def add(self, item):
# For dictionary better to use
# self.elements[item] = new_item
# but in stack we adding one element
self.elements[item] = self.index
self.index = item
def remove(self):
self.elements.pop(self.index)
For this code, I want to create new dictionary after adding two elements:
For instance we add(7), add(8) and we get {7:8}. But instead, after adding more elements, like add(9) and add(10), I got {7:8}, {8:9}, {9:10}.
How do I deal with that? My remove method working fine, but adding seems not great.

How can I have multiple iterators over a single python iterable at the same time?

I would like to compare all elements in my iterable object combinatorically with each other. The following reproducible example just mimics the functionality of a plain list, but demonstrates my problem. In this example with a list of ["A","B","C","D"], I would like to get the following 16 lines of output, every combination of each item with each other. A list of 100 items should generate 100*100=10,000 lines.
A A True
A B False
A C False
... 10 more lines ...
D B False
D C False
D D True
The following code seemed like it should do the job.
class C():
def __init__(self):
self.stuff = ["A","B","C","D"]
def __iter__(self):
self.idx = 0
return self
def __next__(self):
self.idx += 1
if self.idx > len(self.stuff):
raise StopIteration
else:
return self.stuff[self.idx - 1]
thing = C()
for x in thing:
for y in thing:
print(x, y, x==y)
But after finishing the y-loop, the x-loop seems done, too, even though it's only used the first item in the iterable.
A A True
A B False
A C False
A D False
After much searching, I eventually tried the following code, hoping that itertools.tee would allow me two independent iterators over the same data:
import itertools
thing = C()
thing_one, thing_two = itertools.tee(thing)
for x in thing_one:
for y in thing_two:
print(x, y, x==y)
But I got the same output as before.
The real-world object this represents is a model of a directory and file structure with varying numbers of files and subdirectories, at varying depths into the tree. It has nested links to thousands of members and iterates correctly over them once, just like this example. But it also does expensive processing within its many internal objects on-the-fly as needed for comparisons, which would end up doubling the workload if I had to make a complete copy of it prior to iterating. I would really like to use multiple iterators, pointing into a single object with all the data, if possible.
Edit on answers: The critical flaw in the question code, pointed out in all answers, is the single internal self.idx variable being unable to handle multiple callers independently. The accepted answer is the best for my real class (oversimplified in this reproducible example), another answer presents a simple, elegant solution for simpler data structures like the list presented here.
It's actually impossible to make a container class that is it's own iterator. The container shouldn't know about the state of the iterator and the iterator doesn't need to know the contents of the container, it just needs to know which object is the corresponding container and "where" it is. If you mix iterator and container different iterators will share state with each other (in your case the self.idx) which will not give the correct results (they read and modify the same variable).
That's the reason why all built-in types have a seperate iterator class (and even some have an reverse-iterator class):
>>> l = [1, 2, 3]
>>> iter(l)
<list_iterator at 0x15e360c86d8>
>>> reversed(l)
<list_reverseiterator at 0x15e360a5940>
>>> t = (1, 2, 3)
>>> iter(t)
<tuple_iterator at 0x15e363fb320>
>>> s = '123'
>>> iter(s)
<str_iterator at 0x15e363fb438>
So, basically you could just return iter(self.stuff) in __iter__ and drop the __next__ altogether because list_iterator knows how to iterate over the list:
class C:
def __init__(self):
self.stuff = ["A","B","C","D"]
def __iter__(self):
return iter(self.stuff)
thing = C()
for x in thing:
for y in thing:
print(x, y, x==y)
prints 16 lines, like expected.
If your goal is to make your own iterator class, you need two classes (or 3 if you want to implement the reversed-iterator yourself).
class C:
def __init__(self):
self.stuff = ["A","B","C","D"]
def __iter__(self):
return C_iterator(self)
def __reversed__(self):
return C_reversed_iterator(self)
class C_iterator:
def __init__(self, parent):
self.idx = 0
self.parent = parent
def __iter__(self):
return self
def __next__(self):
self.idx += 1
if self.idx > len(self.parent.stuff):
raise StopIteration
else:
return self.parent.stuff[self.idx - 1]
thing = C()
for x in thing:
for y in thing:
print(x, y, x==y)
works as well.
For completeness, here's one possible implementation of the reversed-iterator:
class C_reversed_iterator:
def __init__(self, parent):
self.parent = parent
self.idx = len(parent.stuff) + 1
def __iter__(self):
return self
def __next__(self):
self.idx -= 1
if self.idx <= 0:
raise StopIteration
else:
return self.parent.stuff[self.idx - 1]
thing = C()
for x in reversed(thing):
for y in reversed(thing):
print(x, y, x==y)
Instead of defining your own iterators you could use generators. One way was already shown in the other answer:
class C:
def __init__(self):
self.stuff = ["A","B","C","D"]
def __iter__(self):
yield from self.stuff
def __reversed__(self):
yield from self.stuff[::-1]
or explicitly delegate to a generator function (that's actually equivalent to the above but maybe more clear that it's a new object that is produced):
def C_iterator(obj):
for item in obj.stuff:
yield item
def C_reverse_iterator(obj):
for item in obj.stuff[::-1]:
yield item
class C:
def __init__(self):
self.stuff = ["A","B","C","D"]
def __iter__(self):
return C_iterator(self)
def __reversed__(self):
return C_reverse_iterator(self)
Note: You don't have to implement the __reversed__ iterator. That was just meant as additional "feature" of the answer.
Your __iter__ is completely broken. Instead of actually making a fresh iterator on every call, it just resets some state on self and returns self. That means you can't actually have more than one iterator at a time over your object, and any call to __iter__ while another loop over the object is active will interfere with the existing loop.
You need to actually make a new object. The simplest way to do that is to use yield syntax to write a generator function. The generator function will automatically return a new iterator object every time:
class C(object):
def __init__(self):
self.stuff = ['A', 'B', 'C', 'D']
def __iter__(self):
for thing in self.stuff:
yield thing

I am trying make a function that takes a number as a parameter and removes all occurrences of it from a Queue

class Queue:
def __init__(self):
self.items = []
def is_empty(self):
return self.items == []
def add(self, item):
self.items.append(item)
def remove(self):
self.items.reverse()
return self.items.pop()
I need to create a function that takes in a number as a parameter and a queue then removes every occurrence of that number in the queue but with the exception of the omissions. I've put up a model of what my Queue looks like above and I'll put a model of what the queue should somewhat look like (It's very messy and in its early stages) below.
def remove_item(q, val):
q_temp = Queue
while not q.is_empty():
q_temp.add(q.remove)
remove_item()
I cannot directly modify it in any way and I can't put the elements of the Queue in a normal list. Anyone got any solutions?
Edit: Also it needs to be executable in IDLE like this
remove_item(queue,number)
What I would do is something like this:
number_to_remove = 123
for i in range(0, queue.length()):
number = queue.remove()
if number != number_to_remove:
queue.add(number)
That way you "loop" trough the queue, you look at every number and if it's not the number you should remove then just add it again. You need to create the .length() method though.
That code will fail because bad indentation:
def remove_item(q, val):
#> q_temp = Queue
And you're setting q_temp to the class, not to an instance, making that it can't be modified.
q_temp = Queue()
And, the .remove method doesn't work, there's an easier way to make it work:
def remove(self):
return self.items.pop(-1)
But I don't have a (fully valid) answer. I we could get the queue's items, I'd use conditional list comprehensions for deleting determinated items, and applying that into the queue.
def removeFromQueue(queue, value = ""):
items = queue.items
queue.items = [i for i in items if i != value]
return queue
You can do if you let us to use the Queue class use only the class methods (we can make variables, make another queues, .remove and .add numbers, but not to use .items) you can do this:
def removeFromQueue(queue, value = ""):
items = []
for i in len(queue):
items.append(queue.remove())
items = [i for i in items if i != value]:
for i in items:
queue.add(items.pop(0))
I don't think that's valid, though. And it would need defining Queue.__len__()*:
def __len__(self):
return len(self.items)
* or modififying the code and defining __iter__ or some sort of thing like that

Radix sorting, "Queue" object not iterable

I've come to an end with my assignment, I don't know where I go from where I am right now, the code is currently looking like this:
def radixsorting1(n,m):
div=1
mod=10
bin_list=[]
alist=[]
r=[]
s=[]
for bins in range(0,10):
bin_list.append(Queue())
for k in range(0,m):
r.append(random.randint(1,10**n))
if not len(r)==0:
o=max(r)
y=len(str(o))
for p in range(y):
for num in r:
minsta_tal=num%mod
minsta_tal=int(minsta_tal//div)
bin_list[minsta_tal].put(num)
new_list=[]
for bins in bin_list:
while not bins.isempty():
new_list.append(bins.dequeue())
alist=new_list
return alist
What I've been trying to do is to create 10 queues in put them in a list, then random m numbers from 1 to 10^n. Lets say I get 66 and 72, then I first sort them by the "small number", that is 6 and 2 in my numbers, then put them in a lost, and then do the process all over again but for the number 6 and 7 (the bigger number). In its current shape I get the error "Queue" object is not iterable.
My Queue class is looking like this, I think this one is okay.
class Queue:
def __init__(self):
self.lista=[]
def put(self,x):
self.lista.append(x)
def get(self):
if not len(self.lista)==0:
return self.lista.pop(0)
def isempty(self):
if len(self.lista)==0:
return True
else:
False
def length(self):
return len(self.lista)
def dequeue(self):
if not len(self.lista)==0:
n=self.lista.pop(0)
return n
You need to add a bit more code to make it an iterable. __iter__ should return an iterator. The iterator should have a next method.
Take a look at this:
Build a Basic Python Iterator
So it is my understanding that the thing you want to iterate over is the contents of self.lista... Why not just return lista's iterator.
Here is the easiest way to do that:
class Queue:
...
def __iter__(self):
return self.lista.__iter__()
It's a bit hard to see what exactly it is that you want.. If what you are trying to do is empty listaas you iterate over it (Queue is a fifo kinda deal) it then rather do this:
class Queue:
...
def __iter__(self):
return self
def next(self):
if self.lista: #since empty lists are Falsey
return self.lista.pop(0)
raise StopIteration

Categories

Resources