2010-04-17 18 views
83

He estado jugando con Python recientemente, y una cosa que me resulta un poco extraña es el uso extensivo de 'métodos mágicos', p. para hacer que su longitud esté disponible, un objeto implementa un método def __len__(self) y luego se llama cuando se escribe len(obj).¿Por qué Python usa 'métodos mágicos'?

Me preguntaba por qué los objetos no definen simplemente un método len(self) y lo llaman directamente como miembro del objeto, p. obj.len()? Estoy seguro de que debe haber buenas razones para que Python lo haga de la forma en que lo hace, pero como novato aún no he descubierto lo que son.

+4

creo que la razón general es a) histórico y b) algo como 'len()' o 'invertida()' se aplica a muchos tipos de objetos , pero un método como 'append()' solo se aplica a secuencias, etc. –

Respuesta

55

AFAIK, len es especial a este respecto y tiene raíces históricas.

He aquí una cita from the FAQ:

Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))?

The major reason is history. Functions were used for those operations that were generic for a group of types and which were intended to work even for objects that didn’t have methods at all (e.g. tuples). It is also convenient to have a function that can readily be applied to an amorphous collection of objects when you use the functional features of Python (map(), apply() et al).

In fact, implementing len(), max(), min() as a built-in function is actually less code than implementing them as methods for each type. One can quibble about individual cases but it’s a part of Python, and it’s too late to make such fundamental changes now. The functions have to remain to avoid massive code breakage.

Los otros métodos "mágicos" (en realidad llamados método especial en el folklore de Python) hacer un montón de sentido, y existe una funcionalidad similar en otros idiomas. Se utilizan principalmente para el código que se llama implícitamente cuando se utiliza la sintaxis especial.

Por ejemplo:

  • operadores sobrecargados (existen en C++ y otros)
  • constructor/destructor
  • ganchos para los atributos con el acceso
  • herramientas para metaprogramming

y así sucesivamente ...

+2

Oh cariño. Ahora me siento un poco tonto por hacer una pregunta respondida en las preguntas frecuentes. Supongo que debería ir a leer el resto de ellos, así que no pregunto nada más que haya sido respondido allí: -S –

+12

@Greg: la gran cantidad de votos ascendentes y estrellas "favoritas" demuestran que mucha gente lo encontró interesante, así que no pienses que tienes que sentirte tonto al respecto :-) –

+1

[Python y el Principio de Menos Asombro] (http://lucumr.pocoo.org/2011/7/9/python-and-pola/) es un Una buena lectura de algunas de las ventajas de que Python sea de esta manera (aunque admito que el inglés necesita trabajo). El punto básico: permite que la biblioteca estándar implemente una gran cantidad de códigos que se vuelven muy, muy reutilizables pero que aún se pueden cancelar. – jpmc26

19

Desde el Zen de Python:

In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.

Ésta es una de las razones - con métodos personalizados, los desarrolladores tendrán la libertad de elegir un nombre de método diferente, como getLength(), length(), getlength() o en absoluto. Python impone nombres estrictos para que se pueda usar la función común len().

Todas las operaciones que son comunes para muchos tipos de objetos se ponen en métodos mágicos, como __nonzero__, __len__ o __repr__. Sin embargo, en su mayoría son opcionales.

La sobrecarga del operador también se realiza con métodos mágicos (por ejemplo, __le__), por lo que tiene sentido usarlos para otras operaciones comunes, también.

+0

Este es un argumento convincente. Más satisfactorio que "Guido realmente no creía en OO" .... (como he visto en otro lugar). –

4

No son realmente "nombres mágicos". Es solo la interfaz que un objeto debe implementar para proporcionar un servicio determinado. En este sentido, no son más mágicos que cualquier definición de interfaz predefinida que deba volver a implementar.

7

Algunas de estas funciones hacen más de lo que un único método sería capaz de implementar (sin métodos abstractos en una superclase). Por ejemplo bool() actúa un poco como esto:

def bool(obj): 
    if hasattr(obj, '__nonzero__'): 
     return bool(obj.__nonzero__()) 
    elif hasattr(obj, '__len__'): 
     if obj.__len__(): 
      return True 
     else: 
      return False 
    return True 

También puede estar 100% seguro de que bool() siempre devolverá verdadero o falso; si confiabas en un método, no podías estar completamente seguro de lo que obtendrías.

Algunas otras funciones que tienen implementaciones relativamente complicados (más complicados que los métodos mágicos subyacentes es probable que sean) son iter() y cmp(), y todos los métodos de atributos (getattr, setattr y delattr). Las cosas como int también acceden a los métodos mágicos cuando se hace coerción (puede implementar __int__), pero hacen doble función como tipos. len(obj) es en realidad el único caso en el que no creo que sea diferente de obj.__len__().

+2

En lugar de 'hasattr()' Yo usaría 'try:'/'excepto AttributeError:' y en lugar de 'if obj .__ len __(): return True else: return False' Solo diría' return obj .__ len __ ()> 0' pero esas son solo cosas estilísticas. –

+0

En python 2.6 (que btw 'bool (x)' se refería a 'x .__ nonzero __()'), su método no funcionaría. Las instancias de bool tienen un método '__nonzero __()', y tu código seguiría llamándose una vez que obj fuera un bool. Quizás 'bool (obj .__ bool __())' debería tratarse de la misma manera que trataste a '__len__'? (¿O este código funciona realmente para Python 3?) – Ponkadoodle

+0

La naturaleza circular de bool() era de alguna manera intencionalmente absurda, para reflejar la naturaleza peculiarmente circular de la definición. Hay un argumento de que simplemente debería considerarse un primitivo. –

0

No hay mucho que agregar a las dos publicaciones anteriores, pero todas las funciones "mágicas" no son realmente mágicas en absoluto. Son parte del módulo __builtins__ que se importa de forma implícita o automática cuando se inicia el intérprete. IE:

from __builtins__ import * 

ocurre siempre antes de que se inicie el programa.

Siempre pensé que sería más correcto si python solo hiciera esto para el shell interactivo, y requería scripts para importar las diversas partes de los builtins que necesitaban. También probablemente sea diferente __ main__ handling sería bueno en shells vs interactive. De todas formas, echa un vistazo a todas las funciones, y ver lo que es como sin ellos:

dir (__builtins__) 
... 
del __builtins__ 
1

Mientras que la razón es sobre todo histórica, hay algunas peculiaridades en Python de len que hacen que el uso de una función en lugar de un método apropiado .

Algunas operaciones en Python se implementan como métodos, por ejemplo list.index y dict.append, mientras que otros se implementan como callables y métodos mágicos, por ejemplo str y iter y reversed. Los dos grupos difieren lo suficiente, por lo que el enfoque diferente está justificado:

  1. Son comunes.
  2. str, int y sus amigos son tipos. Tiene más sentido llamar al constructor.
  3. La implementación difiere de la llamada de función. Por ejemplo, iter podría llamar al __getitem__ si __iter__ no está disponible, y es compatible con argumentos adicionales que no caben en una llamada de método. Por la misma razón, it.next() ha sido cambiado a next(it) en versiones recientes de Python, tiene más sentido.
  4. Algunos de estos son parientes cercanos de los operadores. Hay sintaxis para llamar a __iter__ y __next__ - se llama el bucle for. Para consistencia, una función es mejor. Y lo hace mejor para ciertas optimizaciones.
  5. Algunas de las funciones son simplemente demasiado similares al resto de alguna manera - repr actúa como str hace. Tener str(x) versus x.repr() sería confuso.
  6. Algunos de ellos raramente usan el método de implementación real, por ejemplo isinstance.
  7. Algunos de ellos son operadores reales, getattr(x, 'a') es otra forma de hacer x.a y getattr comparte muchas de las cualidades antes mencionadas.

Personalmente llamo al primer grupo como método y al segundo grupo como operador. No es una distinción muy buena, pero espero que ayude de alguna manera.

Habiendo dicho esto, len no encaja exactamente en el segundo grupo. Está más cerca de las operaciones en la primera, con la única diferencia de que es mucho más común que casi ninguna de ellas. Pero lo único que hace es llamar al __len__, y está muy cerca de L.index. Sin embargo, hay algunas diferencias. Por ejemplo, podría llamarse al __len__ para la implementación de otras características, como bool, si el método se llamó len, podría romper bool(x) con el método personalizado len que hace algo completamente diferente.

En resumen, tiene un conjunto de características muy comunes que las clases pueden implementar a las que se puede acceder a través de un operador, mediante una función especial (que normalmente hace más que la implementación, como lo haría un operador) durante la construcción del objeto. y todos ellos comparten algunos rasgos comunes. Todo el resto es un método. Y len es algo así como una excepción a esa regla.

13

Python usa la palabra: - "Métodos mágicos" porque, esos métodos realmente realizan magia para su programa. Una de las mayores ventajas de usar los métodos mágicos de Python es que proporcionan una forma simple de hacer que los objetos se comporten como tipos incorporados. Eso significa que puede evitar formas feas, contraintuitivas y no estándar de realizar operadores básicos.

Consideremos un ejemplo siguiente: -

dict1 = {1 : "ABC"} 
dict2 = {2 : "EFG"} 

dict1 + dict2 
Traceback (most recent call last): 
    File "python", line 1, in <module> 
TypeError: unsupported operand type(s) for +: 'dict' and 'dict' 

Esto da un error, porque el tipo de diccionario no admite la adición. Ahora, vamos a extender la clase diccionario y añadir "__add__" método mágico: -

class AddableDict(dict): 

    def __add__(self, otherObj): 
     self.update(otherObj) 
     return AddableDict(self) 


dict1 = AddableDict({1 : "ABC"}) 
dict2 = AddableDict({2 : "EFG"}) 

print (dict1 + dict2) 

Ahora, se da salida siguiente,

{1: 'ABC', 2: 'EFG'} 

Por lo tanto, mediante la adición de este método, de repente la magia ha ocurrido y el error que estaba recibiendo antes, se ha ido.

Espero que te aclare las cosas. Para obtener más información, diríjase al link que figura a continuación: -

http://web.archive.org/web/20161024123835/http://www.rafekettler.com/magicmethods.html

Cuestiones relacionadas