Você está aqui: Início Mergulhar Em Python 3

Nível de Dificuldade: ♦♦♦♢♢

Strings

I’m telling you this ’cause you’re one of my friends.
My alphabet starts where your alphabet ends!
— Dr. Seuss, On Beyond Zebra!

 

Coisas Aborrecidas Que É Preciso Saber Antes De Poder Mergulhar

Poucas pessoas pensam nisto, mas texto é incrivelmente complicado. Começando pelo alfabeto. As pessoas de Bougainville têm o alfabeto mais pequeno do mundo; o seu alfabeto Rotokas é composto por apenas 12 letras: A, E, G, I, K, O, P, R, S, T, U, e V. No outro lado do espectro, línguas como o Chinês, Japonês, e Coreano têm milhares de caracteres. O Inglês, claro, tem 26 letras — 52 se contar maiúsculas e minúsculas separadamente — mais uma mão cheia de sinais de pontuação !@#$%&.

Quando fala de “texto,” está provavelmente a pensar em “caracteres e símbolos no ecrã do meu computador.” Mas os computadores não trabalham em caracteres e símbolos; eles trabalham em bits e bytes. Qualquer bocado de texto que já se tenha visto no ecrã de um computador está na realidade armazenado numa certa codificação de caracteres. Falando informalmente, a codificação de caracteres fornece um mapeamento entre o que nós vemos nos nossos ecrãs e o que os nossos computadores realmente armazenam em memória ou em disco. Há muitas codificações de caracteres diferentes, algumas optimizadas para determinadas línguas como Russo ou Chinês ou Inglês, e outras que podem ser usadas para várias línguas.

Na realidade, é mais complicado que isso. Muitos caracteres são comuns a várias codificações, mas cada codificação pode usar uma sequência de bytes diferente para realmente armazenar esses caracteres em memória ou em disco. Podemos então pensar na codificação de caracteres como uma espécie de chave de desencriptação. Quando alguém nos dá uma sequência de bytes — um ficheiro, uma página web, qualquer coisa — e afirma que é “texto,” nós precisamos de saber que codificação de caracteres eles usaram para que possamos descodificar os bytes para caracteres. Se nos derem a chave errada ou nenhuma chave, ficamos com a desagradável tarefa de quebrar o código. O mais provável será errarmos, e o resultado ser lixo.

Certamente já viu páginas web como esta, com uns caracteres estranhos parecidos com pontos de interrogação onde deveriam estar apóstrofes. Isso geralmente significa que o autor da página não declarou a sua condificação de caracteres correctamente, o seu browser teve que adivinhar, e o resultado foi uma mistura de caracteres esperados e inesperados. Em Inglês é apenas incómodo; em outras línguas, o resultado pode ser completamente ilegível.

Há codificações de caracteres para cada língua principal no mundo. Uma vez que cada língua é diferente, e espaço em memória e em disco tem sido caro historicamente, cada codificação de caracteres é optimizada para uma língua em particular. Com isto, eu quero dizer que cada codificação usa os mesmos números (0–255) para representar os caracteres dessa língua. Por exemplo, provavelmente já deve estar familiarizado com a codificação ASCII, que armazena caracteres ingleses como números que variam de 0 a 127. (65 é um “A” maiúsculo, 97 é um “a” minúsculo, etc.) O Inglês tem um alfabeto muito simples, por isso pode ser completamente expresso em menos de 128 números. Para os que conseguem contar em base 2, isso é 7 em 8 bits num byte.

As línguas da Europa Ocidental como o Francês, o Espanhol, e o Alemão têm mais letras que o Inglês. Ou, mais precisamente, eles têm letras combinadas com vários sinais diacríticos, como o caracter ñ em Espanhol. A codificação mais comum para estas línguas é CP-1252, também chamada “windows-1252” porque foi amplamente usada em Microsoft Windows. A codificação CP-1252 partilha caracteres com ASCII no intervalo 0–127, mas depois prolonga-se no intervalo 128–255 para caracteres como o n-com-um-til-por-cima (241), o u-com-um-trema-por-cima (252), etc. Mas continua a ser uma codificação de um só byte; o maior número possível, 255, continua a caber num byte.

Depois há línguas como o Chinês, o Japonês, e o Coreano, que têm tantos caracteres que requerem conjuntos de caracteres de múltiplos bytes. Isto é, cada “caracter” é representado por um número de dois bytes de 0–65535. Mas diferentes codificações de múltiplos bytes também partilham o mesmo problema que diferentes codificações de um só byte, nomeadamente todas usam os mesmos números com significados diferentes. Simplesmente o intervalo de números é mais amplo, porque há muitos mais caracteres a representar.

Isto era bastante aceitável num mundo não interligado, onde “texto” era qualquer coisa que nós escreviamos e ocasionalmente imprimiamos. Não havia muito “texto simples”. Código fonte era ASCII, e todas as outras pessoas usavam processadores de texto, que definiam os seus próprios formatos (não textuais) que geriam a informação da codificação de caracteres juntamente com a formatação rica, etc. As pessoas lêem estes documentos com o mesmo programa de processamento de texto que o autor original, portanto tudo funcionava, mais coisa ou menos coisa.

Agora pensemos no aumento das redes globais como o email e a web. Montes de “texto simples” a voar pelo mundo, sendo criados num computador, transmitidos por um segundo computador, e recebidos e apresentados por um terceiro computador. Os computadores só conseguem ver números, mas os números podem significar coisas diferentes. Oh não! O que fazemos? Bem, sistemas tiveram que ser desenhados para transportar a informação da codificação juntamente com cada pedaço de “texto simples.” Recorda-se que é a chave de desencriptação que mapeia os números lidos pelo computador para os caracteres lidos pelos humanos. Uma chave de desencriptação em falta significa texto misturado, lixo, ou pior.

Agora pensemos em tentar armazenar vários bocados de texto no mesmo sítio, por exemplo na mesma tabela de uma base de dados que contém todos os emails que alguma vez recebeu. Ainda precisa de armazenar a codificação de caracteres ao lado de cada bocado de texto para o poder apresentar correctamente. Parece difícil? Experimente pesquisar a base de dados do seu email, o que significa conversões entre várias codificações na hora. Não parece divertido?

Agora pensemos na possibilidade de documentos com várias línguas, onde os caracteres de diferentes línguas estão seguidos uns dos outros no mesmo documento. (Sugestão: programas que tentavam fazer isto tipicamente usavam códigos de escape para trocar “modos.” Puff, estamos no modo koi8-r Russo, logo 241 corresponde a Я; puff, agora estamos no modo Mac Greek, logo 241 corresponde a ώ.) E claro vamos querer pesquisar nesses documentos, também.

Agora chore bastante, porque tudo o que pensava saber sobre strings está errado, e “texto simples” não existe.

Unicode

Inserir Unicode.

Unicode é um sistema desenhado para representar todos os caracteres de todas as línguas. Unicode representa cada letra, caracter, ou ideograma como um número de 4 bytes. Cada número representa um caracter único usado em pelo menos uma das línguas do mundo. (Nem todos os números são usados, mas mais de 65535 são, portanto 2 bytes não seria suficiente.) Os caracteres que são usados em várias línguas geralmente têm o mesmo número, a não ser que haja uma boa razão etimológica para isto não acontecer. Independentemente disso, há exactamente 1 número por caracter, e exactamente 1 caracter por número. Cada número significa sempre apenas uma coisa; não há “modos” para ir acompanhando. U+0041 é sempre 'A', mesmo que a nossa língua não tenha um 'A'.

À primeira vista, isto parece uma boa ideia. Uma codificação para dominar tudo. Várias línguas por documento. Sem “trocas de modos” para alternar entre codificações a meio do texto. Mas imediatamente, a questão óbvia deveria saltar à vita. Quatro bytes? Por cada caracter Isso parece um desperdício terrível, especialmente em línguas como o Inglês e o Espanhol, que precisam de menos de um byte (256 números) para representar cada caracter possível. Na verdade, é um desperdício mesmo em línguas baseadas em ideogramas (como o Chinês), que nunca precisam de mais de dois bytes por caracter.

Há uma codificação Unicode que usa quatro bytes por caracter. É chamada de UTF-32, porque 32 bits = 4 bytes. UTF-32 é uma codificação directa; pega em cada caracter Unicode (um número de 4 bytes) e representa o caracter com esse mesmo número. Isto tem algumas vantagens, sendo a mais importante poder-se encontrar o N-ésimo caracter de uma string em tempo constante, porque o N-ésimo caracter começa no 4×N-ésimo byte. Isto também tem várias desvantagens, sendo a mais óbvia que leva quatro malditos bytes para guardar cada maldito caracter.

Apesar de haverem muitos caracteres Unicode, parece que maioria das pessoas nunca vai usar o que quer que seja para além dos primeiros 65535. Logo, há outra codificação Unicode, chamada UTF-16 (porque 16 bits = 2 bytes). UTF-16 codifica cada caracter de 0–65535 como dois bytes, depois usa alguns “hacks” manhosos se realmente for preciso representar os raramente usados caracteres Unicode do “plano astral” para além de 65535. Vantagem mais óbvia: UTF-16 é duas vezes mais eficiente em espaço que UTF-32, porque cada caracter requer apenas dois bytes para guardar em vez de quatro bytes (excepto os que não requerem). E continua a ser fácil encontrar o N-ésimo caracter de uma string em tempo constante, se se assumir que a string não inclui caracteres do plano astral, o que é uma boa assunção até ao momento em que deixa de ser.

Mas também há desvantagens pouco óbvias para ambos o UTF-32 e o UTF-16. Diferentes sistemas de computadores armazenam bytes individuais de diferentes maneiras. Isso significa que o caracter U+4E2D pode ser armazenado em UTF-16 como 4E 2D ou 2D 4E, dependendo se o sistema é “big-endian” ou “little-endian”. (Para UTF-32, há ainda mais ordenações de bytes possíveis.) Desde que os nossos documentos nunca deixem o nosso computador, estamos safos — aplicações diferentes no mesmo computador vão todas usar a mesma ordem de bytes. Mas no minuto em que quisermos transferir documentos entre sistemas, talvez em algum tipo de rede a nível mundial, nós vamos precisar de uma forma de indicar qual a ordem pela qual os bytes estão guardados. Caso contrário, o receptor não tem como saber se a sequência de dois bytes 4E 2D significa U+4E2D ou U+2D4E.

Para resolver este problema, as codificações Unicode de vários bytes definem uma “Byte Order Mark,” (Marca de Ordem de Byte) que é um caracter especial não-imprimível que podemos incluir no início do nosso documento para indicar qual a ordem a que os nossos bytes estão. Para UTF-16, a Byte Order Mark é U+FEFF. Se recebermos um documento UTF-16 que começa com os bytes FF FE, sabemos que a ordenação dos bytes é de uma maneira; se começa com FE FF, sabemos que a ordenação dos bytes é ao contrário.

Ainda assim, UTF-16 não é exactamente ideal, especialmente se estivermos a lidar com muitos caracteres ASCII. Se pensarmos nisso, até uma página web chinesa vai ter muitos caracteres ASCII — todos os elementos e atributos à volta dos caracteres chineses imprimíveis. Ser capaz de se encontrar o N-ésimo caracter em tempo constante é bom, mas ainda existe o problema chato daqueles caracteres do plano astral, o que significa que nós não conseguimos guarantir que cada caracter tem exactamente dois bytes, portanto não conseguimos realmente encontrar o N-ésimo caracter em tempo constante a não ser que mantenhamos um índice separado. E há muito texto ASCII pelo mundo…

Outras pessoas ponderaram estas questões, e arranjaram uma solução:

UTF-8

UTF-8 é um sistema de codificação de tamanho variável para o Unicode. Ou seja, caracteres diferentes têm um número diferente de bytes. Para caracters ASCII (A-Z, etc.) UTF-8 usa apenas um byte por caracter. Na realidade, usa exactamente os mesmos bytes; os primeiros 128 caracteres (0–127) em UTF-8 são indistinguíveis de ASCII. Caracteres “derivados do alfabeto latino” com o ñ e o ö acabam por usar dois bytes. (Os bytes não são simplesmente o valor Unicode como seriam em UTF-16; há uma complicada manipulação de bits envolvida.) Caracteres chineses como 中 acabam por ter três bytes. Os caracteres de “plano astral” raramente usados levam quatro bytes.

Desvantagens: por cada caracter levar um número diferente de bytes, encontrar o N-ésimo caracter é uma operação O(N) — isto é, quanto mais longa a string, mais tempo se leva a encontrar um caracter específico. Além disso, há manipulação de bits envolvida para codificar caracteres em bytes e descodificar bytes em caracteres.

Vantagens: codificação super-eficiente de caracteres ASCII comuns. Não é pior que UTF-16 para caracteres derivados do alfabeto latino. Melhor que UTF-32 para caracteres chineses. Adicionalmente (e vai ter que confiar em mim nisto, porque não lhe vou mostrar a matemática), devido à natureza exacta da manipulação de bits, não há problemas na ordenação de bytes. Um documento codificado em UTF-8 usa exactamente a mesma sequência de bytes em qualquer computador.

Mergulhar

Em Python 3, todas as strings são sequências de caracteres Unicode. Não existem strings de Python codificadas em UTF-8, ou strings de Python codificadas como CP-1252. “Esta string é UTF-8?” é uma pergunta inválida. UTF-8 é uma forma de codificar caracteres como uma sequência de bytes. Se quisermos pergar numa string e torná-la numa sequência de bytes numa codificação de caracteres em particular, o Python 3 pode ajudar-nos com isso. Se quisermos pegar numa sequência de bytes e torná-la numa string, o Python 3 pode ajudar-nos com isso também. Bytes não são caracteres; bytes são bytes. Caracteres são uma abstração. Uma string é uma sequência dessas abstrações.

>>> s = '深入 Python'    
>>> len(s)               
9
>>> s[0]                 
'深'
>>> s + ' 3'             
'深入 Python 3'
  1. Para criar uma string, delimita-se em aspas. As strings de Python podem ser definidas com apóstrofes (') ou aspas (").
  2. A função primitiva len() devolve o comprimento da string, i.e. o número de caracteres. Esta é a mesma função que usamos para encontrar o tamanho de uma lista, tuplo, conjunto, ou dicionário. Uma string é como um tuplo de caracteres.
  3. Tal como se retira itens individuais de uma lista, também podemos retirar caracteres individuais de uma string usando notação de índices.
  4. Tal como as listas, podemos concatenar strings usando o operador +.

Formatting Strings

Let’s take another look at humansize.py:

[download humansize.py]

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],         
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convert a file size to human-readable form.                          

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    '''                                                                     
    if size < 0:
        raise ValueError('number must be non-negative')                     

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)                       

    raise ValueError('number too large')
  1. 'KB', 'MB', 'GB'… those are each strings.
  2. Function docstrings are strings. This docstring spans multiple lines, so it uses three-in-a-row quotes to start and end the string.
  3. These three-in-a-row quotes end the docstring.
  4. There’s another string, being passed to the exception as a human-readable error message.
  5. There’s a… whoa, what the heck is that?

Python 3 supports formatting values into strings. Although this can include very complicated expressions, the most basic usage is to insert a value into a string with a single placeholder.

>>> username = 'mark'
>>> password = 'PapayaWhip'                             
>>> "{0}'s password is {1}".format(username, password)  
"mark's password is PapayaWhip"
  1. No, my password is not really PapayaWhip.
  2. There’s a lot going on here. First, that’s a method call on a string literal. Strings are objects, and objects have methods. Second, the whole expression evaluates to a string. Third, {0} and {1} are replacement fields, which are replaced by the arguments passed to the format() method.

Compound Field Names

The previous example shows the simplest case, where the replacement fields are simply integers. Integer replacement fields are treated as positional indices into the argument list of the format() method. That means that {0} is replaced by the first argument (username in this case), {1} is replaced by the second argument (password), &c. You can have as many positional indices as you have arguments, and you can have as many arguments as you want. But replacement fields are much more powerful than that.

>>> import humansize
>>> si_suffixes = humansize.SUFFIXES[1000]      
>>> si_suffixes
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> '1000{0[0]} = 1{0[1]}'.format(si_suffixes)  
'1000KB = 1MB'
  1. Rather than calling any function in the humansize module, you’re just grabbing one of the data structures it defines: the list of “SI” (powers-of-1000) suffixes.
  2. This looks complicated, but it’s not. {0} would refer to the first argument passed to the format() method, si_suffixes. But si_suffixes is a list. So {0[0]} refers to the first item of the list which is the first argument passed to the format() method: 'KB'. Meanwhile, {0[1]} refers to the second item of the same list: 'MB'. Everything outside the curly braces — including 1000, the equals sign, and the spaces — is untouched. The final result is the string '1000KB = 1MB'.

What this example shows is that format specifiers can access items and properties of data structures using (almost) Python syntax. This is called compound field names. The following compound field names “just work”:

Just to blow your mind, here’s an example that combines all of the above:

>>> import humansize
>>> import sys
>>> '1MB = 1000{0.modules[humansize].SUFFIXES[1000][0]}'.format(sys)
'1MB = 1000KB'

Here’s how it works:

Format Specifiers

But wait! There’s more! Let’s take another look at that strange line of code from humansize.py:

if size < multiple:
    return '{0:.1f} {1}'.format(size, suffix)

{1} is replaced with the second argument passed to the format() method, which is suffix. But what is {0:.1f}? It’s two things: {0}, which you recognize, and :.1f, which you don’t. The second half (including and after the colon) defines the format specifier, which further refines how the replaced variable should be formatted.

Format specifiers allow you to munge the replacement text in a variety of useful ways, like the printf() function in C. You can add zero- or space-padding, align strings, control decimal precision, and even convert numbers to hexadecimal.

Within a replacement field, a colon (:) marks the start of the format specifier. The format specifier “.1” means “round to the nearest tenth” (i.e. display only one digit after the decimal point). The format specifier “f” means “fixed-point number” (as opposed to exponential notation or some other decimal representation). Thus, given a size of 698.24 and suffix of 'GB', the formatted string would be '698.2 GB', because 698.24 gets rounded to one decimal place, then the suffix is appended after the number.

>>> '{0:.1f} {1}'.format(698.24, 'GB')
'698.2 GB'

For all the gory details on format specifiers, consult the Format Specification Mini-Language in the official Python documentation.

Other Common String Methods

Besides formatting, strings can do a number of other useful tricks.

>>> s = '''Finished files are the re-  
... sult of years of scientif-
... ic study combined with the
... experience of years.'''
>>> s.splitlines()                     
['Finished files are the re-',
 'sult of years of scientif-',
 'ic study combined with the',
 'experience of years.']
>>> print(s.lower())                   
finished files are the re-
sult of years of scientif-
ic study combined with the
experience of years.
>>> s.lower().count('f')               
6
  1. You can input multiline strings in the Python interactive shell. Once you start a multiline string with triple quotation marks, just hit ENTER and the interactive shell will prompt you to continue the string. Typing the closing triple quotation marks ends the string, and the next ENTER will execute the command (in this case, assigning the string to s).
  2. The splitlines() method takes one multiline string and returns a list of strings, one for each line of the original. Note that the carriage returns at the end of each line are not included.
  3. The lower() method converts the entire string to lowercase. (Similarly, the upper() method converts a string to uppercase.)
  4. The count() method counts the number of occurrences of a substring. Yes, there really are six “f”s in that sentence!

Here’s another common case. Let’s say you have a list of key-value pairs in the form key1=value1&key2=value2, and you want to split them up and make a dictionary of the form {key1: value1, key2: value2}.

>>> query = 'user=pilgrim&database=master&password=PapayaWhip'
>>> a_list = query.split('&')                                        
>>> a_list
['user=pilgrim', 'database=master', 'password=PapayaWhip']
>>> a_list_of_lists = [v.split('=', 1) for v in a_list if '=' in v]  
>>> a_list_of_lists
[['user', 'pilgrim'], ['database', 'master'], ['password', 'PapayaWhip']]
>>> a_dict = dict(a_list_of_lists)                                   
>>> a_dict
{'password': 'PapayaWhip', 'user': 'pilgrim', 'database': 'master'}
  1. The split() string method has one required argument, a delimiter. The method splits a string into a list of strings based on the delimiter. Here, the delimiter is an ampersand character, but it could be anything.
  2. Now we have a list of strings, each with a key, followed by an equals sign, followed by a value. We can use a list comprehension to iterate over the entire list and split each string into two strings based on the first equals sign. The optional second argument to the split() method is the number of times you want to split. 1 means “only split once,” so the split() method will return a two-item list. (In theory, a value could contain an equals sign too. If you just used 'key=value=foo'.split('='), you would end up with a three-item list ['key', 'value', 'foo'].)
  3. Finally, Python can turn that list-of-lists into a dictionary simply by passing it to the dict() function.

The previous example looks a lot like parsing query parameters in a URL, but real-life URL parsing is actually more complicated than this. If you’re dealing with URL query parameters, you’re better off using the urllib.parse.parse_qs() function, which handles some non-obvious edge cases.

Slicing A String

Once you’ve defined a string, you can get any part of it as a new string. This is called slicing the string. Slicing strings works exactly the same as slicing lists, which makes sense, because strings are just sequences of characters.

>>> a_string = 'My alphabet starts where your alphabet ends.'
>>> a_string[3:11]           
'alphabet'
>>> a_string[3:-3]           
'alphabet starts where your alphabet en'
>>> a_string[0:2]            
'My'
>>> a_string[:18]            
'My alphabet starts'
>>> a_string[18:]            
' where your alphabet ends.'
  1. You can get a part of a string, called a “slice”, by specifying two indices. The return value is a new string containing all the characters of the string, in order, starting with the first slice index.
  2. Like slicing lists, you can use negative indices to slice strings.
  3. Strings are zero-based, so a_string[0:2] returns the first two items of the string, starting at a_string[0], up to but not including a_string[2].
  4. If the left slice index is 0, you can leave it out, and 0 is implied. So a_string[:18] is the same as a_string[0:18], because the starting 0 is implied.
  5. Similarly, if the right slice index is the length of the string, you can leave it out. So a_string[18:] is the same as a_string[18:44], because this string has 44 characters. There is a pleasing symmetry here. In this 44-character string, a_string[:18] returns the first 18 characters, and a_string[18:] returns everything but the first 18 characters. In fact, a_string[:n] will always return the first n characters, and a_string[n:] will return the rest, regardless of the length of the string.

Strings vs. Bytes

Bytes are bytes; characters are an abstraction. An immutable sequence of Unicode characters is called a string. An immutable sequence of numbers-between-0-and-255 is called a bytes object.

>>> by = b'abcd\x65'  
>>> by
b'abcde'
>>> type(by)          
<class 'bytes'>
>>> len(by)           
5
>>> by += b'\xff'     
>>> by
b'abcde\xff'
>>> len(by)           
6
>>> by[0]             
97
>>> by[0] = 102       
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
  1. To define a bytes object, use the b''byte literal” syntax. Each byte within the byte literal can be an ASCII character or an encoded hexadecimal number from \x00 to \xff (0–255).
  2. The type of a bytes object is bytes.
  3. Just like lists and strings, you can get the length of a bytes object with the built-in len() function.
  4. Just like lists and strings, you can use the + operator to concatenate bytes objects. The result is a new bytes object.
  5. Concatenating a 5-byte bytes object and a 1-byte bytes object gives you a 6-byte bytes object.
  6. Just like lists and strings, you can use index notation to get individual bytes in a bytes object. The items of a string are strings; the items of a bytes object are integers. Specifically, integers between 0–255.
  7. A bytes object is immutable; you can not assign individual bytes. If you need to change individual bytes, you can either use string slicing and concatenation operators (which work the same as strings), or you can convert the bytes object into a bytearray object.
>>> by = b'abcd\x65'
>>> barr = bytearray(by)  
>>> barr
bytearray(b'abcde')
>>> len(barr)             
5
>>> barr[0] = 102         
>>> barr
bytearray(b'fbcde')
  1. To convert a bytes object into a mutable bytearray object, use the built-in bytearray() function.
  2. All the methods and operations you can do on a bytes object, you can do on a bytearray object too.
  3. The one difference is that, with the bytearray object, you can assign individual bytes using index notation. The assigned value must be an integer between 0–255.

The one thing you can never do is mix bytes and strings.

>>> by = b'd'
>>> s = 'abcde'
>>> by + s                       
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't concat bytes to str
>>> s.count(by)                  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't convert 'bytes' object to str implicitly
>>> s.count(by.decode('ascii'))  
1
  1. You can’t concatenate bytes and strings. They are two different data types.
  2. You can’t count the occurrences of bytes in a string, because there are no bytes in a string. A string is a sequence of characters. Perhaps you meant “count the occurrences of the string that you would get after decoding this sequence of bytes in a particular character encoding”? Well then, you’ll need to say that explicitly. Python 3 won’t implicitly convert bytes to strings or strings to bytes.
  3. By an amazing coincidence, this line of code says “count the occurrences of the string that you would get after decoding this sequence of bytes in this particular character encoding.”

And here is the link between strings and bytes: bytes objects have a decode() method that takes a character encoding and returns a string, and strings have an encode() method that takes a character encoding and returns a bytes object. In the previous example, the decoding was relatively straightforward — converting a sequence of bytes in the ASCII encoding into a string of characters. But the same process works with any encoding that supports the characters of the string — even legacy (non-Unicode) encodings.

>>> a_string = '深入 Python'         
>>> len(a_string)
9
>>> by = a_string.encode('utf-8')    
>>> by
b'\xe6\xb7\xb1\xe5\x85\xa5 Python'
>>> len(by)
13
>>> by = a_string.encode('gb18030')  
>>> by
b'\xc9\xee\xc8\xeb Python'
>>> len(by)
11
>>> by = a_string.encode('big5')     
>>> by
b'\xb2`\xa4J Python'
>>> len(by)
11
>>> roundtrip = by.decode('big5')    
>>> roundtrip
'深入 Python'
>>> a_string == roundtrip
True
  1. This is a string. It has nine characters.
  2. This is a bytes object. It has 13 bytes. It is the sequence of bytes you get when you take a_string and encode it in UTF-8.
  3. This is a bytes object. It has 11 bytes. It is the sequence of bytes you get when you take a_string and encode it in GB18030.
  4. This is a bytes object. It has 11 bytes. It is an entirely different sequence of bytes that you get when you take a_string and encode it in Big5.
  5. This is a string. It has nine characters. It is the sequence of characters you get when you take by and decode it using the Big5 encoding algorithm. It is identical to the original string.

Postscript: Character Encoding Of Python Source Code

Python 3 assumes that your source code — i.e. each .py file — is encoded in UTF-8.

In Python 2, the default encoding for .py files was ASCII. In Python 3, the default encoding is UTF-8.

If you would like to use a different encoding within your Python code, you can put an encoding declaration on the first line of each file. This declaration defines a .py file to be windows-1252:

# -*- coding: windows-1252 -*-

Technically, the character encoding override can also be on the second line, if the first line is a UNIX-like hash-bang command.

#!/usr/bin/python3
# -*- coding: windows-1252 -*-

For more information, consult PEP 263: Defining Python Source Code Encodings.

Further Reading

On Unicode in Python:

On Unicode in general:

On character encoding in other formats:

On strings and string formatting:

© 2001–11 Mark Pilgrim