I'd say that for a "full" evaluation at a list of images V for all variables, a guide would be that f(*V) should be roughly:
sum(c*prod(m^e for e,m in zip(es,V)) for es,c in f.dict().items())
Only roughly, though, because for, for instance, if f==0, then this code evaluates to an empty sum, so we probably get the integer 0 back, which is not particularly desirable.
So, I'd say for a full evaluation, the result should be whatever the coercion system can find to be the "common over-ring" for f.base_ring() and the elements of V. We'd probably want to have some shortcuts in determining this codomain. It also suggests that if you want to do multiple evaluations, building the corresponding homomorphism might be faster, since it avoids a lot of discovery.
I'd say that a partial evaluation such as
R.<x,y>=QQ[]
f=x^2+x*y+y^2
f(x=1)
should correspond to a full evaluation, where the unmentioned variables are taken to be evaluated at themselves, so the result should be
f(1,y)
which means it's f(*V) with V=(1,y). The same codomain determination rules as above apply.
In particular, this means that something like
f(x=1.0)
still works and would give the same result as
f(1.0,y)
It looks like the example you give would then not work anymore, because sage does not create a common parent for QQ['x','y'] and QQ['q']. I think this ends up being a little more permissive rule for (partial) evaluations than you are proposing, but it would be a little more consistent with what we''re doing right now.