2012-06-13 15 views
45

¿Existe algún método mágico que pueda sobrecargar al operador de asignación, como __assign__(self, new_value)?¿Es posible sobrecargar la asignación de Python?

me gustaría prohibir a una re-unen para una instancia:

class Protect(): 
    def __assign__(self, value): 
    raise Exception("This is an ex-parrot") 

var = Protect() # once assigned... 
var = 1   # this should raise Exception() 

¿Es posible? ¿Es loco? ¿Debo tomar medicamentos?

+1

¿Cuál es el caso de uso? – msw

+0

Caso de uso: las personas van a escribir pequeños scripts utilizando mi API de servicio, y quiero evitar que cambien datos internos y propaguen este cambio al siguiente script. – Caruccio

+3

Python explícitamente evita prometer que un codificador malicioso o ignorante no podrá acceder. Otros idiomas le permiten evitar algún error de programador debido a su ignorancia, pero las personas tienen una extraña habilidad para codificarlos. – msw

Respuesta

7

No creo que sea posible. La forma en que lo veo, la asignación a una variable no le hace nada al objeto al que se refería anteriormente: simplemente es que la variable "apunta" a un objeto diferente ahora.

In [3]: class My(): 
    ...:  def __init__(self, id): 
    ...:   self.id=id 
    ...: 

In [4]: a = My(1) 

In [5]: b = a 

In [6]: a = 1 

In [7]: b 
Out[7]: <__main__.My instance at 0xb689d14c> 

In [8]: b.id 
Out[8]: 1 # the object is unchanged! 

Sin embargo, puede imitar el comportamiento deseado mediante la creación de un objeto envoltorio con __setitem__() o __setattr__() métodos que elevan una excepción, y mantener las cosas "inmutable" dentro.

25

No, como asignación es language intrinsic que no tiene un gancho de modificación.

+2

Esté seguro, esto no sucederá en Python 4.x. –

+4

Ahora estoy tentado de escribir un PEP para subclases y reemplazar el alcance actual. – zigg

2

No hay no

pensar en ello, en su ejemplo que está REBINDING el nombre var a un nuevo valor. No está realmente tocando la instancia de Protect.

Si el nombre que desea vincular es, de hecho, una propiedad de otra entidad, por ejemplo, myobj.var, puede evitar asignar un valor a la propiedad/atributo de la entidad. Pero supongo que eso no es lo que quiere de su ejemplo.

+0

¡Casi allí! Traté de sobrecargar el '' __dict_______ del mensaje del módulo '' pero el 'module .__ dict__' sí mismo es de solo lectura. Además, escriba (mymodule) == , y no es instanciable. – Caruccio

39

La forma en que la describes no es posible en absoluto. La asignación a un nombre es una característica fundamental de Python y no se han proporcionado ganchos para cambiar su comportamiento.

Sin embargo, la asignación a un miembro en una instancia de clase puede controlarse como desee, anulando .__setattr__().

class MyClass(object): 
    def __init__(self, x): 
     self.x = x 
     self._locked = True 
    def __setattr__(self, name, value): 
     if self.__dict__.get("_locked") and name == "x": 
      raise AttributeError("MyClass does not allow assignment to .x member") 
     self.__dict__[name] = value 

>>> m = MyClass(3) 
>>> m.x 
3 
>>> m.x = 4 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 7, in __setattr__ 
AttributeError: MyClass does not allow assignment to .x member 

Tenga en cuenta que hay una variable miembro, _locked, que controla si se permite la asignación. Puedes desbloquearlo para actualizar el valor.

+1

Usar '@ property' con un getter pero sin setter es una forma similar a la asignación de pseudo-sobrecarga. – jtpereyda

2

En el espacio de nombres global esto no es posible, pero podría aprovechar la metaprogramación más avanzada de Python para evitar que se creen varias instancias de un objeto Protect. El Singleton pattern es un buen ejemplo de esto.

En el caso de un Singleton, se aseguraría de que una vez instanciado, incluso si se reasigna la variable original que hace referencia a la instancia, el objeto persistiría. Cualquier instancia posterior simplemente devolvería una referencia al mismo objeto.

A pesar de este patrón, nunca podrá evitar que se reasigne un nombre de variable global.

+0

Un singleton no es suficiente, ya que 'var = 1' no llama al mecanismo singleton. – Caruccio

+0

Entendido. Me disculpo si no estaba claro. Un singleton evitaría que se creen nuevas instancias de un objeto (por ejemplo, 'Protect()'). No hay forma de proteger el nombre asignado originalmente (por ejemplo, 'var'). – jathanism

1

Una solución fea es reasignar en destructor. Pero no es una tarea de sobrecarga real.

import copy 
global a 

class MyClass(): 
    def __init__(self): 
      a = 1000 
      # ... 

    def __del__(self): 
      a = copy.copy(self) 


a = MyClass() 
a = 1 
2

Utilizando el espacio de nombres de nivel superior, esto es imposible.Cuando se ejecuta

`var = 1` 

Almacena la clave y el valor var1 en el diccionario global. Es más o menos equivalente a llamar al globals().__setitem__('var', 1). El problema es que no se puede reemplazar el diccionario global en un script en ejecución (es probable que se pueda jugar con la pila, pero esa no es una buena idea). Sin embargo, puede ejecutar código en un espacio de nombres secundario y proporcionar un diccionario personalizado para sus globales.

class myglobals(dict): 
    def __setitem__(self, key, value): 
     if key=='val': 
      raise TypeError() 
     dict.__setitem__(self, key, value) 

myg = myglobals() 
dict.__setitem__(myg, 'val', 'protected') 

import code 
code.InteractiveConsole(locals=myg).interact() 

que iniciará un REPL que casi se opera con normalidad, pero niega cualquier intento de establecer la variable val. También puede usar execfile(filename, myg). Tenga en cuenta que esto no protege contra el código malicioso.

+0

¡Esto es magia oscura! Esperaba encontrar solo un montón de respuestas en las que la gente sugiriera usar un objeto explícitamente con un setattr sustituido, no pensar en anular globales y locales con un objeto personalizado, wow. Sin embargo, esto debe hacer llorar a PyPy. –

1

Sí, es posible, puede manejar __assign__ a través de modificar ast.

pip install assign

de prueba con:

class T(): 
    def __assign__(self, v): 
     print('called with %s' % v) 
b = T() 
c = b 

Usted recibirá

>>> import magic 
>>> import test 
called with c 

El proyecto se encuentra en https://github.com/RyanKung/assign Y la esencia más simple: https://gist.github.com/RyanKung/4830d6c8474e6bcefa4edd13f122b4df

+0

Hay algo que no entiendo ... ¿No debería ser 'print ('llamado con% s'% self)'? – zezollo

Cuestiones relacionadas