Consider these two variants of the same loop structure:
x = find_number_of_iterations()
for n in range(x):
# do something in loop
and:
for n in range(find_number_of_iterations()):
# do something
Will the second loop evaluate the method find_number_of_iterations in every subsequent loop run, or will the method find_number_of_iterations be evaluated only once even in the second variant?
I suspect that your mentor's confusion is traceable to the fact that the semantics of Python's for loop is so much different than in other languages.
In a language like C a for loop is more or less syntactic sugar for a while loop:
for(i = 0; i < n; i++)
{
//do stuff
}
is equivalent to:
i = 0;
while(i < n)
{
//do stuff
i++
}
In Python it is different. Its for loops are iterator-based. The iterator object is initialized just once and then consumed in subsequent iterations. The following snippets show that Python's for loop is not (easily) translatable into a while loop, and also shows that with a while loop your mentor's concern is valid:
>>> def find_number_of_iterations():
print("called")
return 3
>>> for i in range(find_number_of_iterations()): print(i)
called
0
1
2
>>> i = 0
>>> while i < find_number_of_iterations():
print(i)
i += 1
called
0
called
1
called
2
called
The function is called once. Logically, were it to be called on every iteration then the loop range could change causing all kinds of havoc. This is easily tested:
def find_iterations():
print "find_iterations called"
return 5
for n in range(find_iterations()):
print n
Results in:
$ python test.py
find_iterations called
0
1
2
3
4
Either way, the function only gets called once. You can demonstrate this as follows:
>>> def test_func():
"""Function to count calls and return integers."""
test_func.called += 1
return 3
# first version
>>> test_func.called = 0
>>> x = test_func()
>>> for _ in range(x):
print 'loop'
loop
loop
loop
>>> test_func.called
1
# second version
>>> test_func.called = 0
>>>
>>> for _ in range(test_func()):
print 'loop'
loop
loop
loop
>>> test_func.called
1
The function is called once, and the result of calling that function is passed to range (then the result of calling range is iterated over); the two versions are logically equivalent.
Related
If I have a python generator function, let's say this one:
def gen():
x = 0
while (true):
yield x
x += 1
This function remembers its current state, and every time you call gen(), yields a new value. Essentially, I would like a Kotlin sequence which can remember its state.
def gen():
x = 0
while (true):
yield x
x += 1
This function remembers its current state, and every time you call gen(), yields a new value.
This is incorrect. Every time you call gen() you get a new "generator object" whose state is independent of any other generator object created by this function. You then query the generator object to get the next number. For example:
def demo():
numbers = gen() # 'gen()' from your question
for _ in range(0, 3):
next_number = next(numbers)
print(next_number)
if __name__ == '__main__'
demo()
print()
demo()
Output:
0
1
2
0
1
2
As you can see, the sequence of numbers "starts over" when you call gen() again (though if you kept a reference to the old generator object it would continue from 2, even after calling gen() again).
In Kotlin, you can use the kotlin.sequences.iterator function. It creates an Iterator which lazily yields the next value, just like a Python generator object. For example:
fun gen() = iterator {
var x = 0
while (true) {
yield(x)
x++
}
}
fun demo() {
val numbers = gen()
repeat(3) {
val nextNumber = numbers.next()
println(nextNumber)
}
}
fun main() {
demo()
println()
demo()
}
Which will output:
0
1
2
0
1
2
Just like the Python code.
Note you can do the essentially the same thing with a Kotlin Sequence, you just have to convert the Sequence into an Iterator if you want to use it like a Python generator object. Though keep in mind that Kotlin sequences are meant more for defining a series of operations and then lazily processing a group of elements in one go (sort of like Java streams, if you're familiar with them).
As stated before in the comments https://kotlinlang.org/docs/sequences.html are the answer, and you don't even need an iterator. You can generate sequence using https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/generate-sequence.html
and here is a little playground witch produces similar sequence as your generator https://pl.kotl.in/LdboRzAzr
I'm wondering if there's a way to have member variables in a function instance. For example,
incr_instance = incrementor(1)
incr_instance()
incr_instance()
incr_instance()
would print out
1
2
3
Yes, I know I can just use a class, but this is a question about weird language quirks in the python language.
You can have a function incrementor that returns another function that keeps incrementing the counter n (the inner function uses nonlocal n to access n instead of using incrementor.n):
def incrementor(n):
def inc():
nonlocal n
n += 1
return n - 1
return inc
Test:
incr_instance = incrementor(1)
incr_instance_2 = incrementor(10)
print(incr_instance())
print(incr_instance())
print(incr_instance())
print(incr_instance_2())
print(incr_instance_2())
print(incr_instance_2())
print(incr_instance())
print(incr_instance())
print(incr_instance())
print(incr_instance_2())
print(incr_instance_2())
print(incr_instance_2())
Output:
1
2
3
10
11
12
4
5
6
13
14
15
You can do the following:
def incrementor(n):
def incrementor_aux():
incrementor_aux.x += n
print(incrementor_aux.x)
incrementor_aux.x = 0
return incrementor_aux
You can even create multiple incrementors and have them increment their values independantly:
incr_instance1 = incrementor(1)
incr_instance2 = incrementor(1)
incr_instance1() #prints 1
incr_instance1() #prints 2
incr_instance2() #prints 1
incr_instance1() #prints 3
incr_instance2() #prints 2
What happens is a new instance of incrementor_aux function is created everytime you call incrementor, with its own x local value which stays in each instance's scope.
Here's one that doesn't use function attributes:
from functools import partial
def increment(x):
def gen():
y = x
while True:
y += 1
yield y
g = gen()
return partial(next, g)
f1 = increment(1)
f1()
# 2
f1()
# 3
f2 = increment(0)
f2()
# 1
f2()
# 2
f1()
# 4
def incrementor(i=0):
def inc():
inc.i += 1
return inc.i
inc.i = i - 1
return inc
incr_inst = incrementor(1)
incr_inst() # 1
incr_inst() # 2
But for this actual task
from itertools import count
incr = count(1)
incr() # 1
incr() # 2
Looks like you want a factory that gives you functions with local state, without using a class. Instead of making the state variable a member of the function, I'd use python's nonlocal syntax:
def incrementor(start):
state = start -1
def interior():
nonlocal state
state += 1
return state
return interior
This will print 7, then 8:
inc7 = incrementor(7)
print(inc7())
print(inc7())
This will print 1, then 9 10, then 2 3; each of the functions inc1() and inc7() is going at its own pace.
inc1 = incrementor(1)
print(inc1())
# Yes, I can mix them
print("inc7 again:", inc7(), inc7())
print("inc1 again:", inc1(), inc1())
I fancied having a go at this and tried to make it concise, here's what I ended up with. It's not best practice but you asked for quirky!
The aim here was to not pass the value back into the function, as the OP requested.
index = 0;
incr_instance = lambda: [index, exec("global index; index += 1")]
print(incr_instance()[0])
print(incr_instance()[0])
print(incr_instance()[0])
Will print
0
1
2
Index is our global which keeps track of our progress, the lambda expression executes two statements. The first statement returns the value of index and the second increments it, which had to be done using exec as you cannot assign variables from a lambda as it's for expressions only. The two statements are within square brackets in order to run both; otherwise it will return index only.
The [0] index gives us the value of index rather than the result of the exec expression (always None)
I want to use next to skip one or more items returned from a generator. Here is a simplified example designed to skip one item per loop (in actual use, I'd test n and depending on the result, may repeat the next() and the generator is from a package I don't control):
def gen():
for i in range(10):
yield i
for g in gen():
n = next(gen())
print(g, n)
I expected the result to be
0 1
2 3
etc.
Instead I got
0 0
1 0
etc.
What am I doing wrong?
You're making a new generator each time you call gen(). Each new generator starts from 0.
Instead, you can call it once and capture the return value.
def gen():
for i in range(10):
yield i
x = gen()
for g in x:
n = next(x)
print(g, n)
I'm a fairly experienced C/C++ (and to some degree, Java) programmer. I'm learning python, but I'm baffled at some strange (for my backgroung) behaviors of the language.
I'm learning about nested function and closures (reading "Learning Python", that seems a really good source for me).
I understand that if I nest a def inside a for loop when I call the created function, it looks up the last value of the captured loop variable (as it captures by reference, as a C++ programmer would put it)
funcs = []
for i in range(4):
def f():
print(i)
funcs.append(f)
and running the program the result is
>>> for f in funcs:
f()
3
3
3
3
Now, I was wrapping my head around this when I stumbled upon this (what to me seems) an inconsistency: if I do
for i in range(4):
funcs[i]()
0
1
2
3
more baffling, if I do
>>> i = 2
>>> funcs[i]()
2
and now, all functions in list returns 2:
for f in funcs:
f()
2
2
2
2
there must be some scope related question that I can't grasp
First, this creates a list of four functions.
funcs = []
for i in range(4):
def f():
print(i)
funcs.append(f)
Each of these functions looks up the value of i and then prints it.
This loops through the list of function and calls each one:
>>> for f in funcs:
f()
As stated above, these functions look up i, which is 3 right now due to the for i in range(4) loop that completed earlier, so you get four printouts of 3.
Now you loop again, using i as the loop variable:
for i in range(4):
funcs[i]()
0
1
2
3
The first time through the loop, i is 0, so when the function looks up i, it gets 0, and then prints that. Then it changes to 1, then 2, then 3.
The following code simply changes i in yet another way, and calls a function:
>>> i = 2
>>> funcs[i]()
2
You could've called any of those functions and they still would've printed 2, because that's the value of i now. You're just getting lost because you looped over range(4) to create these functions, then you looped over range(4) to index the list of functions, and you keep reusing i, and then you reassign i and also use it to index the list.
If you want each function's printed value of i to be fixed at what it was when you defined the function, the easiest way to do that is with a default argument, as those are evaluated when the function is defined rather than when it's called:
funcs = []
for i in range(4):
def f(num=i):
print(num)
funcs.append(f)
For the sake of completeness, this is an alternate implementation:
def getfunc(i):
return lambda: i
funcs = []
for i in range(5):
funcs.append(getfunc(i))
for item in funcs:
print(item())
Your functions
def f():
print(i)
print the current value of i.
If you write
for i in range(4):
funcs[i]()
then i is being set to 0,1,2,3 as you go through the loop. That's what for i in range(4) means.
If you write
for f in funcs:
f()
then i continues with whatever value it already had.
take some time for me to understand, actually in this example,
You will find f.__closure__ is None if you print it, ie. nothing related to closure, it's just about procedure the undefined local var i look for its value:
it can't find its value in local scope, finally find it in global scope (like python MRO)
There is no inconsistency here. The value of i in f() depends on the value of i from the parent scope. After you've run the first for i in range(4) i has the value of the last item in the range, which is 3, and thus all subsequent calls to f() will print 3
If you run
for i in range(4):
funcs[i]()
you redefine the value of i at each iteration step, and so you get 0,1,2,3 as the values printed by f. Doing
for x in range(4):
funcs[x]()
will not affect the value of i and so you'll get 3 as the value of i in all function calls
I have had experience in Java/C#/C++ and for loops or pretty much if not exactly done the same. Now I'm learning Python through Codecademy. I find it poor the way it trys to explain for loops to me. The code they give you is
my_list = [1,9,3,8,5,7]
for number in my_list:
# Your code here
print 2 * number
Is this saying for every number in my_list ... print 2 * number.
That somewhat makes sense to me if that's true but I don't get number and how that works. It's not even a variable declared earlier. Are you declaring a variable withing the for loop? And how does Python know that number is accessing the values within my_list and multiplying them by 2? Also, how do for loops work with things other than lists because I've looked at other Python code that contains for loops and they make no sense. Could you please find some way to explain the way these are similar to something like C# for loops or just explain Python for loops in general.
Yes, number is a newly defined variable. Python does not require variables to be declared before using them. And the understanding of the loop iteration is correct.
This is the same sytnax Borne-style shells use (such as bash).
The logic of the for loop is this: assign the named variable the next value in the list, iterate, repeat.
correction
As for other non-list values, they should translate into a sequence in python. Try this:
val="1 2 3"
for number in val:
print number
Note this prints "1", " ", "2", " ", "3".
Here's a useful reference: http://www.tutorialspoint.com/python/python_for_loop.htm.
The quick answer, to relate to C#, is that a Python for loop is roughly equivalent to a C# foreach loop. C++ sort of has similar facilities (BOOST_FOREACH for example, or the for syntax in C++11), but C does not have an equivalent.
There is no equivalent in Python of the C-style for (initial; condition; increment) style loop.
Python for loops can iterate over more than just lists; they can iterate over anything that is iterable. See for example What makes something iterable in python.
Python doesn't need variables to be declared it can be declared itself at the time of initialization
While and do while are similar to those languages but for loop is quite different in python
you can use it for list similar to for each
but for another purpose like to run from 1 to 10 you can use,
for number in range(10):
print number
Python for loops should be pretty similar to C# foreach loops. It steps through my_list and at each step you can use "number" to reverence to that element in the list.
If you want to access list indices as well as list elements while you are iterating, the usual idiom is to use g the "enumerate function:
for (i, x) in enumerate(my_list):
print "the", i, "number in the list is", x
The foreach loop should should be similar to the following desugared code:
my_iterator = iter(my_list)
while True:
try:
number = iter.next()
#Your code here
print 2*number
except StopIteration:
break
Pretty similar to this Java loop: Java for loop syntax: "for (T obj : objects)"
In python there's no need to declare variable type, that's why number has no type.
In Python you don't need to declare variables. In this case the number variable is defined by using it in the loop.
As for the loop construct itself, it's similar to the C++11 range-based for loop:
std::vector<int> my_list = { 1, 9, 3, 8, 5, 7 };
for (auto& number : my_list)
std::cout << 2 * number << '\n';
This can of course be implemented pre-C++11 using std::for_each with a suitable functor object (which may of course be a C++11 lambda expression).
Python does not have an equivalent to the normal C-style for loop.
I will try to explain the python for loop to you in much basic way as possible:
Let's say we have a list:
a = [1, 2, 3, 4, 5]
Before we jump into the for loop let me tell you we don't have to initialize the variable type in python while declaring variable.
int a, str a is not required.
Let's go to for loop now.
for i in a:
print 2*i
Now, what does it do?
The loop will start from the first element so,
i is replaced by 1 and it is multiplied by 2 and displayed. After it's done with 1 it will jump to 2.
Regarding your another question:
Python knows its variable type in it's execution:
>>> a = ['a', 'b', 'c']
>>> for i in a:
... print 2*i
...
aa
bb
cc
>>>
Python uses protocols (duck-typing with specially named methods, pre and post-fixed with double underscores). The equivalent in Java would be an interface or an abstract base class.
In this case, anything in Python which implements the iterator protocol can be used in a for loop:
class TheStandardProtocol(object):
def __init__(self):
self.i = 0
def __iter__(self):
return self
def __next__(self):
self.i += 1
if self.i > 15: raise StopIteration()
return self.i
# In Python 2 `next` is the only protocol method without double underscores
next = __next__
class TheListProtocol(object):
"""A less common option, but still valid"""
def __getitem__(self, index):
if index > 15: raise IndexError()
return index
We can then use instances of either class in a for loop and everything will work correctly:
standard = TheStandardProtocol()
for i in standard: # `__iter__` invoked to get the iterator
# `__next__` invoked and its return value bound to `i`
# until the underlying iterator returned by `__iter__`
# raises a StopIteration exception
print i
# prints 1 to 15
list_protocol = TheListProtocol()
for x in list_protocol: # Python creates an iterator for us
# `__getitem__` is invoked with ascending integers
# and the return value bound to `x`
# until the instance raises an IndexError
print x
# prints 0 to 15
The equivalent in Java is the Iterable and Iterator interface:
class MyIterator implements Iterable<Integer>, Iterator<Integer> {
private Integer i = 0;
public Iterator<Integer> iterator() {
return this;
}
public boolean hasNext() {
return i < 16;
}
public Integer next() {
return i++;
}
}
// Elsewhere
MyIterator anIterator = new MyIterator();
for(Integer x: anIterator) {
System.out.println(x.toString());
}