Asked  6 Months ago    Answers:  5   Viewed   52 times

Following code gives different output in Python2 and in Python3:

from sys import version

print(version)

def execute(a, st):
    b = 42
    exec("b = {}nprint('b:', b)".format(st))
    print(b)
a = 1.
execute(a, "1.E6*a")

Python2 prints:

2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)]
('b:', 1000000.0)
1000000.0

Python3 prints:

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42

Why does Python2 bind the variable b inside the execute function to the values in the string of the exec function, while Python3 doesn't do this? How can I achieve the behavior of Python2 in Python3? I already tried to pass dictionaries for globals and locals to exec function in Python3, but nothing worked so far.

--- EDIT ---

After reading Martijns answer I further analyzed this with Python3. In following example I give the locals() dictionay as d to exec, but d['b'] prints something else than just printing b.

from sys import version

print(version)

def execute(a, st):
    b = 42
    d = locals()
    exec("b = {}nprint('b:', b)".format(st), globals(), d)
    print(b)                     # This prints 42
    print(d['b'])                # This prints 1000000.0
    print(id(d) == id(locals())) # This prints True
a = 1.
execute(a, "1.E6*a")

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42
1000000.0
True

The comparison of the ids of d and locals() shows that they are the same object. But under these conditions b should be the same as d['b']. What is wrong in my example?

 Answers

12

There is a big difference between exec in Python 2 and exec() in Python 3. You are treating exec as a function, but it really is a statement in Python 2.

Because of this difference, you cannot change local variables in function scope in Python 3 using exec, even though it was possible in Python 2. Not even previously declared variables.

locals() only reflects local variables in one direction. The following never worked in either 2 or 3:

def foo():
    a = 'spam'
    locals()['a'] = 'ham'
    print(a)              # prints 'spam'

In Python 2, using the exec statement meant the compiler knew to switch off the local scope optimizations (switching from LOAD_FAST to LOAD_NAME for example, to look up variables in both the local and global scopes). With exec() being a function, that option is no longer available and function scopes are now always optimized.

Moreover, in Python 2, the exec statement explicitly copies all variables found in locals() back to the function locals using PyFrame_LocalsToFast, but only if no globals and locals parameters were supplied.

The proper work-around is to use a new namespace (a dictionary) for your exec() call:

def execute(a, st):
    namespace = {}
    exec("b = {}nprint('b:', b)".format(st), namespace)
    print(namespace['b'])

The exec() documentation is very explicit about this limitation:

Note: The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.

Tuesday, June 1, 2021
 
alioygur
answered 6 Months ago
42

Use dict.setdefault():

dic.setdefault(key,[]).append(value)

help(dict.setdefault):

    setdefault(...)
        D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
Wednesday, June 9, 2021
 
GGio
answered 6 Months ago
18

str.expandtabs(n) is not equivalent to str.replace("t", " " * n).

str.expandtabs(n) keeps track of the current cursor position on each line, and replaces each tab character it finds with the number of spaces from the current cursor position to the next tab stop. The tab stops are taken to be every n characters.

This is fundamental to the way tabs work, and is not specific to Python. See this answer to a related question for a good explanation of tab stops.

string.expandtabs(n) is equivalent to:

def expandtabs(string, n):
    result = ""
    pos = 0
    for char in string:
        if char == "t":
            # instead of the tab character, append the
            # number of spaces to the next tab stop
            char = " " * (n - pos % n)
            pos = 0
        elif char == "n":
            pos = 0
        else:
            pos += 1
        result += char
    return result

And an example of use:

>>> input = "123t12345t1234t1n12t1234t123t1"
>>> print(expandtabs(input, 10))
123       12345     1234      1
12        1234      123       1

Note how each tab character ("t") has been replaced with the number of spaces that causes it to line up with the next tab stop. In this case, there is a tab stop every 10 characters because I supplied n=10.

Thursday, August 5, 2021
 
Connor Johnson
answered 4 Months ago
89

Difference between Python 2 and Python 3 is Python 3 returns an iterators. Idea of this saving memory.

Monday, August 23, 2021
 
test12345
answered 4 Months ago
70

You are right. None of the numbers can be represented exactly. In some cases the fractional part is strictly greater than 0.45 and in some it is strictly less:

In [4]: ['%.20f' % val for val in (0.45, 1.45, 2.45, 3.45, 4.45, 5.45, 6.45, 7.45, 8.45, 9.45)]
Out[4]: 
['0.45000000000000001110',
 '1.44999999999999995559',
 '2.45000000000000017764',
 '3.45000000000000017764',
 '4.45000000000000017764',
 '5.45000000000000017764',
 '6.45000000000000017764',
 '7.45000000000000017764',
 '8.44999999999999928946',
 '9.44999999999999928946']

This explains the seemingly inconsistent rounding.

Friday, October 15, 2021
 
Asnexplore
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share