This gets rid of temporary arrays, but this still isn't optimal if z is large. Memory locality means it's faster to apply a scalar operation like z
2+c in a single pass, rather than in two separate passes.
Explicitly unrolling loopy code (e.g., in pypy or Numba) is one easy way to achieve this, but you have to write more code.
Julia has some really nice syntax that lets you write things in this clean vectorized way but still get efficient code:
https://julialang.org/blog/2017/01/moredots/