A bit late to the party, but hopefully not too late to be helpful. I don't know a whole lot about customizing Chameleon like this, but I'll do my best to answer.
As you may already know, Chameleon renders each template into Python bytecode. So what your function is doing is generating an AST which is compiled into the template bytecode.
Let's say provider.render() returns "foo". The first time the template is called, your expression will end up as bytecode equivalent to "target = 'foo'" The next time the template is called, the previously generated bytecode is run. So no matter what "provider.render()" might return this time, all the template is doing is running "target = 'foo'".
What you need to do is break off the code to grab the provider and render it into its own function, then have your ProviderExpression returning the AST for running that function, i.e. the equivalent of "target = render_provider()". I have limited experience with Python's AST, so unfortunately I can't advise you on how to achieve that.
Anyways, I'm not quite sure what your use case is, but extending Chameleon seems a bit extreme. In my opinion, a simple Python function (executed via tal:condition="myfunc()") or Chameleon's macros would work best for most situations.