I'd bet they have very similar performance-metrics, but the yield syntax is more extensible (i.e. you're not limited to one expression) and debug-able (you can put breakpoints within the function).
Also the name and the generator is nicer (for some definition of nice):
>>> def square_vals(x : list):
... return (v * v for v in x)
...
>>> square_vals([1,2,3])
<generator object square_vals.<locals>.<genexpr> at 0x786b8511f5e0>
>>> def square_vals_yields(x: list):
... for v in x:
... yield v * v
...
>>> square_vals_yields([1,2,3])
<generator object square_vals_yields at 0x786b851f5ff0>
I think it's more idiomatic to pass generator-comprehensions into functions rather than return them from functions >>> sum((v*v for v in x))