OK twitter to the rescue!
Here's my solution:
bpy.context.scene.statistics(bpy.context.view_layer) gets you the same string as shown in the status bar, so to get the triangle count this just needs to be parsed:
s = bpy.context.scene.statistics(bpy.context.view_layer) tris = int(s.split("Tris:")[1].split(' ')[0].replace(',', ''))
This is obviously dependent on the view, so it only includes the visible objects in the current view layer.
If you only want to know the tris of a specific object you can hide everything else or just enter edit mode.