2009-10-12 28 views
11

Por lo que recuerdo de mi clase de C++, el profesor dijo que la sobrecarga del operador es genial, pero dado que se necesita una gran cantidad de pensamiento y código para cubrir todos los casos finales (por ejemplo, al sobrecargar + es probable que también desee sobrecargar ++ y +=, y también asegurarse de manejar casos extremos como agregar un objeto a sí mismo, etc.), solo debe considerarlo en aquellos casos en que esta característica tenga un gran impacto en su código, como sobrecargar los operadores para la clase de matriz en una aplicación matemática.Reglas para cuándo usar la sobrecarga del operador en python

¿Se aplica lo mismo a python? ¿Recomendarías anular el comportamiento del operador en Python? ¿Y qué reglas básicas me puedes dar?

Respuesta

23

operador es útil sobre todo cuando estás haciendo una nueva clase que cae en una ya existente "clase base abstracta" (ABC) - de hecho, muchos de los ABC en el módulo de biblioteca estándar collections dependen de la presencia de ciertos métodos especiales (y los métodos especiales, uno con nombres que comienzan y terminan con doble subrayado AKA "dunders", son exactamente la forma de realizar la sobrecarga del operador en Python). Esto proporciona una buena guía de partida.

Por ejemplo, una clase Container imprescindible anular método especial __contains__, es decir, el operador de registro de miembros item in container (como en, if item in container: - no confundir con la declaración for, for item in container:, que se basa en __iter__ -!) . mismo modo, un Hashable debe invalidar __hash__, un Sized debe invalidar __len__, una Sequence o una Mapping debe invalidar __getitem__, y así sucesivamente. (Además, el ABC puede proporcionarle a su clase funcionalidad mixin; por ejemplo, tanto Sequence como Mapping pueden proporcionar __contains__ sobre la base de la anulación __getitem__ que usted suministró, y de ese modo automáticamente hacer que su clase sea Container).

Más allá del collections, querrá anular los métodos especiales (es decir, prever la sobrecarga del operador) principalmente si su nueva clase "es un número". Existen otros casos especiales, pero resisten la tentación de sobrecargar a los operadores "solo por frialdad", sin conexión semántica con los significados "normales", como las secuencias de C++ para << y >> y cadenas de Python (en Python 2.*, afortunadamente no en 3.*) más ;-) para % - cuando tales operadores ya no significan "cambio de bit" o "resto de división", estás generando confusión. La biblioteca estándar de un idioma puede salirse con la suya (aunque no debería ;-), pero a menos que su biblioteca se extienda tanto como la estándar del idioma, la confusión dañará! -)

+4

BTW, para aquellos desesperados en el pensamiento de no tener% para el formato de cadena: aunque la documentación de Python 3 describe% como obsoleto, todavía está documentado y parece que no hay posibilidades de que la característica realmente desaparezca hasta Python 4, según las discusiones recientes en python-dev. Eso deja mucho tiempo para aprender y amar el nuevo método de formato de cadena ya disponible en 2.6. –

+0

La función de formato es mucho mejor que% alguna vez – Casebash

12

He escrito software con grandes cantidades de sobrecarga, y últimamente lamento esa política. Quiero decir lo siguiente:

operadores de sobrecarga Sólo si es lo natural, que se espera que hacer y no tiene ningún efecto secundario.

Así que si usted hace una nueva clase RomanNumeral, tiene sentido sobrecargar la suma y resta etc, pero no sobrecargue a menos que sea natural: no tiene sentido definir la suma y resta para un Car o un objeto Vehicle .

Otra regla de oro: no sobrecargar ==. Hace que sea muy difícil (aunque no imposible) probar realmente si dos objetos son iguales. Cometí este error y pagué durante mucho tiempo.

En cuanto a cuándo sobrecargar +=, ++ etc., realmente diría: solo sobrecargue los operadores adicionales si tiene mucha demanda para esa funcionalidad. Es más fácil tener una forma de hacer algo que cinco. Claro, significa que a veces tendrás que escribir x = x + 1 en lugar de x += 1, pero hay más códigos correctos si está claro.

En general, al igual que con muchas características 'fantasiosas', es fácil pensar que quiere algo cuando realmente no, implementar un montón de cosas, no notar los efectos secundarios, y luego descubrirlo más tarde. Err en el lado conservador.

EDIT: Quería añadir una nota explicativa sobre la sobrecarga de ==, porque parece que varios comentaristas lo malinterpretan y me ha sorprendido. Sí, is existe, pero es una operación diferente. Supongamos que tengo un objeto x, que es de mi clase personalizada o es un número entero. Quiero ver si x es el número 500. Pero si configura x = 500, luego pruebe x is 500, obtendrá False, debido a la forma en que Python almacena los números en caché. Con 50, devolvería True. Pero no puede usar is, porque es posible que desee x == 500 para devolver True si x es una instancia de su clase. ¿Confuso? Seguro. Pero este es el tipo de detalle que necesita comprender para sobrecargar exitosamente a los operadores.

+2

Sobrecarga '++' no particularmente aplicar, ya que Python no tiene un operador '++'. –

+0

seguro, cambiaré el ejemplo para Python. (aunque quise explicar el principio general). – Peter

+2

¿No puedes probar si dos objetos son iguales con 'si a es b: ...', incluso si '==' está sobrecargado?¿O estoy malinterpretando el punto que estás haciendo? – sth

3

La sobrecarga de Python es "más segura" en general que la de C++; por ejemplo, el operador de asignación no se puede sobrecargar, y += tiene una implementación predeterminada sensata.

De alguna manera, sin embargo, la sobrecarga en Python sigue siendo tan "quebrada" como en C++. Los programadores deben restringir el deseo de "reutilizar" un operador para fines no relacionados, como C++ reutilizando los cambios de bits para realizar el formateo de cadenas y el análisis sintáctico.No sobrecargues a un operador con una semántica diferente de la implementación solo para obtener una sintaxis más bonita.

El estilo moderno de Python desaconseja la sobrecarga "pícara", pero muchos aspectos del lenguaje y la biblioteca estándar retienen a los operadores con un nombre deficiente para compatibilidad con versiones anteriores. Por ejemplo:

  • %: módulo y el formato de cadenas
  • +: la suma y la secuencia de concatenación
  • *: la multiplicación y la repetición de secuencia

Así, regla de oro? Si la implementación de su operador sorprenderá a las personas, no lo haga.

+0

Creo que se merecen los usos duales de '+' y '*'; ambos usos al menos hacen lo mismo conceptual, incluso si lo hacen de manera diferente. –

+1

No son para nada iguales. '(1 + 2) == (2 + 1)', pero '(" a "+" b ")! = (" B "+" a ")'. '(1 + 2-1) == 2', pero' ("a" + "b" - "a") 'es una tontería. El mismo tipo de problemas existen para la multiplicación. –

+3

No quise decir que eran lo mismo, pero lo dije muy mal. Quise decir que conceptualmente realizan acciones similares. Creo que la mayoría de la gente diría que unir dos cadenas y agregar dos números como operaciones "aditivas", y que multiplicar números y repetir una cadena varias veces son operaciones "multiplicativas". –

5

Aquí hay un ejemplo que usa la operación en modo bit para simular una canalización de Unix. Esto tiene la intención de ser un contraejemplo para la mayoría de las reglas generales.

me acaba de encontrar Lumberjack que utiliza esta sintaxis en código real sobrecarga



class pipely(object): 
    def __init__(self, *args, **kw): 
     self._args = args 
     self.__dict__.update(kw) 

    def __ror__(self, other): 
     return (self.map(x) for x in other if self.filter(x)) 

    def map(self, x): 
     return x 

    def filter(self, x): 
     return True 

class sieve(pipely): 
    def filter(self, x): 
     n = self._args[0] 
     return x==n or x%n 

class strify(pipely): 
    def map(self, x): 
     return str(x) 

class startswith(pipely): 
    def filter(self, x): 
     n=str(self._args[0]) 
     if x.startswith(n): 
      return x 

print"*"*80 
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | strify() | startswith(5): 
    print i 

print"*"*80 
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | startswith(5): 
    print i 

print"*"*80 
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | pipely(filter=lambda x: x.startswith('5')): 
    print i 

+0

+1 porque es muy interesante, aunque no sé si apruebo el uso de este en código real. –

+2

:) Yo admin. No lo he usado en código real, pero es una forma práctica de encadenar generadores juntos. Puede hacer algo similar con co-rutinas, pero la sintaxis se parece más a ' tamiz (2, tamiz (3, tamiz (5, tamiz (7))))' que no me gusta más –