UTF-8 может использовать более одного байта для кодирования символа, поэтому итерация по байтовой строке и манипулирование отдельными байтами не всегда будут работать.Лучше декодировать в Python 2 unicode type.Выполните ваши манипуляции, а затем перекодируйте в UTF-8.
>>> dobry="costąm"
>>> udobry = unicode(dobry, 'utf-8')
>>> changed = udobry[:4] + udobry[4].upper() + udobry[5]
>>> new_dobry = changed.encode('utf-8')
>>> print new_dobry
costĄm
Как прокомментировал @tripleee, не-ascii символы могут не отображаться в одну кодовую точку Юникода: «ą» может быть единственной кодовой точкой U +0105 МАЛЕНЬКОЕ ЛАТИНСКОЕ ПИСЬМО А С ОГОНЕКОМ или оно может быть составлено из «а», за которым следует U + 0328 КОМБИНИРОВАНИЕ ОГОНЕКА.
В составленной строке символ «a» может быть заглавным, а «a», за которым следует КОМБИНИРОВАНИЕ OGONEK, приведет к «Ą» (хотя это может выглядеть как два отдельных символа в Python REPL или терминале(в зависимости от настроек терминала).
Обратите внимание, что при индексации необходимо учитывать дополнительный символ.
Также возможно нормализовать составленную строку вверсия с одним кодом ( canonical ) с использованием инструментов модуля unicodedata:
>>> unicodedata.normalize('NFC', u'costa\u0328m') == u"costąm"
True
, но это может вызвать проблемы, если, например, вы возвращаете измененную строку в систему, которая ожидаетобъединяющий символ, подлежащий сохранению.