Why do I need to use latin-1 instead of utf-8 when using python with arduino?
but is there something about arduino that requires using latin-1?

No, not really.
What it comes down to is that Serial.read() reads bytes, irrespective of whatever encoding they may be being used with.  ISO-8859 only encodes character code points in the 0-255 range, so when you choose a 0-255 code point and send it as ISO-8859, it gets sent as 1 byte, which is how you're code has been written to receive it.
A 255 code point as utf-8 would require multiple bytes to encode and so would result in multiple Serial.read() calls for a given value.
If you open a python3 REPL and put chr(255).encode() or more explicitly chr(255).encode('utf-8') you will see it results in b'\xc3\xbf'.  So on the Arduino side you will see this as two separate Serial.read() results, 0xC3 and 0xBF.
When you're just taking in strings and UTF-8 and splatting them out via Serial.println(), the Arduino is blissfully unaware of that they're UTF-8 and not ISO-8859.  Really you could use any encoding you wanted, the the caveat being that if you're using c-strings to store them, the encoding would need to be one that doens't allow for a single null byte to be in the middle of the string.

You may want to look into using struct.pack rather than cobbling together python bytestrings with sequences of chr() and .encode(whatever). struct.pack will also result in a bytestring, but it's more purpose built for doing this.
You can import struct and then struct.pack('B', 255) will result in b'\xff same as chr(255).encode('iso8859-1'), but at least expresses intent.  'B' here signifies an unsigned char range, you'll find the format specifiers in the documentation. You'll get a greater benefit when you begin using multiple fields where struct.pack will be a lot less unwieldy than the chr and encode and string concatenation.