update_st is a closure, capturing the variable st. I think the same thing should work in Python, too, it’s just that the shadowing rules are different so you’d have to do it in multiple statements.
With the current Julia implementation, such closures are not implemented as efficiently as might be expected (see Performance tips in the Manual), so when you apply some workarounds, like type annotations, the more efficient version of your code would end up longer, too.
Recently I was comparing different possible ways of implementing something like your example function, so here you go:
f0(n) =
function()
n += 1
n
end
f1(n) =
let n = n
function()
n += 1
n
end
end
f2(n) =
let n::Int = n
function()
n += 1
n
end
end
incremented(n) = n + one(typeof(n))
f3(n) =
let n::Int = n
function()
n = incremented(n)
n
end
end
f4(n) =
let n::Int = n
function()
let m = incremented(n)
n = m
m
end
end
end
f5(n) =
let n::Int = n
function()
let m::Int = incremented(n)
n = m
m
end
end
end
# Same as f5, but with overriden effects analysis.
f6(n) =
let n::Int = n
Base.@assume_effects :nothrow :terminates_locally function()
let m::Int = incremented(n)
n = m
m
end
end
end
f7(n) =
let n::Int = n
Base.@assume_effects :nothrow :terminates_globally function()
let m::Int = incremented(n)
n = m
m
end
end
end
The conclusion was that f0 and f1 are slow, and the other forms, from f2 on, should mostly be equally fast.