3

I'm rather new to python and I'm wondering how local variables work. Let's start with an example of a simple method:

def do_sth():
    local_dict = { 'a': 1, 'b': 2, 'c': 3, ... }
    ...

Let's assume that local_dict is used like a sort of const variable. And here's the question: it is created every time when do_sth() is invoked or it is created once and is kept somewhere in do_sth() internals?

  • In general, things that aren't changed are optimized to only be initialized once in most languages unless you direct the compiler to do otherwise (where you are able to). – Dustin Oprea May 11 '16 at 20:19
  • In Python, in general, nothing is ever optimized by the interpreter ;-) – thebjorn May 11 '16 at 20:21
  • http://stackoverflow.com/questions/12590058/python-performance-with-global-variables-vs-local gives you some answers – DevLounge May 11 '16 at 20:39

5 Answers5

7

You can look at what the interpreter does using the dis module:

def do_sth():
    d = {'a':2, 'b':3}
    print(id(d))


import dis
dis.dis(do_sth)

will print

  2           0 BUILD_MAP                2
              3 LOAD_CONST               1 (2)
              6 LOAD_CONST               2 ('a')
              9 STORE_MAP           
             10 LOAD_CONST               3 (3)
             13 LOAD_CONST               4 ('b')
             16 STORE_MAP           
             17 STORE_FAST               0 (d)

  3          20 LOAD_GLOBAL              0 (id)
             23 LOAD_FAST                0 (d)
             26 CALL_FUNCTION            1
             29 PRINT_ITEM          
             30 PRINT_NEWLINE       
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE        

which shows that the interpreter is rebuilding the value every time the function is called.

thebjorn
  • 26,297
  • 11
  • 96
  • 138
3

Every time do_sth() is invoked. This is important because it allows you to make modifications to local_dict inside the function and it won't have any effect on other calls. The interpreter is not smart enough to see if you won't change it, especially because Python is so dynamic that there are some very roundabout ways you can cause it to change.

Here's how you can prove to yourself that the dictionary keeps getting recreated:

def print_3():
    print(3)

def do_sth():
    local_dict = {'a': print_3()}

do_sth()
do_sth()
do_sth()

This prints 3 ... 3 times.

I think global variables are fine for optimising this, but if you really want, how about this:

def do_sth():
    return do_sth.local_dict

do_sth.local_dict = {'a': print_3()}

Technically this can still be accessed by everyone but it's clearer what it belongs to.

Alex Hall
  • 34,833
  • 5
  • 57
  • 89
  • I really hope you plan on expanding this answer. – Tadhg McDonald-Jensen May 11 '16 at 20:17
  • 2
    @TadhgMcDonald-Jensen I just did but there's really not that much to say. It's a very simple question. – Alex Hall May 11 '16 at 20:18
  • I liked it better without the second sentence. Perhaps a "It's created .." before the first sentence to give the sentence a subject..? – thebjorn May 11 '16 at 20:20
  • 3
    You guys are overthinking this. – Alex Hall May 11 '16 at 20:23
  • It took me a second read to figure out why this answer does answer the question. @AlexHall you mention modification but you're not really modifying anything (so it's confusing). The fact that it `print`s every time you call the function shows that it's creating the `dict` anew every time, that's just not clear from your wording around it. – dwanderson May 11 '16 at 20:26
  • @dwanderson I'm very sorry for your second. – Alex Hall May 11 '16 at 20:29
  • No need for condescending sarcasm - people are saying your answer, while correct, isn't clear. Part of the point of these answers is to be as clear as possible. – dwanderson May 11 '16 at 20:35
  • @dwanderson I think it was just good-natured sarcasm (at least that's how I read it). – thebjorn May 11 '16 at 20:41
  • @dwanderson I meant no harm. I did edit the question after your comment, even before my reply. – Alex Hall May 11 '16 at 20:42
  • Thank you for your answer. Could you share with me any tips on where I should place such "const" (hardcoded) dict to avoid creating it every time? Placing it as a global variable doesn't sound good to me because it's used in only one method. – m3diumendi4n May 11 '16 at 20:43
  • 2
    @thebjorn indeed, it was just a little fun. I cannot believe how much thought everyone has put into this. – Alex Hall May 11 '16 at 20:44
  • 1
    @m3diumendi4n just keep it where you have it (these are not the kinds of optimizations you should worry about in Python). – thebjorn May 11 '16 at 20:46
  • @m3diumendi4n Also thebjorn is right, don't over-optimise. – Alex Hall May 11 '16 at 20:50
1

Local variable are always created in the scope of a function. There are collected by the garbage collector once the program counter quits function (scope of the local variable).

global_dict = []

def do_sth():
    local_dict = { 'a': 1, 'b': 2, 'c': 3, ... }
    ...

def do_other_task():
   #local_dict not visible. It's outside of scope. 
   global global_dict # fetch memory address of previously declared global_dict
   global_dict = { 'a': 1, 'b': 2, 'c': 3, ... }
LAL
  • 480
  • 5
  • 13
1

it would be easy to check with the is operator:

def test():
    return {'a': 1, 'b': 2, 'c': 3}

>>> test() is test() #did both produce the same object in memory space?
False

This makes a lot of sense for mutable objects or the mutable default argument trap would come up everywhere.

However there are values that are stored as constants and those can be seen using dis as constants are loaded with the LOAD_CONST byte code:

>>> dis.dis(lambda:1)
  1           0 LOAD_CONST               1 (1)
              3 RETURN_VALUE
>>> dis.dis(lambda:(1,True, 10*1000, "a"))
  1           0 LOAD_CONST               7 ((1, True, 10000, 'a'))
              3 RETURN_VALUE
>>> dis.dis(lambda:[1,2,3]) #list is mutable
  1           0 LOAD_CONST               1 (1)
              3 LOAD_CONST               2 (2)
              6 LOAD_CONST               3 (3)
              9 BUILD_LIST               3
             12 RETURN_VALUE
>>> dis.dis(lambda:{"a":1}) #dict is also mutable
  1           0 LOAD_CONST               1 ('a')
              3 LOAD_CONST               2 (1)
              6 BUILD_MAP                1
              9 RETURN_VALUE
Community
  • 1
  • 1
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
0

The other answers are correct, I would just add this.

I would rather do, in such case, either:

constant_dict = { 'a': 1, 'b': 2, 'c': 3, ... }
def do_sth():
    # do something reading constant_dict

do_sth()

or but less preferable:

def do_sth(local_dict = { 'a': 1, 'b': 2, 'c': 3, ... }):
    # do something reading local_dict

do_sth()

In both cases, python would not have to reallocate the memory for the variable, because it got declared and allocated when the interpreter reads the python file.

Correct me if I'm wrong.

DevLounge
  • 8,313
  • 3
  • 31
  • 44
  • you're wrong ;-) don't use dicts as default arguments (at least not without a comment). Python is also exceedingly fast at dicts, and comparably slow at using global variables, so I'm unsure if you would be saving any time... – thebjorn May 11 '16 at 20:33
  • I know that using dicts or lists as default args can be dangerous, and indeed do_smth() would have to be always called without args ;-) – DevLounge May 11 '16 at 20:36
  • It's not so much that it is dangerous, but the semantics are generally surprising to anyone who's used default arguments anywhere else. If the comment doesn't say something like "...is used to memoize.." most Python programmers would be concerned. – thebjorn May 11 '16 at 20:39
  • Yeah, I also would be concerned but I saw this many times in some famous third-party modules source code. Especially for memoizing a tree level during recursion, or iteration numbers, things like that. – DevLounge May 11 '16 at 20:43
  • 1
    It's the right thing to do when using it for memoization -- but use care with multiple threads, and again, with a comment, to let the next person know that you understood what you were doing. – thebjorn May 11 '16 at 20:50