Python lists / arrays: deshabilita el enrollamiento de la indexación negativa en segmentos

Si bien el ajuste del número negativo (es decir, A[-2] indexa el elemento del segundo al último) es extremadamente útil en muchos casos, cuando ocurre dentro de una división, suele ser más molesto que una característica útil, y a menudo deseo de una manera de deshabilitar ese comportamiento particular.

Aquí hay un ejemplo 2D en lata a continuación, pero he tenido la misma molestia unas cuantas veces con otras estructuras de datos y en otros números de dimensiones.

 import numpy as np A = np.random.randint(0, 2, (5, 10)) 

introduzca la descripción de la imagen aquí

 def foo(i, j, r=2): '''sum of neighbours within r steps of A[i,j]''' return A[ir:i+r+1, jr:j+r+1].sum() 

En la división anterior, preferiría que cualquier número negativo de la división se tratara del mismo modo que None , en lugar de ajustarse al otro extremo de la matriz.

Debido a la envoltura, la implementación agradable de lo contrario proporciona resultados incorrectos en condiciones de contorno y requiere algún tipo de parche como:

 def ugly_foo(i, j, r=2): def thing(n): return None if n < 0 else n return A[thing(ir):i+r+1, thing(jr):j+r+1].sum() 

También he intentado rellenar con cero la matriz o la lista, pero todavía es poco elegante (requiere ajustar los índices de ubicación de búsqueda en consecuencia) e ineficiente (requiere copiar la matriz).

¿Me estoy perdiendo algún truco estándar o una solución elegante para rebanar así? Noté que Python y Numpy ya manejan el caso en el que se especifica un número demasiado grande, es decir, si el índice es mayor que la forma de la matriz, se comporta igual que si fuera None .

Supongo que tendría que crear su propio contenedor de subclase alrededor de los objetos deseados y volver a implementar __getitem__() para convertir claves negativas a None , y luego llamar a la superclase __getitem__

Tenga en cuenta que lo que estoy sugiriendo es subclasificar las clases personalizadas, pero NO las integradas como list o dict . Esto es simplemente para hacer una utilidad alrededor de otra clase, no para confundir las operaciones normales esperadas de un tipo de list . Sería algo que desearía usar dentro de un cierto contexto durante un período de tiempo hasta que sus operaciones estén completas. Es mejor evitar realizar un cambio global diferente que confundirá a los usuarios de su código.

Modelo de datos

objeto. getitem (self, key)
Llamado a implementar la evaluación de uno mismo [clave]. Para los tipos de secuencia, las claves aceptadas deben ser enteros y objetos de sector. Tenga en cuenta que la interpretación especial de los índices negativos (si la clase desea emular un tipo de secuencia) depende del método getitem (). Si la clave es de un tipo inapropiado, TypeError puede ser levantado; si tiene un valor fuera del conjunto de índices para la secuencia (después de cualquier interpretación especial de valores negativos), IndexError debe elevarse. Para los tipos de mapeo, si falta una clave (que no esté en el contenedor), KeyError debe aparecer.

Incluso podría crear un envoltorio que simplemente tome una instancia como un argumento, y simplemente difiere todas las llamadas __getitem__() a ese miembro privado, mientras convierte la clave, para los casos en los que no puede o no desea subclasificar un tipo, y en su lugar solo desea un contenedor de utilidades para cualquier objeto de secuencia.

Ejemplo rápido de la última sugerencia:

 class NoWrap(object): def __init__(self, obj, default=None): self._obj = obj self._default = default def __getitem__(self, key): if isinstance(key, int): if key < 0: return self._default return self._obj.__getitem__(key) In [12]: x = range(-10,10) In [13]: x_wrapped = NoWrap(x) In [14]: print x_wrapped[5] -5 In [15]: print x_wrapped[-1] None In [16]: x_wrapped = NoWrap(x, 'FOO') In [17]: print x_wrapped[-1] FOO 

Si bien puede hacer una subclase, por ejemplo, la list como lo sugiere jdi, el comportamiento de corte de Python no es algo con lo que nadie vaya a esperar.

Es probable que el cambio de la misma lleve a que otras personas que trabajan con su código se rasquen seriamente la cabeza cuando no se comporta como se esperaba, y puede tomar un tiempo antes de que examinen los métodos especiales de su subclase para ver qué sucede realmente. en.

Ver: Acción a distancia.

Creo que esto no es lo suficientemente feo como para justificar nuevas clases y envolver cosas. Entonces otra vez es tu código.

 def foo(i, j, r=2): '''sum of neighbours within r steps of A[i,j]''' return A[ir:abs(i+r+1), jr:abs(j+r+1)].sum() # ugly, but works? 

(La votación descendente es divertida, así que he agregado algunas opciones más)

Descubrí algo bastante inesperado (para mí): ¡el __getslice__(i,j) no se ajusta! En cambio, los índices negativos son ignorados, así que:

lst[1:3] == lst.__getslice__(1,3)

lst[-3:-1] == 2 next to last items pero lst.__getslice__(-3,-1) == []

y finalmente:

lst[-2:1] == [] , pero lst.__getslice__(-2,1) == lst[0:1]

Sorprendente, interesante, y completamente inútil.

Si esto solo necesita aplicarse en unas pocas operaciones específicas, un simple y directo if index>=0: do_something(array[i]) / if index<0: raise IndexError haría.

Si esto tiene que aplicarse de forma más amplia, sigue siendo la misma lógica, simplemente envolviéndose de esta manera u otra.