So - what happened is that the text that you had had been "double-encoded"as utf-8.
So, at some point in the process that generated the data you had, the text that already had an internal representation of "\xc3\xa9" for "é" was interpreted as being in latin-1, and re-transformed from "latin1" (where the "\xc3\xa9" represents "é") to utf-8, so that ach character was expanded to be in two bytes, becoming: "\xc3\x83" "\xc2\xa9" (the utf-8 for "é"). As @Novoselov puts it in the other answer this corruption likely came out of you opening the file to read as text, without specifying an encoding on Windows: Python will think the file is "latin-1", the default Windows encoding, and therefore read each byte in there, which is part of an- utf-8 text sequence as a single latin-1 character.
What the fix did: your system setup is already configured to read text as utf-8 - so when you got the lines in the for loop you got Python-3 strings (Python-2 unicode) correctly interpreted for the UTF-8 characters on the text file. So the 4 byte sequence became 2 text characters. Now, one characteristic of the "latin1" encoding is that it is "transparent": it is equivalent to perform no transform at all in the text bytes. In other words, each character represented by a value that fits in a single byte in Python's Unicode internal representation becomes a single byte in the encoded byte-string. (And each character whose value does not fit in a byte can't be encoded as Latin-1 at all, yielding an Unicode-Encode error).
So, after the "transparent" encoding step, you have bytes that represent your text - this time with only "one pass" of utf-8 encoding. And decoding these bytes as "utf-8" yielded you the correct text for the file.
Again:
This was the original text:
"cliché". Encoded to UTF-8 it becomes like this:
b'clich\xc3\xa9'
But the original process, that created your file, thought of this sequence as being in latin-1, so reconverted both > 0x80 characters to utf-8:
b'clich\xc3\x83\xc2\xa9'.
And this is what prints as "cliché"
On reading, Python3 reads:
b'clich\xc3\x83\xc2\xa9' from the disk, and returns to you "cliché" as (unicode) text.
You encode this to bytes, and gets b'clich\xc3\xa9' with the call to "encode('latin-1'). Finally you then "decode" that from "utf-8" getting the text "cliché".
Python3 does not easily allow one to spoil text like this. To go from the text to the incorrect version you had, one has also to use the "transparent" encoding "latin-1" - this is an example:
In [10]: a = "cliché"
In [11]: b = a.encode("utf-8")
In [12]: b
Out[12]: b'clich\xc3\xa9'
In [13]: c = b.decode("latin1").encode("utf-8")
In [14]: c
Out[14]: b'clich\xc3\x83\xc2\xa9'