78

I am using the PyQt library to take a screenshot of a webpage, then reading through a CSV file of different URLs. I am keeping a variable feed that incremements everytime a URL is processed and therefore should increment to the number of URLs.

Here's code:

webpage = QWebPage()
fo = open("C:/Users/Romi/Desktop/result1.txt", "w")
feed = 0
def onLoadFinished(result):
    #fo.write( column1[feed])#, column2[feed], urls[feed])
   #feed = 0
   if not result:
        print "Request failed"
    fo.write(column1[feed])
    fo.write(',')
    fo.write(column2[feed])
    fo.write(',')
    #fo.write(urls[feed])
    fo.write(',')
    fo.write('404,image not created\n')
    feed = feed + 1
        sys.exit(1)
        save_page(webpage, outputs.pop(0))   # pop output name from list and save
   if urls:
        url = urls.pop(0)   # pop next url to fetch from list
        webpage.mainFrame().load(QUrl(url))
    fo.write(column1[feed])#,column2[feed],urls[feed],'200','image created','/n')
    fo.write(',')
    fo.write(column2[feed])
    fo.write(',')
    #fo.write(urls[feed])
    fo.write(',')
    fo.write('200,image created\n')
    feed = feed + 1
   else:
        app.quit()  # exit after last url

webpage.connect(webpage, SIGNAL("loadFinished(bool)"), onLoadFinished)
webpage.mainFrame().load(QUrl(urls.pop(0)))
#fo.close()
sys.exit(app.exec_())

It gives me the error:

local variable feed referenced before the assignment at fo.write(column1[feed])#,column2[feed],urls[feed],'200','image created','/n')

Any idea why?

martineau
  • 119,623
  • 25
  • 170
  • 301
Scooby
  • 3,371
  • 8
  • 44
  • 84

5 Answers5

124

When Python parses the body of a function definition and encounters an assignment such as

feed = ...

Python interprets feed as a local variable by default. If you do not wish for it to be a local variable, you must put

global feed

in the function definition. The global statement does not have to be at the beginning of the function definition, but that is where it is usually placed. Wherever it is placed, the global declaration makes feed a global variable everywhere in the function.

Without the global statement, since feed is taken to be a local variable, when Python executes

feed = feed + 1,

Python evaluates the right-hand side first and tries to look up the value of feed. The first time through it finds feed is undefined. Hence the error.

The shortest way to patch up the code is to add global feed to the beginning of onLoadFinished. The nicer way is to use a class:

class Page(object):
    def __init__(self):
        self.feed = 0
    def onLoadFinished(self, result):
        ...
        self.feed += 1

The problem with having functions which mutate global variables is that it makes it harder to grok your code. Functions are no longer isolated units. Their interaction extends to everything that affects or is affected by the global variable. Thus it makes larger programs harder to understand.

By avoiding mutating globals, in the long run your code will be easier to understand, test and maintain.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 1
    how come you don't need to do this for dict? – HoKy22 Sep 12 '17 at 02:19
  • @HoKy22: Are you asking why `dct[key] = val` does not raise a "local variable referenced before assignment" error? The reason is that this is not a *bare name* assignment. Instead, it causes Python to make the function call `dct.__setitem__(key, val)`. – unutbu Sep 12 '17 at 10:19
  • yes, that's what i am asking. for example, in a file, all the way in the top, you just put dict = {} and then in a method, you set dict['apple'] = 5 and in a different method you can print(dict['apple']). so this is because dict call __setitem__('apple', 5) when you do dict['apple'] = 5? – HoKy22 Sep 12 '17 at 13:10
  • 2
    When Python encounters `dct['apple'] = 5` in a function, it does not see this as an assignment to `dct`. It translates it into the function call `dct.__setitem__('apple', 5)`. (I'm trying to avoid calling the variable `dict` since that shadows the builtin of the same name). Assuming there is no assignment to `dct`, `dct` is not a local variable of the function. So according to the [LEGB scoping rules](https://stackoverflow.com/a/292502/190597) Python will find `dct` in the global namespace since `dct = {}` was defined at the top of the script. Hence, no error is raised. – unutbu Sep 12 '17 at 16:09
  • 1
    You might also find Ned Batchelder's essay on [Facts and myths about Python names and values](https://nedbatchelder.com/text/names.html) to be helpful. It will clarify the mental model you need when thinking about how Python treats bare variable names and their relationship to values. – unutbu Sep 12 '17 at 16:15
  • You also don't need to do this for list.append(). As others said, anything that doesn't run assignment under the hood is fine. – alant Jun 27 '19 at 21:10
  • The class thing is a *convenient* workaround, woah... – Peter Nielsen Jul 15 '22 at 16:05
46

Put a global statement at the top of your function and you should be good:

def onLoadFinished(result):
    global feed
    ...

To demonstrate what I mean, look at this little test:

x = 0
def t():
    x += 1
t()

this blows up with your exact same error where as:

x = 0
def t():
    global x
    x += 1
t()

does not.

The reason for this is that, inside t, Python thinks that x is a local variable. Furthermore, unless you explicitly tell it that x is global, it will try to use a local variable named x in x += 1. But, since there is no x defined in the local scope of t, it throws an error.

12

As the Python interpreter reads the definition of a function (or, I think, even a block of indented code), all variables that are assigned to inside the function are added to the locals for that function. If a local does not have a definition before an assignment, the Python interpreter does not know what to do, so it throws this error.

The solution here is to add

global feed

to your function (usually near the top) to indicate to the interpreter that the feed variable is not local to this function.

lmjohns3
  • 7,422
  • 5
  • 36
  • 56
2

in my case that exact same error was triggered by a typo ! I thought my my var name was

varAlpha

but in the code i had defined

varalpha

& got the error

UnboundLocalError: local variable 'varAlpha' referenced before assignment

when calling varAlpha

I hope it helps somebody one day searching for that error & wondering (as my search for that error led me here while being unrelated with the use of global or not global which was a head scratcher !)

mikakun
  • 2,203
  • 2
  • 16
  • 24
0

You can do like this for the function scope

def main()

  self.x = 0

  def incr():
    self.x += 1
  
  for i in range(5):
     incr()
  
  print(self.x)
Henadzi Rabkin
  • 6,834
  • 3
  • 32
  • 39