herança múltipla (C++)
sobrecarga de operadores (C++)
não obriga a criar classes (C++)
tipagem dinâmica (Smalltalk)
- tipagem dinâmica, mas não tipagem fraca
>>> a = 'casa'
>>> b = 10
>>> a + b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
>>> a + str(b)
'casa10'
>>> '%s #%s' % (a, b)
'casa #10'
>>> a = 2
>>> b = 3
>>> a + b
5
>>> a.__add__(b)
5
Uma classe com três métodos; __init__ é invocado automaticamente na instanciação:
class Contador(object):
def __init__(self):
self.dic = {}
def incluir(self, item):
qtd = self.dic.get(item, 0) + 1
self.dic[item] = qtd
def contar(self, item):
return self.dic[item]
todos os métodos de instância recebem como primeiro argumento uma referência explícita à instância
é obrigatório fazer referência explícita a self para acessar atributos da instância:
class Contador(object):
def __init__(self):
self.dic = {}
def incluir(self, item):
qtd = self.dic.get(item, 0) + 1
self.dic[item] = qtd
def contar(self, item):
return self.dic[item]
Nas chamadas de métodos, o self é implícito pois a sintaxe é instancia.metodo().
>>> cont = Contador() # não se usa 'new'
>>> pal = 'abacaxi'
>>> for letra in pal:
... cont.incluir(letra)
...
>>> for letra in sorted(set(pal)):
... print letra, cont.contar(letra)
...
a 3
b 1
c 1
i 1
x 1
__new__ x __init__
Raramente implementamos construtores __new__ em Python; usamos o construtor padrão e apenas usamos __init__ para inicializar os atributos da instância.
>>> class Animal(object):
... 'um animal qualquer'
...
>>> baleia = Animal()
>>> baleia.nome = 'Moby Dick'
>>> baleia.peso = 1200
>>> print '%s (%s Kg)' % (baleia.nome, baleia.peso)
Moby Dick (1200 Kg)
>>> class Animal(object):
... nome = 'Rex' # atributo da classe
...
>>> cao = Animal()
>>> cao.nome # atributo adquirido da classe
'Rex'
>>> cao.nome = 'Fido' # criado na instância
>>> cao.nome
'Fido'
>>> Animal.nome # na classe, nada mudou
'Rex'
>>> dino = Animal()
>>> dino.nome
'Rex'
>>>
class Exemplo(object):
@classmethod
def da_classe(cls, arg1):
return (cls, arg1)
@staticmethod
def estatico(arg1):
return arg1
>>> Exemplo.da_classe('fu')
(<class '__main__.Exemplo'>, 'fu')
>>> Exemplo.estatico('bar')
'bar'
class ContadorTolerante(Contador):
def contar(self, item):
return self.dic.get(item, 0)
a forma mais simples:
class ContadorTotalizador(Contador):
def __init__(self):
Contador.__init__(self)
self.total = 0
def incluir(self, item):
Contador.incluir(self, item)
self.total += 1
a forma mais correta:
class ContadorTotalizador(Contador):
def __init__(self):
super(ContadorTotalizador,
self).__init__()
self.total = 0
def incluir(self, item):
super(ContadorTotalizador,
self).incluir(item)
self.total += 1
>>> from contadores import *
>>> class ContadorTT(ContadorTotalizador,
... ContadorTolerante):
... pass
...
>>> ctt = ContadorTT()
>>> for letra in 'abacaxi':
... ctt.incluir(letra)
...
>>> ctt.total
7
>>> ctt.contar('a')
3
>>> ctt.contar('z')
0
classe que totaliza e não levanta exceções:
class ContadorTT(ContadorTotalizador,
ContadorTolerante):
pass
MRO = ordem de resolução de métodos:
>>> ContadorTT.__mro__
(<class '__main__.ContadorTT'>,
<class '__main__.ContadorTotalizador'>,
<class '__main__.ContadorTolerante'>,
<class '__main__.Contador'>,
<type 'object'>)
Propriedades
encapsulamento para quem precisa de encapsulamento:
>>> a = C() >>> a.x = 10 # violação!? >>> print a.x 10 >>> a.x = -10 >>> print a.x # como?????? 0
apenas para leitura, via decorator:
class C(object):
def __init__(self, x):
self.__x = x
@property
def x(self):
return self.__x
a notação __x protege este atributo contra acessos acidentais
para leitura e escrita:
class C(object):
def __init__(self, x=0):
self.__x = x
def getx(self):
return self.__x
def setx(self, x):
if x < 0: x = 0
self.__x = x
x = property(getx, setx)
class ContadorTotalizador(Contador):
def __init__(self):
super(ContadorTotalizador,
self).__init__()
self.__total = 0
def incluir(self, item):
super(ContadorTotalizador,
self).incluir(item)
self.__total += 1
@property
def total(self):
return self.__total
>>> def f(a, b=1, c=None):
... return a, b, c
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes at least 1 argument (0 given)
>>> f(9)
(9, 1, None)
>>> f(5,6,7)
(5, 6, 7)
>>> f(3, c=4)
(3, 1, 4)
>>> def f(*args, **kwargs):
... return args, kwargs
...
>>> f()
((), {})
>>> f(1)
((1,), {})
>>> f(cor='azul')
((), {'cor': 'azul'})
>>> f(10,20,30,sabor='uva',cor='vinho')
((10, 20, 30), {'cor': 'vinho', 'sabor': 'uva'})
>>> def f(*args, **kwargs):
... print '%r\n%r' % (args, kwargs)
...
>>> l
[0, 1, 2]
>>> d = {'peso':83,'altura':1.7}
>>> f(l,d)
([0, 1, 2], {'altura': 1.7, 'peso': 83})
{}
>>> f(*l)
(0, 1, 2)
{}
>>> f(**d)
()
{'peso': 83, 'altura': 1.7}
>>> f(*l,**d)
(0, 1, 2)
{'peso': 83, 'altura': 1.7}
O conceito de “polimorfismo” significa que podemos tratar instâncias de diferentes classes da mesma maneira.
Assim, podemos enviar uma mensagem a um objeto sem saber de antemão qual é o seu tipo, e o objeto ainda assim fará “a coisa certa”, pelo menos do seu ponto de vista.
Scott Ambler - The Object Primer, 2nd ed. - p. 173
>>> l = [1, 2, 3]
>>> l[:2]
[1, 2]
>>> 'casa'[:2]
'ca'
>>> len(l), len('casa')
(3, 4)
“tipagem pato” (duck typing) é polimorfismo dinâmico anabolizado
>>> def dobro(x):
... return x * 2
...
>>> dobro(10)
20
>>> dobro([1, 2, 3])
[1, 2, 3, 1, 2, 3]
>>> dobro('casa')
'casacasa'
>>> s = 'Python: simples e correta'
>>> s[0]
'P'
>>> s[-1]
'a'
>>> s[:3]
'Pyt'
>>> for letra in reversed(s): print letra
...
.
a
t
e
r
r
o
c
>>> l = range(10)
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[0]
0
>>> l[-1]
9
>>> l[:3]
[0, 1, 2]
>>> for n in reversed(l): print n
...
9
8
7
6
5
4
3
2
1
0
class Carta(object):
def __init__(self, valor, naipe):
self.valor = valor
self.naipe = naipe
def __repr__(self):
return '<%s de %s>' % (self.valor, self.naipe)
from random import shuffle
class Baralho(object):
naipes = 'copas ouros espadas paus'.split()
valores = 'A 2 3 4 5 6 7 8 9 10 J Q K'.split()
def __init__(self):
self.cartas = [Carta(v, n)
for n in self.naipes
for v in self.valores]
def embaralhar(self):
shuffle(self.cartas)
def __len__(self):
return len(self.cartas)
def __getitem__(self, pos):
return self.cartas[pos]
>>> from baralho import Baralho
>>> b = Baralho()
>>> len(b)
52
>>> b[0], b[1], b[2]
(<A de copas>, <2 de copas>, <3 de copas>)
>>> b.embaralhar()
>>> b[-3:]
[<7 de espadas>, <6 de copas>, <K de copas>]
>>> for carta in reversed(b): print carta
...
<K de copas>
<6 de copas>
<7 de espadas>
<3 de ouros>
<8 de espadas>
<6 de espadas>
<3 de espadas>
<J de espadas>
<6 de ouros>
<Q de espadas>
# etc...