Esta é uma versão mais antiga do livro agora conhecido como Think Python. Você pode preferir ler uma versão mais recente.
Como Pensar como um Cientista Informático |
Capítulo 15
15.1 Composição
Por agora, você já viu vários exemplos de composição. Um dos primeiros exemplos foi usar uma invocação de método como parte da anexação. Outro exemplo é a estrutura aninhada de comandos; você pode colocar um comando if dentro de um loop, dentro de outro comando if, e assim por diante.
Visto este padrão, e tendo aprendido sobre listas e objetos, você não deve se surpreender em aprender que você pode criar listas de objetos. Você também pode criar objetos que contenham listas (asattributes); você pode criar listas que contenham listas; você pode criar objetos que contenham objetos; e assim por diante.
Neste capítulo e no próximo, vamos ver alguns exemplos destas combinações, usando objetos de cartas como exemplo.
15.2 Objetos de cartas
Se você não está familiarizado com cartas comuns, agora seria um bom momento para obter um baralho, ou então este capítulo pode não fazer muito sentido. Os naipes são Espadas, Corações, Diamantes e Clubes (em ordem decrescente em ponte). As fileiras são Ás, 2, 3, 4, 5,6, 7, 8, 9, 10, Valete, Dama e Rei. Dependendo do jogo que você estiver jogando, a classificação do Ás pode ser maior que Rei ou menor que 2,
Se quisermos definir um novo objeto para representar uma carta de jogo, é óbvio quais devem ser os atributos: classificação e terno. Não é tão óbvio qual deve ser o tipo de atributos. Uma possibilidade é usar strings contendo palavras como “Espada” para os naipes e “Rainha” para as fileiras. Um problema com esta implementação é que não seria fácil comparar cartas com cartas que tivessem um rank ou terno mais alto.
Uma alternativa é usar inteiros para codificar os rankings e ternos.Por “codificar”, não nos referimos ao que algumas pessoas pensam, que é codificar ou traduzir para um código secreto. O que um cientista da computação significa por “encode” é “definir um mapeamento entre a sequência de números e os itens que eu quero representar”. Por exemplo, o que é que um cientista informático quer dizer?
Pades | -> | 3 |
Hearts | -> | 2 |
Diamonds | -> | 1 |
Clubs | -> | 0 |
Uma característica óbvia deste mapeamento é que os fatos mapeiam para inteiros em ordem, para podermos comparar fatos, comparando números inteiros. O mapeamento de forranks é bastante óbvio; cada um dos mapas de classificação numérica para o inteiro correspondente, e para as cartas faciais:
Jack | -> | 11 |
Queen | -> | 12 |
King | -> | 13 |
A razão pela qual estamos usando notação matemática para estes mapeamentos é que eles não fazem parte do programa Python. Eles são parte do projeto do programa, mas nunca aparecem explicitamente no código. A definição da classe para o tipo Card se parece com isto:
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
Como de costume, nós fornecemos um método de inicialização que leva um parametro opcional para cada atributo. O valor padrão do naipe é0, que representa os Clubes.
Para criar uma Carta, invocamos o Construtor de Cartas com o terno e a classificação da carta que queremos.
threeOfClubs = Card(3, 1)
Na próxima seção vamos descobrir qual carta acabamos de fazer.
15.3 Atributos de classe e o método __str__
Para imprimir objetos de cartão de uma forma que as pessoas possam ler facilmente, queremos mapear os códigos inteiros em palavras. Uma maneira natural de tudo que é com listas de strings. Atribuímos estas listas aos classattributes no topo da definição da classe:
class Card:
suitList =
rankList =
#init method omitted
def __str__(self):
return (self.rankList + ” of ” +
self.suitList)
Um atributo de classe é definido fora de qualquer método, e pode ser acessado a partir de qualquer um dos métodos da classe.
Inside __str__, podemos usar suitList e rankList para mapear os valores numéricos de suit e rank para strings. Por exemplo, a expressão self.suitList significa “use o atributo suit do objeto self como um índice no atributo de classe chamado suitList, e selecione a string apropriada.”
A razão para o “narf” no primeiro elemento da rankList é para agir como um guardião de lugar para o elemento zero-eth da lista, que nunca deve ser usado. Os únicos ranks válidos são de 1 a 13. Este item maldito não é totalmente necessário. Poderíamos ter começado em 0,como de costume, mas é menos confuso codificar 2 como 2, 3 como 3, e assim por diante.
Com os métodos que temos até agora, podemos criar e imprimir cartões:
>>>> card1 = Card(1, 11)
>>>> print card1
Jack of Diamonds
Atributos de classe como suitList são compartilhados por todos os Cardobjects. A vantagem disso é que podemos usar qualquer Cardobject para acessar os atributos da classe:
>>>3818> print card2
3 of Diamonds
>>3818> print card2.suitList
Diamonds
A desvantagem é que se modificarmos um atributo de classe, ele afeta cada instância da classe. Por exemplo, se decidirmos que “Valete de Ouros” deve realmente ser chamado de “Valete de Baleias de Espiral”, poderíamos fazer isto:
>>>> card1.suitList = “Baleias de Espiral”
>>> cartão1
Jack of Swirly Whales
O problema é que todos os Diamantes acabaram de se tornar Baleias de Espiral:
>>> imprimir cartão2
3 de Baleias de Espiral
Normalmente não é uma boa ideia modificar os atributos de classe.
15.4 Comparando cartas
Para tipos primitivos, existem operadores condicionais(<, >, ==, etc.) que comparam e determinam quando um é maior, menor ou igual a outro. Para tipos definidos pelo usuário, podemos substituir o comportamento dos operadores embutidos fornecendo um método chamado__cmp__. Por convenção, __cmp__ tem dois parâmetros, self e outro, e retorna1 se o primeiro objeto é maior, -1 se o objeto de segundo é maior, e 0 se são iguais um ao outro.
Alguns tipos estão completamente ordenados, o que significa que você pode comparar dois elementos e dizer qual é maior. Por exemplo, os números inteiros e os números de vírgula flutuante estão completamente ordenados. Alguns conjuntos são desordenados, o que significa que não há uma maneira significativa de dizer que o oneelement é maior que outro. Por exemplo, as frutas são desordenadas, e é por isso que você não pode comparar maçãs e laranjas.
O conjunto de cartas de jogo é parcialmente ordenado, o que significa que às vezes você pode comparar cartas e às vezes não. Por exemplo, você sabe que o 3 de Paus é maior que o 2 de Paus, e o 3 deDiamonds é maior que o 3 de Paus. Mas qual é melhor, os 3 de Clubes ou os 2 de Diamantes? Um tem um valor superior, mas o outro tem um naipe superior.
Para tornar as cartas comparáveis, você tem que decidir qual é mais importante, valor ou naipe. Para ser honesto, a escolha é isarbitrária. Para escolher, vamos dizer que o naipe é mais importante, porque um novo baralho de cartas vem com todos os Clubes juntos, seguido por todos os Diamantes, e assim por diante.
Com isso decidido, podemos escrever __cmp__:
def __cmp__(self, other):
# verifique os naipe
se self.suit > other.suit: return 1
se self.suit < other.suit: return -1
# os naipe são os mesmos… verifique os rankings
se self.rank > other.rank: return 1
se self.rank < other.rank: return -1
# os rankings são os mesmos… é um empate
retorno 0
Neste ordenamento, os Ases aparecem mais baixos que os Deuces (2s).
Como um exercício, modifique __cmp__ para que os Ases sejam mais altos que os Reis.
15.5 Decks
Agora temos objetos para representar Cartas, o próximo passo lógico é definir uma classe para representar um Deck. Claro, adeck é composto de cartas, portanto cada objeto Deck conterá alistar cartas como um atributo.
A seguir está uma definição de classe para a classe Deck. O método de inicialização cria as cartas de atributo e gera o conjunto padrão de cinqüenta e duas cartas:
classe Deck:
def __init__(self):
self.cards =
para terno no intervalo(4):
para rank no intervalo(1, 14):
self.cards.append(Card(suit, rank))
A maneira mais fácil de povoar o baralho é com um loop aninhado. O laço externo enumera os naipes de 0 a 3. O laço interno enumera os naipe de 1 a 13. Como o laço externo itera quatro vezes, e o laço interno itera treze vezes, o número total de vezes que o corpo é executado é cinqüenta e duas (treze vezes quatro). Cada iteração cria uma nova instância de carta com o naipe e a classificação atual, e anexa essa carta à lista de cartas.
O método do apêndice funciona em listas mas não, é claro, tuples.
15.6 Imprimindo o baralho
Como de costume, quando definimos um novo tipo de objeto queremos um método que imprima o conteúdo de um objeto.Para imprimir um Deck, atravessamos a lista e imprimimos cada Carta:
Classe Deck:
…
def printDeck(self):
para carta em self.cards:
print card
Here, e de agora em diante, a elipse (….) indica que nós temos os outros métodos na classe.
Como alternativa ao printDeck, nós poderíamos escrever um método __str__ para a classe Deck. A vantagem de __str__ é que ele é mais flexível. Ao invés de apenas imprimir o conteúdo do objeto, ele gera uma stringrepresentation que outras partes do programa podem manipular antes de imprimir, ou armazenar para uso posterior.
Aqui está uma versão de __str__ que retorna uma stringrepresentation de um Deck.Para adicionar um pouco de pizzazz, ele organiza as cartas em um cascadewhere onde cada carta é recuada um espaço a mais do que a carta anterior:
class Deck:
…
def __str__(self):
s = “”
for i in range(len(self.cards)):
s = s + “”*i + str(self.cards) + “\n”
retorno s
Este exemplo demonstra várias características. Primeiro, em vez de gravarmos self.cards e atribuirmos cada cartão a uma variável, estamos usando i como um loopvariable e um índice na lista de cartões.
Segundo, estamos usando o operador de multiplicação de string para indenteach cartão por mais um espaço do que o último. A expressão “*i produz um número de espaços igual ao valor atual de i.
Terceiro, ao invés de usar o comando de impressão para imprimir os cartões,usamos a função str. Passar um objeto como argumento tostr é equivalente a invocar o método __str__ no objeto.
Finalmente, estamos usando a variável s como acumulador.Inicialmente, s é a string vazia. Cada vez através do loop, uma nova string é gerada e concatenada com o valor antigo de sto obtém o novo valor. Quando o loop termina, s contém a representação completa da string do Deck, que se parece com este:
>>>> print deck
Ace de Paus
2 de Paus
3 de Paus
4 de Paus
5 de Paus
6 de Tacos
7 de Tacos
8 de Tacos
9 de Tacos
10 de Tacos
Jack de Tacos
Queen de Tacos
Rei de Tacos
Ace de Diamantes
E assim por diante. Mesmo que o resultado apareça em 52 linhas, é uma cadeia longa que contém novas linhas.
15.7 Baralhando o baralho
Se um baralho estiver perfeitamente baralhado, então qualquer carta é igualmente provável que apareça em qualquer lugar do baralho, e qualquer lugar do baralho é igualmente provável que contenha qualquer carta.
Para baralhar o baralho, usaremos a função randrange do módulo aleatório. Com dois argumentos inteiros,a e b, randrange escolhe um inteiro aleatório no intervalo a <= x < b. Uma vez que o limite superior é estritamente sem limites que b, podemos usar o comprimento de uma lista como este segundo argumento, e temos a garantia de obter um índice legal.Por exemplo, esta expressão escolhe o índice de uma carta aleatória num baralho:
random.randrange(0, len(self.cards))
Uma maneira fácil de baralhar o baralho é atravessando as cartas e trocando cada carta com uma escolhida aleatoriamente. É possível que a carta seja trocada consigo mesma, mas isso é bom. De facto, se excluíssemos essa possibilidade, a ordem das cartas seria menos aleatória:
class Deck:
…
def shuffle(self):
importar aleatória
nCards = len(self.cards)
for i in range(nCards):
j = random.randrange(i, nCards)
self.cards, self.cards = self.cards, self.cards
Rather do que assumir que existem cinquenta e duas cartas no baralho, obtemos o tamanho real da lista e armazenamo-la em nCards.
Para cada carta no baralho, escolhemos uma carta aleatória de entre as cartas que ainda não foram embaralhadas. Depois trocamos a carta atual (i) com a carta selecionada (j). Para trocar as cartas usamos uma atribuição tuple, como na Secção 9.2:
self.cards, self.cards = self.cards, self.cards
Como um exercício, reescreva esta linha de código sem usar uma atribuição de sequência.
15.8 Removing and dealing cards
Outro método que seria útil para a classe Deck é removeCard, que toma uma carta como argumento, remove-a, e retorna True se a carta estava no baralho e Falseotherwise:
class Deck:
…
def removeCard(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else:
return False
The in operator retorna true se o primeiro operando estiver no segundo, que deve ser uma lista ou um tuple. Se o primeiro operando for um anobjeto, Python usa o método __cmp__ do objeto para determinar a qualidade com os itens da lista. Como o __cmp__ na classeCard verifica a igualdade profunda, o método removeCard verifica a igualdade profunda.
Para dar cartas, nós queremos remover e devolver a carta superior.O pop método de lista fornece uma maneira conveniente de fazer isso:
class Deck:
…
def popCard(self):
return self.cards.pop()
Atualmente, o pop remove a última carta da lista, então estamos negociando de forma inefeita a partir da parte inferior do baralho.
Mais uma operação que provavelmente queremos é a função booleanaEmpty, que retorna verdadeiro se o baralho não contém cartas:
classe Deck:
…
def isEmpty(self):
retorno (len(self.cards) == 0)
15.9 Glossário
codificar Para representar um conjunto de valores usando um outro conjunto de valores através da construção de um mapeamento entre eles. class attribute Uma variável que é definida na definição de classe insidea mas fora de qualquer método. Os atributos da classe são acessíveis a partir de qualquer método da classe e são compartilhados por todas as instâncias da classe. accumulator Uma variável utilizada em um loop para acumular uma série de valores, por exemplo, concatenando-os em uma string ou adicionando-os a uma soma em execução.
Esta é uma versão mais antiga do livro agora conhecida como Think Python. Você pode preferir ler uma versão mais recente.
Como Pensar como um Cientista Informático |