SQL is a serialized protocol. The application sends to the database one single string no matter what API you use.
The only difference between this:
execute('SELECT * FROM Orders WHERE OrderId=%s' % escape(userdata))
and this
execute('SELECT * FROM Orders WHERE OrderId=?', userdata)
is whether the escaping is done explicitly or inside the execute function provided by the driver. The same string is sent to the database.
What matters is that web2py never requires the developer to do escape(...) explicitly because the queries are built programmatically. If one uses the DAL to build queries SQL injections are impossible.
There are two exception. Exception 1) Old versions of postgresql do not handle escaping in a way conform to the SQL standard. For this reason web2py on postgresql web2py sets standard_conforming_strings=on; thus restoring the conforming behavior. Exception 2) when available we use the escape function provided by the driver. If the driver has a bug we have a vulnerability (notice the ? notation would use the very same buggy escape function internally).
Because of caveats like the ones above I trust the way web2py handles escaping better than is it were hidden in the drive (the driver may not know my database settings when handling conforming or non-conforming string escaping). Although I agree that the '?' notation is more aesthetically pleasing and probably we should use it in the future (but not because it adds any security to web2py).
Massimo