2009-05-14 13 views
26

Me pregunto si es posible hacer un método que se comporta de manera diferente cuando se llama como un método de clase que cuando se llama como un método de instancia.¿Un método de clase que se comporta de manera diferente cuando se llama como un método de instancia?

Por ejemplo, como proyecto de mejora de habilidades, estoy escribiendo una clase Matrix (sí, sé que ya hay clases de matriz perfectamente buenas). Creé un método de clase llamado identity que devuelve una matriz de identidad de un tamaño específico.

Ahora, cuando se invoca en una instancia de Matrix, parece lógico que no se deba especificar el tamaño; debe devolver una matriz de identidad del mismo tamaño que el Matrix en el que se invoca.

En otras palabras, me gustaría definir un método que pueda determinar si se llamó a través de una instancia y, de ser así, acceder a los atributos de esa instancia. Desafortunadamente, incluso después de buscar en la documentación y en algunas búsquedas de Google, no encontré nada que sugiera que esto sea posible. ¿Alguien sabe de manera diferente?

Editar:

Wow! Claramente, todavía no estoy acostumbrado a las funciones de primera clase. Esto es lo que terminé con: ¡gracias a Desconocido por proporcionar la clave!

class Foo(object): 
    def __init__(self, bar): 
     self.baz = bar 
     self.bar = MethodType(lambda self: self.__class__.bar(self.baz), self, self.__class__) 

    @classmethod 
    def bar(cls, baz): 
     return 5 * baz 

Foo.bar(3) # returns 15 

foo = Foo(7) 
foo.bar() # returns 35 

Edición 2:

Sólo una pequeña nota - esta técnica (y la mayoría de los que se presentan a continuación) no funcionará en las clases que definen __slots__, como no se puede reasignar el método.

+0

La pregunta que se le hizo a un objeto único que se puede llamar que podría detectar cómo se llamó. (Esto debería ser posible utilizando el módulo de inspección para obtener el marco de llamada). La 'solución' presentada es crear un segundo método obligatorio, vinculado, que está vinculado a cada instancia con el mismo nombre que el primer llamante. Esto puede resolver el problema de cómo tener clases y 'métodos de instancia' con el mismo nombre. Pero no es lo mismo que tener un único invocable que pueda actuar como cualquiera de los dos. –

+0

@TerryJanReedy ¿es posible inspeccionar el marco de llamada en tiempo de ejecución? – aayush

+0

Sí, esto es lo que quise decir con "use el módulo de inspección para obtener el marco de llamada". Hay una o más funciones para esto. –

Respuesta

38

Posiblemente útil Los hacks de Python son mi fuerte.

from types import * 

class Foo(object): 
    def __init__(self): 
     self.bar = methodize(bar, self) 
     self.baz = 999 

    @classmethod 
    def bar(cls, baz): 
     return 2 * baz 


def methodize(func, instance): 
    return MethodType(func, instance, instance.__class__) 

def bar(self): 
    return 4*self.baz 


>>> Foo.bar(5) 
10 
>>> a=Foo() 
>>> a.bar() 
3996 
+9

Kudos, y un voto favorable, por el esfuerzo en el ejemplo del código, y especialmente por la frase "los pirates Python cuestionablemente útiles son mi fuerte" :-) –

+0

@Jarret, no es solo una cita para mostrar tampoco. Vea mis otros, tales como: http://stackoverflow.com/questions/813882/is-there-a-way-to-check-whether-function-output-is-assigned-to-a-variable-in- pyth/813938 # 813938 – Unknown

+1

Ok, eso es bueno. ¿Ahora cómo sacas esto? – Neoecos

3

Creo que el problema más grande es que estás sobrecargando el nombre 'bar' en la clase 'Foo', algo que Python no permite. La segunda definición de 'barra' corta la primera definición de 'barra'.

Trate de pensar en nombres únicos para su método classmethod y instancia. es decir

@classmethod 
def create(cls, baz): 
    ... 

def rubber_stamp(self): 
    ... 
+1

+1: Y, por supuesto, el factor de confusión inherente al comportamiento variante, incluso si está "estrechamente relacionado". –

+0

¿Y su sugerencia de corregir el "código conceptual que no funciona" que el OP ya identificó como un gran problema con el que necesita ayuda? –

+2

IMO, el código de concepto que no funciona es una mala idea; la solución es volver a pensar la API. No hay "solución". –

7

[editado: atributo Uso de ser una respuesta más directa; Ver el comentario como útiles por John Fouhy]

Puede utilizar un descriptor de hacer lo que quiere:

class cls_or_inst_method(object): 
    def __init__(self, class_method, instance_method): 
     self.class_method = class_method 
     self.instance_method = instance_method 

    def __get__(self, obj, objtype): 
     if obj is None: 
      return self.class_method 
     else: 
      return lambda: self.instance_method(obj) 

def my_class_method(baz): 
    return baz + 1 

def my_instance_method(self): 
    return self.baz * 2 

class Foo(object): 
    baz = 10 
    bar = cls_or_inst_method(my_class_method, my_instance_method) 

Utilizando lo anterior:

>>> print Foo.bar(5) 
6 
>>> my_foo = Foo() 
>>> print my_foo.bar() 
20 
+1

Para ser una solución, el método de instancia debe hacer referencia a self.baz y no tomar ningún argumento (excepto el yo), creo que ... –

7

@Unknown ¿Cuál es la diferencia entre sus de y esto:

class Foo(object): 

    def _bar(self, baz): 
     print "_bar, baz:", baz 

    def __init__(self, bar): 
     self.bar = self._bar 
     self.baz = bar 

    @classmethod 
    def bar(cls, baz): 
     print "bar, baz:", baz 

In [1]: import foo 

In [2]: f = foo.Foo(42) 

In [3]: f.bar(1) 
_bar, baz: 1 

In [4]: foo.Foo.bar(1) 
bar, baz: 1 

In [5]: f.__class__.bar(1) 
bar, baz: 1 
+0

¡Oh! Gracias, Sr. Desconocido :-) – Ale

+1

Bueno, la diferencia es que puede enlazar cualquier función que desee como método. Solo aproveché esta oportunidad para mostrar ese truco. Y también que no hay ningún f._bar() colgado – Unknown

+4

@Unknown: si el '' colgante 'f._bar()' es un problema, entonces simplemente defínalo usando una función anidada o lambda dentro del '__init __()' This el código aquí es intuitivamente obvio cómo funciona, mientras que la otra solución implica el vudú de Python oscuro, sin mencionar las funciones de nivel superior visibles del módulo 'methodize()' y 'bar()' –

2

Puede reasignar su método de identidad en init con función lambda corta:

class Matrix(object): 
    def __init__(self): 
     self.identity = lambda s=self:s.__class__.identity(s) 

     #...whatever initialization code you have... 
     self.size = 10   

    @classmethod 
    def identity(self, other): 
     #...always do you matrix calculations on 'other', not 'self'... 
     return other.size 


m = Matrix() 
print m.identity() 
print Matrix.identity(m) 

Si no está familiarizado con lambda, se crea una función anónima. Rara vez es necesario, pero puede hacer que su código sea más conciso. La línea lambda anterior podría reescribirse:

def identity(self): 
     self.__class__.indentity(self) 
    self.identity = identity 
Cuestiones relacionadas