Need help understanding python syntax - python

Can someone explain the syntax on lines 5 & 16
1 # Using the generator pattern (an iterable)
2 class firstn(object):
3 def __init__(self, n):
4 self.n = n
5 self.num, self.nums = 0, []
6
7 def __iter__(self):
8 return self
9
10 # Python 3 compatibility
11 def __next__(self):
12 return self.next()
13
14 def next(self):
15 if self.num < self.n:
16 cur, self.num = self.num, self.num+1
17 return cur
18 else:
19 raise StopIteration()
20
21 sum_of_first_n = sum(firstn(1000000))

That's tuple assignment; you can assign to multiple targets.
The right-hand expression is evaluated first, and then each value in that sequence is assigned to the names on left-hand side one by one, from left to right.
Thus, self.num, self.nums = 0, [] assigns 0 to self.num and [] to self.nums.
See the assigment statements documentation:
If the target list is a comma-separated list of targets: The object must be an iterable with the same number of items as there are targets in the target list, and the items are assigned, from left to right, to the corresponding targets.
Because the right-hand side portion is executed first, the line cur, self.num = self.num, self.num+1 assigns self.num to cur after calculating self.num + 1, which is assigned to self.num. If self.num was 5 before that line, then after that line cur is 5, and self.num is 6.

self.num, self.nums = 0, []
cur, self.num = self.num, self.num+1
These are shorthands for the following:
self.num = 0
self.nums = []
and
cur = self.num
self.num = self.num + 1
As a personal preference, I would not use the compound assignments in either of these two lines. The assignments are not related, so there's no reason to combine them.
There are times when compound assignments can prove useful. Consider how one swaps two numbers in languages like C and Java:
temp = a
a = b
b = temp
In Python we can eliminate the temporary variable!
a, b = b, a

Related

How does for-loop actually work in python [duplicate]

This question already has answers here:
Strange result when removing item from a list while iterating over it
(8 answers)
Closed 6 months ago.
I used to thought that for-loop in python work like this
it first makes an iterator by doing iter(iterable)
then does next(that_new_iterator_object)
and when it raises StopIteration then for-loop ends and goes to else block (if provided)
but here it is working differently
>>> a = [1,2,3,4,5,6,7,8,9]
>>> for i in a:
del a[-1]
print(i)
1
2
3
4
5
where are the other numbers
6,7,8,9
the new iterator object that for-loop creates and variable a is different
The for loop works just as you described. However, here is how a list iterator works, roughly:
class ListIterator:
def __init__(self, lst):
self.lst = lst
self.idx = 0
def __iter__(self):
return self
def __next__(self):
if self.idx >= len(self.lst):
raise StopIteration
else:
val = self.lst[self.idx]
self.idx += 1
return val
IOW, the iterator depends on the list, which you are modifying.
So observe:
>>> class ListIterator:
... def __init__(self, lst):
... self.lst = lst
... self.idx = 0
... def __iter__(self):
... return self
... def __next__(self):
... if self.idx >= len(self.lst):
... raise StopIteration
... else:
... val = self.lst[self.idx]
... self.idx += 1
... return val
...
>>> a = list(range(10))
>>> iterator = ListIterator(a)
>>> for x in iterator:
... print(x)
... del a[-1]
...
0
1
2
3
4
>>>
The iterator object holds a reference to the list, it doesn't copy it to iterate over.
You're shortening the list on each iteration, the iterator quits when it runs out at 5 items.
(That said, mutating an iterable while iterating over it is not a good practice; dicts will outright complain if you do that.)
That is roughly how a for-loop works. You can rewrite it like that to prove to yourself that the behaviour is the same:
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for_iter = iter(a)
>>> while True:
... try:
... i = next(for_iter)
... del a[-1]
... print(i)
... except StopIteration:
... break
...
1
2
3
4
5
>>>
iter(a) doesn't hold its own references to all of the elements in a, just to the list itself.

How exactly works this Python generator example? [duplicate]

How would one create an iterative function (or iterator object) in python?
Iterator objects in python conform to the iterator protocol, which basically means they provide two methods: __iter__() and __next__().
The __iter__ returns the iterator object and is implicitly called
at the start of loops.
The __next__() method returns the next value and is implicitly called at each loop increment. This method raises a StopIteration exception when there are no more value to return, which is implicitly captured by looping constructs to stop iterating.
Here's a simple example of a counter:
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
This will print:
3
4
5
6
7
8
This is easier to write using a generator, as covered in a previous answer:
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
The printed output will be the same. Under the hood, the generator object supports the iterator protocol and does something roughly similar to the class Counter.
David Mertz's article, Iterators and Simple Generators, is a pretty good introduction.
There are four ways to build an iterative function:
create a generator (uses the yield keyword)
use a generator expression (genexp)
create an iterator (defines __iter__ and __next__ (or next in Python 2.x))
create a class that Python can iterate over on its own (defines __getitem__)
Examples:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
To see all four methods in action:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
Which results in:
A B C D E
A B C D E
A B C D E
A B C D E
Note:
The two generator types (uc_gen and uc_genexp) cannot be reversed(); the plain iterator (uc_iter) would need the __reversed__ magic method (which, according to the docs, must return a new iterator, but returning self works (at least in CPython)); and the getitem iteratable (uc_getitem) must have the __len__ magic method:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
To answer Colonel Panic's secondary question about an infinite lazily evaluated iterator, here are those examples, using each of the four methods above:
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
Which results in (at least for my sample run):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
How to choose which one to use? This is mostly a matter of taste. The two methods I see most often are generators and the iterator protocol, as well as a hybrid (__iter__ returning a generator).
Generator expressions are useful for replacing list comprehensions (they are lazy and so can save on resources).
If one needs compatibility with earlier Python 2.x versions use __getitem__.
I see some of you doing return self in __iter__. I just wanted to note that __iter__ itself can be a generator (thus removing the need for __next__ and raising StopIteration exceptions)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
Of course here one might as well directly make a generator, but for more complex classes it can be useful.
First of all the itertools module is incredibly useful for all sorts of cases in which an iterator would be useful, but here is all you need to create an iterator in python:
yield
Isn't that cool? Yield can be used to replace a normal return in a function. It returns the object just the same, but instead of destroying state and exiting, it saves state for when you want to execute the next iteration. Here is an example of it in action pulled directly from the itertools function list:
def count(n=0):
while True:
yield n
n += 1
As stated in the functions description (it's the count() function from the itertools module...) , it produces an iterator that returns consecutive integers starting with n.
Generator expressions are a whole other can of worms (awesome worms!). They may be used in place of a List Comprehension to save memory (list comprehensions create a list in memory that is destroyed after use if not assigned to a variable, but generator expressions can create a Generator Object... which is a fancy way of saying Iterator). Here is an example of a generator expression definition:
gen = (n for n in xrange(0,11))
This is very similar to our iterator definition above except the full range is predetermined to be between 0 and 10.
I just found xrange() (suprised I hadn't seen it before...) and added it to the above example. xrange() is an iterable version of range() which has the advantage of not prebuilding the list. It would be very useful if you had a giant corpus of data to iterate over and only had so much memory to do it in.
This question is about iterable objects, not about iterators. In Python, sequences are iterable too so one way to make an iterable class is to make it behave like a sequence, i.e. give it __getitem__ and __len__ methods. I have tested this on Python 2 and 3.
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
If you looking for something short and simple, maybe it will be enough for you:
class A(object):
def __init__(self, l):
self.data = l
def __iter__(self):
return iter(self.data)
example of usage:
In [3]: a = A([2,3,4])
In [4]: [i for i in a]
Out[4]: [2, 3, 4]
All answers on this page are really great for a complex object. But for those containing builtin iterable types as attributes, like str, list, set or dict, or any implementation of collections.Iterable, you can omit certain things in your class.
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in self.string)
# or simply
return self.string.__iter__()
# also
return iter(self.string)
It can be used like:
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
Include the following code in your class code.
def __iter__(self):
for x in self.iterable:
yield x
Make sure that you replace self.iterablewith the iterable which you iterate through.
Here's an example code
class someClass:
def __init__(self,list):
self.list = list
def __iter__(self):
for x in self.list:
yield x
var = someClass([1,2,3,4,5])
for num in var:
print(num)
Output
1
2
3
4
5
Note: Since strings are also iterable, they can also be used as an argument for the class
foo = someClass("Python")
for x in foo:
print(x)
Output
P
y
t
h
o
n
This is an iterable function without yield. It make use of the iter function and a closure which keeps it's state in a mutable (list) in the enclosing scope for python 2.
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
For Python 3, closure state is kept in an immutable in the enclosing scope and nonlocal is used in local scope to update the state variable.
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
Test;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
class uc_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
Improving previous answer, one of the advantage of using class is that you can add __call__ to return self.value or even next_value.
class uc_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
def __call__(self):
next_value = self.value
self.value += 2
return next_value
c = uc_iter()
print([c() for _ in range(10)])
print([next(c) for _ in range(5)])
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# [20, 22, 24, 26, 28]
Other example of a class based on Python Random that can be both called and iterated could be seen on my implementation here

How to take first element from iterator/generator and put it back in Python?

I would like to take first element from iterator, analyse it, then put it back and work with iterator as if it was not touched.
For now I wrote:
def prepend_iterator(element, it):
yield element
for element in it:
yield element
def peek_first(it):
first_element = next(it)
it = prepend_iterator(first_element, it)
return first_element, it
first_element, it = peek_first(it)
analyse(first_element)
continue_work(it)
it is possible to write better/shorter?
Here is an example wit itertools.tee
import itertools
def colors():
for color in ['red', 'green', 'blue']:
yield color
rgb = colors()
foo, bar = itertools.tee(rgb, 2)
#analize first element
first = next(foo)
print('first color is {}'.format(first))
# consume second tee
for color in bar:
print(color)
output
first color is red
red
green
blue
EDIT (05 May 2022): To expand this answer, if you don't mind installing extra [third-party] package, there is more-itertools, that provide convenient tools to lookahead and lookback when working with iterator. These tools peek at an iterable’s values without advancing it.
Here I present a simple method that exploits concatenation of generators.
import itertools
def concat_generators(*args: Iterable[Generator]) -> Generator:
r"""
Concat generators by yielding from first, second, ..., n-th
"""
for gen in args:
yield from gen
your_generator = (i for i in range(10))
first_element = next(your_generator)
# then you could do this
your_generator = concat_generators([first_element], your_generator)
# or this
your_generator = itertools.chain([first_element], your_generator)
Note this will only work if you're pushing back non-None values.
If you implement your generator function (which is what you have) so that you care about the return value of yield, you can "push back" on the generator (with .send()):
# Generator
def gen():
for val in range(10):
while True:
val = yield val
if val is None: break
# Calling code
pushed = false
f = gen()
for x in f:
print(x)
if x == 5:
print(f.send(5))
pushed = True
Here, you're printing both the x from the for loop and the return value of .send() (if you call it).
0
1
2
3
4
5
5 # 5 appears twice because it was pushed back
6
7
8
9
This will only work let you push back once. If you want to push back more times than that, you do something like:
# Generator
def gen():
for val in range(10):
while True:
val = yield val
if val is None: break
# Generator Wrapper
class Pushable:
def __init__(self, g):
self.g = g
self._next = None
def send(self, x):
if self._next is not None:
raise RuntimeError("Can't pushback twice without pulling")
self._next = self.g.send(x)
def __iter__(self):
try:
while True:
# Have to clear self._next before yielding
if self._next is not None:
(tmp, self._next) = (self._next, None)
yield tmp
else:
yield next(self.g)
except StopIteration: return
# Calling code
num_pushed = 0
f = Pushable(gen())
for x in f:
print(x)
if (x == 5) and (num_pushed in [0,1,2]):
f.send(x)
num_pushed += 1
Produces:
0
1
2
3
4
5 # Pushed back (num_pushed = 0)
5 # Pushed back (num_pushed = 1)
5 # Pushed back (num_pushed = 2)
5 # Not pushed back
6
7
8
9

modify variable in argument of conditional python

I have a while loop that operates on outputs provided by another class, until no outputs are left.
while a.is_next():
fn(a.get_next())
Is there a way of checking if a new item exists and. "loading" it at the same time?
while b=a.get_next():
fn(b)
It looks like you're trying to reinvent the iterator. Iterators must have two methods: an __iter__ method that returns the iterator itself and a __next__ method that returns either the next item or raises StopIteration. For example
class MyIterator:
def __init__(self):
self.list = [1, 2, 3]
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
ret = self.list[self.index]
self.index += 1
return ret
except IndexError:
raise StopIteration
That's a lot for that example, but it allows us to use that iterator everywhere Python expects an iterator
for x in MyIterator():
print(x)
1
2
3
Not sure why you want this but you can assign and check if exists in the same statement like:
import itertools as it
for b in (x.get_next() for x in it.repeat(a) if x.is_next()):
fn(b)
Is there a way of checking if a new item exists and. "loading" it at the same time?
The short answer is no. Python assignments cannot be done in the place of a while loop's conditional statement. However, why not simply reassign the value of a.get_next() to a variable each iteration, and use that as your loops conditional:
b = a.get_next() # get the initial value of b
while b:
fn(b)
b = a.get_next() # get the next value for b. If b is 'fasly', the loop will end.
Search for generators, iterators and yield statement.
Code example
class Container:
def __init__(self,l):
self.l = l
def next(self):
i = 0
while (i < len(self.l)):
yield self.l[i]
i += 1
c = Container([1,2,3,4,5])
for item in c.next():
print(item, end=" ") # 1 2 3 4 5

obj in sequence: comparing with 'is' rather than '=='

In a program I have something like this going on:
class MyClass:
def __init__(self, index, other_attribute)
self.index = index #is of type int
self.other_attribute = other_attribute
# I know this is being taken out of python 3,
# but I haven't converted it to using __eq__ etc.
def __cmp__(self, other):
if self.index < other.index:
return -1
if self.index == other.index:
return 0
if self.index > other.index:
return -1
here's the problem
#here are some objects
a = MyClass(1, something)
b = MyClass(1, something_else)
c = MyClass(2, something_more)
ary = [a,c]
if b not in ary:
ary.append(b)
This will not append b because their indices are equal, but they are still different instances. This is b == a is true, but b is a is false. I would like to test for membership by address, not by equivalence. Is there a way to have the in and not in operators use is and not ==? Are there other operators/algorithms that would solve this problem?
If you would like to test membership by address, you could use this any/is combo:
if not any(b is x for x in ary):
ary.append(b)
If you are stuck on using the in syntax, you could define your own list object and implement a __contains__ method that compares with is rather than ==.

Categories

Resources