x = 1
def fn():
    print x
fn()
This prints "1":
x = 1
def fn():
    x += 1
    print x
fn()
This raises "UnboundLocalError: local variable 'x' referenced before assignment"
What is going on here?
x = 1
def fn():
    print x
fn()
This prints "1":
x = 1
def fn():
    x += 1
    print x
fn()
This raises "UnboundLocalError: local variable 'x' referenced before assignment"
What is going on here?
 
    
    In Python, assigning to a variable is also an implicit local declaration, that is resolved during the bytecode compilation. So
x += 1
will create a local variable x and compile to this byte code: 
0 LOAD_FAST                0 (x)
3 LOAD_CONST               1 (1)
6 INPLACE_ADD
7 STORE_FAST               0 (x)
The command LOAD_FAST will try to load a local variable x which is not yet defined, that's why it fails.
However, if you define x as global explicitly, then it will use LOAD_GLOBAL/STORE_GLOBAL instead.
In the case of print in your first function, the compiler assumes that since no local variable is declared (assigned) ever in the function body, you should mean a global variable.
 
    
    The act of assigning to a name, anywhere inside a function, makes that name local only.
 
    
    You're using a local variable , a different binding,  not the global one.
A scope defines the visibility of a name within a block. If a local variable is defined in a block, its scope includes that block. If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, unless a contained block introduces a different binding for the name
 
    
    Because you have to add global x to the function:
def fn():
    global x
    x += 1
    print x
This tells the interpreter that you intend to modify a global variable. This is not necessary for certain objects like mutable sequences (e.g. list).
 
    
    