¿Cuál es la diferencia entre el estilo antiguo y las nuevas clases de estilo en Python?

Respuestas:

560

De clases de estilo nuevo y clásico :

Hasta Python 2.1, las clases de estilo antiguo eran el único sabor disponible para el usuario.

El concepto de clase (estilo antiguo) no está relacionado con el concepto de tipo: si xes una instancia de una clase de estilo antiguo, entonces x.__class__ designa la clase de x, pero type(x)siempre lo es <type 'instance'>.

Esto refleja el hecho de que todas las instancias de estilo antiguo, independientemente de su clase, se implementan con un único tipo incorporado, llamado instancia.

Se introdujeron nuevas clases de estilo en Python 2.2 para unificar los conceptos de clase y tipo . Una nueva clase de estilo es simplemente un tipo definido por el usuario, ni más ni menos.

Si x es una instancia de una clase de estilo nuevo, type(x)normalmente es lo mismo que x.__class__(aunque esto no está garantizado: una instancia de clase de estilo nuevo puede anular el valor devuelto para x.__class__).

La principal motivación para introducir clases de estilo nuevo es proporcionar un modelo de objeto unificado con un metamodelo completo .

También tiene una serie de beneficios inmediatos, como la capacidad de subclasificar la mayoría de los tipos integrados, o la introducción de "descriptores", que permiten propiedades calculadas.

Por razones de compatibilidad, las clases siguen siendo antiguas por defecto .

Las clases de estilo nuevo se crean especificando otra clase de estilo nuevo (es decir, un tipo) como una clase principal o el objeto "tipo de nivel superior" si no se necesita otro padre.

El comportamiento de las clases de estilo nuevo difiere del de las clases de estilo antiguo en varios detalles importantes, además de qué tipo devuelve.

Algunos de estos cambios son fundamentales para el nuevo modelo de objetos, como la forma en que se invocan métodos especiales. Otros son "arreglos" que no se pudieron implementar antes por cuestiones de compatibilidad, como el orden de resolución del método en caso de herencia múltiple.

Python 3 solo tiene clases de estilo nuevo .

No importa si subclasa objecto no, las clases son de nuevo estilo en Python 3.

Projesh Bhoumik
fuente
41
Ninguna de estas diferencias suena como razones convincentes para usar clases de estilo nuevo, sin embargo, todos dicen que siempre debes usar el estilo nuevo. Si estoy usando pato escribiendo como debería, nunca necesito usarlo type(x). Si no estoy subclasificando un tipo incorporado, entonces no parece haber ninguna ventaja que pueda ver de las clases de estilo nuevo. Hay una desventaja, que es la tipificación adicional de (object).
recursivo
78
Ciertas características como super()no funcionan en las clases de estilo antiguo. Sin mencionar, como dice ese artículo, hay soluciones fundamentales, como MRO, y métodos especiales, lo cual es más que una buena razón para usarlo.
John Doe
21
@Usuario: las clases antiguas se comportan de la misma manera en 2.7 que en 2.1, y, dado que pocas personas recuerdan las peculiaridades y la documentación ya no analiza la mayoría de ellas, son aún peores. La cita de documentación anterior dice directamente esto: hay "arreglos" que no se pudieron implementar en las clases de estilo antiguo. A menos que quiera toparse con caprichos que nadie más ha tratado desde Python 2.1, y que la documentación ya ni siquiera explica, no use clases de estilo antiguo.
abarnert
10
Aquí hay un ejemplo de una peculiaridad con la que puede tropezar si usa clases de estilo antiguo en 2.7: bugs.python.org/issue21785
KT.
55
Para cualquiera que se pregunte, una buena razón para heredar explícitamente de un objeto en Python 3 es que facilita el soporte de múltiples versiones de Python.
jpmc26
308

Declaración sabia:

Las clases de estilo nuevo heredan del objeto o de otra clase de estilo nuevo.

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

Las clases de estilo antiguo no.

class OldStyleClass():
    pass

Python 3 Nota:

Python 3 no admite clases de estilo antiguas, por lo que cualquiera de los formularios mencionados anteriormente da como resultado una clase de estilo nuevo.

Mark Harrison
fuente
24
si una clase de estilo nuevo hereda de otra clase de estilo nuevo, entonces, por extensión, hereda de object.
aaronasterling
2
¿Es este un ejemplo incorrecto de la clase de python de estilo antiguo? class AnotherOldStyleClass: pass
Ankur Agarwal
11
@abc Creo eso class A: passy class A(): passson estrictamente equivalentes. El primero significa "A no hereda de ninguna clase principal" y el segundo significa "A no hereda de ninguna clase principal" . Eso es bastante similar not isyis not
eyquem
55
Como nota al margen, para 3.X, se asume automáticamente la herencia de "objeto" (lo que significa que no tenemos forma de no heredar "objeto" en 3.X). Sin embargo, por razones de compatibilidad con versiones anteriores, no está mal mantener "(objeto)" allí.
Yo Hsiao
1
Si vamos a obtener información técnica sobre las clases heredadas, esta respuesta debe tener en cuenta que puede crear otra clase de estilo antiguo heredando de una clase de estilo antiguo. (Como está escrito, esta respuesta deja al usuario preguntándose si puede heredar de una clase de estilo antiguo. Puede
hacerlo
224

Cambios importantes de comportamiento entre las clases de estilo antiguas y nuevas.

  • super agregado
  • MRO cambiado (explicado a continuación)
  • descriptores agregados
  • los nuevos objetos de clase de estilo no se pueden generar a menos que se deriven de Exception(ejemplo a continuación)
  • __slots__ adicional

MRO (Orden de resolución de método) cambiado

Se mencionó en otras respuestas, pero aquí va un ejemplo concreto de la diferencia entre MRO clásico y MRO C3 (usado en nuevas clases de estilo).

La pregunta es el orden en que se buscan los atributos (que incluyen métodos y variables miembro) en herencia múltiple.

Las clases clásicas hacen una búsqueda profunda primero de izquierda a derecha. Detente en el primer partido. No tienen el __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

Las clases de estilo nuevo MRO son más complicadas de sintetizar en una sola oración en inglés. Se explica en detalle aquí . Una de sus propiedades es que solo se busca una clase base una vez que se han buscado todas sus clases derivadas. Tienen el __mro__atributo que muestra el orden de búsqueda.

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)

No se pueden generar nuevos objetos de clase de estilo a menos que se deriven de Exception

Alrededor de Python 2.5, muchas clases se podían generar, y alrededor de Python 2.6 se eliminó. En 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 郝海东 冠状 病 六四 事件 法轮功
fuente
8
Bonito resumen claro, gracias. Cuando dice "difícil de explicar en inglés", creo que está describiendo una búsqueda de profundidad de postorder en oposición a la clase de estilo antiguo que utiliza una búsqueda de profundidad de orden previa. (preordenar significa que nos buscamos antes de nuestro primer hijo y postorder significa que nos buscamos después de nuestro último hijo).
Steve Carter
40

Las clases de estilo antiguo siguen siendo marginalmente más rápidas para la búsqueda de atributos. Esto generalmente no es importante, pero puede ser útil en el código Python 2.x sensible al rendimiento:

En [3]: clase A:
   ...: def __init __ (self):
   ...: self.a = 'hola'
   ...:

En [4]: ​​clase B (objeto):
   ...: def __init __ (self):
   ...: self.a = 'hola'
   ...:

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

En [8]:% timeit aobj.a
10000000 bucles, lo mejor de 3: 78.7 ns por bucle

En [10]:% timeit bobj.a
10000000 bucles, lo mejor de 3: 86.9 ns por bucle
xioxox
fuente
55
Es interesante que te hayas dado cuenta en la práctica, acabo de leer que esto se debe a que las clases de estilo nuevo, una vez que han encontrado el atributo en la instancia dict, tienen que hacer una búsqueda adicional para determinar si es una descripción, es decir, tiene un obtener el método que debe invocarse para obtener el valor que se devolverá. Las clases de estilo antiguas simplemente devuelven el objeto encontrado sin cálculos de suma (pero luego no admiten descriptores). Puede leer más en esta excelente publicación de Guido python-history.blogspot.co.uk/2010/06/… , específicamente la sección sobre tragamonedas
xuloChavez
1
no parece ser cierto con 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
Todavía más rápido para aobj en CPython 2.7.2 en Linux x86-64 para mí.
xioxox
41
Probablemente sea una mala idea confiar en el código Python puro para aplicaciones sensibles al rendimiento. Nadie dice: "Necesito un código rápido, así que usaré las clases antiguas de Python". Numpy no cuenta como Python puro.
Phillip Cloud
También en IPython 2.7.6, esto no es cierto. '' '' 477 ns vs. 456 ns por ciclo '' ''
kmonsoor
37

Guido ha escrito The Inside Story en New-Style Classes , un artículo realmente genial sobre la clase de estilo nuevo y antiguo en Python.

Python 3 solo tiene una clase de estilo nuevo. Incluso si escribe una 'clase de estilo antiguo', se deriva implícitamente de object.

Las clases de estilo nuevo tienen algunas características avanzadas que faltan en las clases de estilo antiguo, como superel nuevo C3 mro , algunos métodos mágicos, etc.

Xiao Hanyu
fuente
24

Aquí hay una muy práctica, verdadera / falsa diferencia. La única diferencia entre las dos versiones del siguiente código es que en la segunda versión, la Persona hereda del objeto . Aparte de eso, las dos versiones son idénticas, pero con resultados diferentes:

  1. Clases de estilo antiguo

    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. Clases de nuevo 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
fuente
2
¿Qué hace '_names_cache'? ¿Podrías compartir una referencia?
Muatik
44
_names_cachees un diccionario que almacena en caché (almacena para la recuperación futura) cada nombre que le pase Person.__new__. El método setdefault (definido en cualquier diccionario) toma dos argumentos: una clave y un valor. Si la clave está en el dict, devolverá su valor. Si no está en el dict, lo establecerá primero en el valor pasado como segundo argumento y luego lo devolverá.
ychaouche
44
El uso es incorrecto. La idea es no construir un nuevo objeto si ya existe, pero en su caso __new__()siempre se llama, y ​​siempre construye un nuevo objeto, y luego lo arroja. En este caso ifes preferible a .setdefault().
Amit Upadhyay
Pero, no entendí por qué la diferencia en la salida, es decir, en la clase de estilo anterior, las dos instancias eran diferentes, por lo tanto, se devolvió Falso, pero en la nueva clase de estilo, ambas instancias son iguales. Cómo ? ¿Cuál es el cambio en la nueva clase de estilo, que hizo las dos instancias iguales, que no estaba en la clase de estilo anterior?
Pabitra Pati
1
@PabitraPati: Es una especie de demostración barata aquí. __new__en realidad no es algo para las clases de estilo antiguo, no se usa en la construcción de instancias (es solo un nombre aleatorio que se ve especial, como definir __spam__). Por lo tanto, construir la clase de estilo antiguo solo invoca __init__, mientras que la construcción de estilo nuevo invoca __new__(fusionándose a instancia de singleton por nombre) para construir e __init__inicializar.
ShadowRanger
10

Las clases de estilo nuevo heredan objecty deben escribirse como tales en Python 2.2 en adelante (es decir, en class Classname(object):lugar de class Classname:). El cambio principal es unificar tipos y clases, y el efecto secundario de esto es que le permite heredar de los tipos incorporados.

Lea la descripción para más detalles.

aureola
fuente
8

Las nuevas clases de estilo pueden usar super(Foo, self)where Fooes una clase y selfes la instancia.

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

Devuelve un objeto proxy que delega llamadas de método a una clase de tipo padre o hermano. Esto es útil para acceder a métodos heredados que se han anulado en una clase. El orden de búsqueda es el mismo que el utilizado por getattr (), excepto que se omite el tipo en sí.

Y en Python 3.x simplemente puede usar super()dentro de una clase sin ningún parámetro.

jamylak
fuente