54

when I try to concatenate this, I get the UnicodeDecodeError when the field contains 'ñ' or '´'. If the field that contains the 'ñ' or '´' is the last I get no error.

#...

nombre = fabrica
nombre = nombre.encode("utf-8") + '-' + sector.encode("utf-8")
nombre = nombre.encode("utf-8") + '-' + unidad.encode("utf-8")

#...

return nombre 

any idea? Many thanks!

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Capens
  • 559
  • 1
  • 4
  • 6
  • Possible duplicate of [Python - 'ascii' codec can't decode byte](http://stackoverflow.com/questions/9644099/python-ascii-codec-cant-decode-byte) – bummi Jan 04 '16 at 13:11

3 Answers3

64

You are encoding to UTF-8, then re-encoding to UTF-8. Python can only do this if it first decodes again to Unicode, but it has to use the default ASCII codec:

>>> u'ñ'
u'\xf1'
>>> u'ñ'.encode('utf8')
'\xc3\xb1'
>>> u'ñ'.encode('utf8').encode('utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)

Don't keep encoding; leave encoding to UTF-8 to the last possible moment instead. Concatenate Unicode values instead.

You can use str.join() (or, rather, unicode.join()) here to concatenate the three values with dashes in between:

nombre = u'-'.join(fabrica, sector, unidad)
return nombre.encode('utf-8')

but even encoding here might be too early.

Rule of thumb: decode the moment you receive the value (if not Unicode values supplied by an API already), encode only when you have to (if the destination API does not handle Unicode values directly).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    I think the rule of thumb here is the key point. It could be worded - "encode and decode only on API boundaries, and then only if you have to". – Michael Anderson Oct 05 '16 at 06:37
  • Thanks for this answer. Was banging my head against what I thought was a "simple" convert... and your note about double encoding was spot on. – Wing Tang Wong Apr 03 '18 at 17:58
14

When you get a UnicodeEncodeError, it means that somewhere in your code you convert directly a byte string to a unicode one. By default in Python 2 it uses ascii encoding, and utf8 encoding in Python3 (both may fail because not every byte is valid in either encoding)

To avoid that, you must use explicit decoding.

If you may have 2 different encoding in your input file, one of them accepts any byte (say UTF8 and Latin1), you can try to first convert a string with first and use the second one if a UnicodeDecodeError occurs.

def robust_decode(bs):
    '''Takes a byte string as param and convert it into a unicode one.
First tries UTF8, and fallback to Latin1 if it fails'''
    cr = None
    try:
        cr = bs.decode('utf8')
    except UnicodeDecodeError:
        cr = bs.decode('latin1')
    return cr

If you do not know original encoding and do not care for non ascii character, you can set the optional errors parameter of the decode method to replace. Any offending byte will be replaced (from the standard library documentation):

Replace with a suitable replacement character; Python will use the official U+FFFD REPLACEMENT CHARACTER for the built-in Unicode codecs on decoding and ‘?’ on encoding.

bs.decode(errors='replace')
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • This is not directly an answer to present question but to [that one](http://stackoverflow.com/q/42410322/3545273) that was closed as a duplicate. At least it is related because of a UnicodeDecodeError and users searching for the error will find this answer... – Serge Ballesta Feb 23 '17 at 09:00
-9

I was getting this error when executing in python3,I got the same program working by simply executing in python2

Jose Kj
  • 2,912
  • 2
  • 28
  • 40