I am not understandinhg $wpdb->prepare correctly

Ok, so there is one major problem with your code and it has nothing to do with escaping LIKE statements in SQL. But let me start from that…

There is nothing wrong with your escaping. You should do it exactly like that:

global $wpdb;
// Create a SQL statement with placeholders for the string input.
$sql3 = "SELECT id, post_date, post_title, guid FROM $wpdb->posts where 
post_type="attachment" and post_mime_type like %s;";
// Prepare the SQL statement so the string input gets escaped for security.
$sql3 = $wpdb->prepare( $sql3, $wpdb->esc_like('image').'%' );
echo $sql3;
$result3 = $wpdb->get_results( $sql3, OBJECT );

Although there is no point in escaping this time – you know exactly what string is passed in there and that it is secure.

So why $wpdb generates such strange SQL?

It’s a fix for that vulnerability: https://blog.ircmaxell.com/2017/10/disclosure-wordpress-wpdb-sql-injection-technical.html

This behavior was introduced in WP 4.8.3 and it’s making “double-preparing” sql-injection-safe…

So why is your code working incorrect?

$wpdb->get_results( $sql3, OBJECT );

is returning an array of objects (one for each selected row). It’s an array, so you can’t call ->num_rows on it…

If you want to count the rows, you can use:

count( $result3 );

or

$wpdb->num_rows;