In python, variable definition is attached to the scope that it's in. In this example, x is attached to the scope of foo at the time foo is compiled. Python gives you an UnboundLocalError on line 3 because x is attached to foo's scope but has not been bound yet. The same error can be seen if no global x is present:
>>> def foo():
...     print(x)
...     x = 10
...
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
UnboundLocalError: local variable 'x' referenced before assignment
Exploring further, we can see that when compiling line-by-line (outside of a function, in the interactive REPL), python can't look ahead to see if x will be defined:
>>> print(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
And we get a NameError instead. UnboundLocalError is a subclass of NameError because it indicates the same problem, just with a more helpful name and message.