Qual é a diferença entre o estilo antigo e as novas classes de estilo no Python?

994

Qual é a diferença entre o estilo antigo e as novas classes de estilo no Python? Quando devo usar um ou outro?

Somente leitura
fonte

Respostas:

560

Das classes clássica e de novo estilo :

Até o Python 2.1, as classes de estilo antigo eram o único sabor disponível para o usuário.

O conceito de classe (de estilo antigo) não tem relação com o conceito de tipo: se xé uma instância de uma classe de estilo antigo, x.__class__ designa a classe de x, mas type(x)é sempre <type 'instance'>.

Isso reflete o fato de que todas as instâncias do estilo antigo, independentemente de sua classe, são implementadas com um único tipo interno, chamado instância.

As classes de novo estilo foram introduzidas no Python 2.2 para unificar os conceitos de classe e tipo . Uma classe de novo estilo é simplesmente um tipo definido pelo usuário, nem mais nem menos.

Se x é uma instância de uma classe de novo estilo, type(x)normalmente é igual a x.__class__(embora isso não seja garantido - é permitido que uma instância de classe de novo estilo substitua o valor retornado x.__class__).

A principal motivação para a introdução de novas classes de estilo é fornecer um modelo de objeto unificado com um metamodelo completo .

Ele também possui vários benefícios imediatos, como a capacidade de subclassificar a maioria dos tipos internos ou a introdução de "descritores", que habilitam propriedades calculadas.

Por motivos de compatibilidade, as classes ainda são antigas por padrão .

As classes de novo estilo são criadas especificando outra classe de novo estilo (ou seja, um tipo) como uma classe pai ou o objeto "tipo de nível superior" se nenhum outro pai for necessário.

O comportamento das classes de estilo novo difere do das classes de estilo antigo em vários detalhes importantes, além do tipo de retorno.

Algumas dessas mudanças são fundamentais para o novo modelo de objeto, como a maneira como métodos especiais são chamados. Outras são "correções" que antes não podiam ser implementadas por questões de compatibilidade, como a ordem de resolução do método em caso de herança múltipla.

O Python 3 possui apenas classes de novo estilo .

Não importa se você subclassifica objectou não, as classes são de novo estilo no Python 3.

Projesh Bhoumik
fonte
41
Nenhuma dessas diferenças soa como motivos convincentes para usar classes de novo estilo, mas todos dizem que você deve sempre usar o novo estilo. Se estou usando a digitação de pato como deveria, nunca preciso usá-la type(x). Se eu não estou subclassificando um tipo incorporado, não parece haver nenhuma vantagem que eu possa ver nas classes de novo estilo. Há uma desvantagem, que é a digitação extra de (object).
recursivo
78
Certos recursos, como super()não funcionam em aulas de estilo antigo. Sem mencionar, como diz o artigo, existem correções fundamentais, como MRO, e métodos especiais, que são mais do que uma boa razão para usá-lo.
John Doe
21
@Usuário: as classes de estilo antigo se comportam da mesma forma que no 2.1 - e, como poucas pessoas se lembram das peculiaridades, e a documentação não discute mais a maioria delas, elas são ainda piores. A citação da documentação acima diz diretamente isso: existem "correções" que não puderam ser implementadas em classes de estilo antigo. A menos que você queira encontrar peculiaridades com as quais ninguém mais lidou desde o Python 2.1, e que a documentação nem mais explica, não use classes de estilo antigo.
abarnert
10
Aqui está um exemplo de uma peculiaridade que você pode encontrar se usar classes de estilo antigo no 2.7: bugs.python.org/issue21785
KT.
5
Para quem se pergunta, um bom motivo para herdar explicitamente do objeto no Python 3 é que ele facilita o suporte a várias versões do Python.
precisa saber é o seguinte
308

Em termos de declaração:

As classes de novo estilo herdam do objeto ou de outra classe de novo estilo.

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

As aulas de estilo antigo não.

class OldStyleClass():
    pass

Nota do Python 3:

O Python 3 não suporta classes de estilo antigo, portanto, qualquer um dos formulários mencionados acima resulta em uma classe de novo estilo.

Mark Harrison
fonte
24
se uma classe de novo estilo herda de outra classe de novo estilo, por extensão, ela herda de object.
precisa saber é o seguinte
2
Este é um exemplo incorreto da classe python de estilo antigo? class AnotherOldStyleClass: pass
Ankur Agarwal
11
@abc Eu acredito nisso class A: passe class A(): passsão estritamente equivalentes. O primeiro significa "A não herda nenhuma classe pai" e o segundo significa "A não herda nenhuma classe pai" . Isso é bem parecido com not iseis not
eyquem 20/12/2013
5
Apenas como uma observação lateral, para 3.X, a herança de "objeto" é assumida automaticamente (o que significa que não temos como não herdar "objeto" em 3.X). Pelo motivo de compatibilidade com versões anteriores, não é ruim manter "(objeto)" lá.
precisa saber é o seguinte
1
Se formos técnicos sobre as classes herdadas, esta resposta deve observar que você pode criar outra classe de estilo antigo herdando de uma classe de estilo antigo. (Como está escrito, isto deixa responder às perguntas do usuário se você pode herdar de uma classe de estilo antigo Você pode..)
jpmc26
224

Alterações importantes de comportamento entre classes de estilo antigas e novas

  • super adicionado
  • MRO alterado (explicado abaixo)
  • descritores adicionados
  • novos objetos de classe de estilo não podem ser gerados, a menos que sejam derivados de Exception(exemplo abaixo)
  • __slots__ adicionado

MRO (ordem de resolução do método) alterado

Foi mencionado em outras respostas, mas aqui vai um exemplo concreto da diferença entre MRO clássico e C3 MRO (usado em novas classes de estilo).

A questão é a ordem na qual os atributos (que incluem métodos e variáveis-membro) são pesquisados ​​em herança múltipla.

As aulas clássicas fazem uma pesquisa profunda da esquerda para a direita. Pare na primeira partida. Eles não têm o __mro__atributo

class C: i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 0
assert C21().i == 2

try:
    C12.__mro__
except AttributeError:
    pass
else:
    assert False

As classes de novo estilo MRO são mais complicadas de sintetizar em uma única frase em inglês. É explicado em detalhes aqui . Uma de suas propriedades é que uma classe base é pesquisada apenas uma vez que todas as suas classes derivadas foram. Eles têm o __mro__atributo que mostra a ordem de pesquisa.

class C(object): i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 2
assert C21().i == 2

assert C12.__mro__ == (C12, C1, C2, C, object)
assert C21.__mro__ == (C21, C2, C1, C, object)

Os novos objetos de classe de estilo não podem ser gerados, a menos que derivados de Exception

Em torno do Python 2.5 muitas classes poderiam ser geradas e, em torno do Python 2.6, isso foi removido. No Python 2.7.3:

# OK, old:
class Old: pass
try:
    raise Old()
except Old:
    pass
else:
    assert False

# TypeError, new not derived from `Exception`.
class New(object): pass
try:
    raise New()
except TypeError:
    pass
else:
    assert False

# OK, derived from `Exception`.
class New(Exception): pass
try:
    raise New()
except New:
    pass
else:
    assert False

# `'str'` is a new style object, so you can't raise it:
try:
    raise 'str'
except TypeError:
    pass
else:
    assert False
Ciro Santilli adicionou uma nova foto
fonte
8
Resumo claro e agradável, obrigado. Quando você diz "difícil de explicar em inglês", acho que você está descrevendo uma pesquisa aprofundada pós-ordem, em oposição à classe de estilo antigo, que usa uma pesquisa aprofundada de pré-ordem. (pré-encomenda significa que procuramos a nós mesmos antes do primeiro filho e pós-encomenda significa que procuramos a nós mesmos após o último filho).
Steve Carter
40

As classes de estilo antigo ainda são marginalmente mais rápidas para a pesquisa de atributos. Isso geralmente não é importante, mas pode ser útil no código Python 2.x sensível ao desempenho:

Em [3]: classe A:
   ...: def __init __ (próprio):
   ...: self.a = 'oi'
   ...:

Em [4]: ​​classe B (objeto):
   ...: def __init __ (próprio):
   ...: self.a = 'oi'
   ...:

Em [6]: aobj = A ()
Em [7]: bobj = B ()

Em [8]:% timeit aobj.a
10000000 loops, o melhor de 3: 78,7 ns por loop

Em [10]:% timeit bobj.a
10000000 loops, o melhor de 3: 86,9 ns por loop
xioxox
fonte
5
Interessante que você notou na prática, acabei de ler que isso ocorre porque as classes de novo estilo, uma vez encontradas o atributo no ditado de instância, precisam fazer uma pesquisa adicional para descobrir se é uma descrição, ou seja, tem uma método get que precisa ser chamado para obter o valor a ser retornado. As classes de estilo antigo retornam o objeto encontrado sem cálculos adicionais (mas não suportam descritores). Você pode ler mais neste excelente post por Guido python-history.blogspot.co.uk/2010/06/... , especificamente a seção sobre ranhuras
xuloChavez
1
não parece ser verdade com o CPython 2.7.2:%timeit aobj.a 10000000 loops, best of 3: 66.1 ns per loop %timeit bobj.a 10000000 loops, best of 3: 53.9 ns per loop
Benedikt Waldvogel
1
Ainda mais rápido para o aobj no CPython 2.7.2 no Linux x86-64 para mim.
Xxox
41
Provavelmente, é uma má idéia confiar no código Python puro para aplicativos sensíveis ao desempenho. Ninguém diz: "Preciso de código rápido, então usarei classes Python à moda antiga". Numpy não conta como Python puro.
Phillip Nuvem
também no IPython 2.7.6, isso não é verdade. '' '' 477 ns vs. 456 ns por loop '' ''
kmonsoor
37

Guido escreveu The Inside Story on New Style Classes , um excelente artigo sobre classes de estilo novo e antigo em Python.

O Python 3 possui apenas classe de novo estilo. Mesmo se você escrever uma 'classe de estilo antigo', ela é derivada implicitamente object.

As classes de estilo novo têm alguns recursos avançados ausentes nas classes de estilo antigo, como supero novo C3 mro , alguns métodos mágicos etc.

Xiao Hanyu
fonte
24

Aqui está uma diferença muito prática, verdadeira / falsa. A única diferença entre as duas versões do código a seguir é que, na segunda versão, Person herda de objeto . Fora isso, as duas versões são idênticas, mas com resultados diferentes:

  1. Classes à moda antiga

    class Person():
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed1 is ahmed2
    print ahmed1
    print ahmed2
    
    
    >>> False
    <__main__.Person instance at 0xb74acf8c>
    <__main__.Person instance at 0xb74ac6cc>
    >>>
    
  2. Classes de novo estilo

    class Person(object):
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed2 is ahmed1
    print ahmed1
    print ahmed2
    
    >>> True
    <__main__.Person object at 0xb74ac66c>
    <__main__.Person object at 0xb74ac66c>
    >>>
ychaouche
fonte
2
o que '_names_cache' faz? Você poderia compartilhar uma referência?
Muatik 22/01
4
_names_cacheé um dicionário que armazena em cache (armazena para recuperação futura) todos os nomes para os quais você passa Person.__new__. O método setdefault (definido em qualquer dicionário) usa dois argumentos: uma chave e um valor. Se a chave estiver no ditado, ela retornará seu valor. Se não estiver no dict, ele o definirá primeiro como o valor passado como segundo argumento e depois o retornará.
ychaouche
4
O uso está errado. A idéia é não construir um novo objeto, se ele já existir, mas no seu caso __new__()é sempre chamado, e sempre constrói um novo objeto, e depois o lança. Nesse caso, a ifé preferível .setdefault().
Amit Upadhyay
Mas, eu não entendi porque é a diferença na saída, ou seja, na classe de estilo antigo, as duas instâncias eram diferentes, portanto, retornavam False, mas na nova classe de estilo, ambas as instâncias são iguais. Como ? Qual é a mudança na nova classe de estilo, que tornou as duas instâncias iguais, que não estavam na classe de estilo antigo?
Pabitra Pati
1
@PitraPati: É uma espécie de demonstração barata aqui. __new__na verdade não é uma coisa para classes de estilo antigo, não é usado na construção de instância (é apenas um nome aleatório que parece especial, como definição __spam__). Portanto, a construção da classe de estilo antigo apenas chama __init__, enquanto a construção de estilo novo chama __new__(coalescendo para instância singleton por nome) para construir e __init__inicializá-la.
ShadowRanger
10

As classes de novo estilo herdam objecte devem ser escritas como tal no Python 2.2 em diante (ou seja, em class Classname(object):vez de class Classname:). A principal mudança é unificar tipos e classes, e o bom efeito colateral disso é que ele permite herdar dos tipos internos.

Leia a descrição para mais detalhes.

aréola
fonte
8

Novas classes de estilo podem usar super(Foo, self)where Fooé uma classe e selfé a instância.

super(type[, object-or-type])

Retorne um objeto proxy que delega chamadas de método a uma classe pai ou irmã do tipo. Isso é útil para acessar métodos herdados que foram substituídos em uma classe. A ordem de pesquisa é igual à usada por getattr (), exceto que o próprio tipo é ignorado.

E no Python 3.x você pode simplesmente usar super()dentro de uma classe sem nenhum parâmetro.

jamylak
fonte