Saltar al contenido

Mismo nombre para método de clase y método de instancia

Ya no tienes que indagar más por todo internet ya que llegaste al lugar adecuado, poseemos la respuesta que necesitas pero sin complicarte.

Solución:

Los métodos de clase e instancia viven en el mismo espacio de nombres y no puede reutilizar nombres como ese; la última definición de id ganará en ese caso.

El método de clase seguirá funcionando en instancias, sin embargo, hay no hay necesidad para crear un método de instancia independiente; Solo usa:

class X:
    @classmethod
    def id(cls):
        return cls.__name__

porque el método sigue vinculado a la clase:

>>> class X:
...     @classmethod
...     def id(cls):
...         return cls.__name__
... 
>>> X.id()
'X'
>>> X().id()
'X'

Esto está documentado explícitamente:

Se puede llamar en la clase (como C.f()) o en una instancia (como C().f()). La instancia se ignora a excepción de su clase.

Si necesita distinguir entre el enlace a la clase y una instancia

Si necesita que un método funcione de manera diferente según el lugar en el que se esté utilizando; enlazado a una clase cuando se accede en la clase, enlazado a la instancia cuando se accede en la instancia, necesitará crear un objeto descriptor.

La API de descriptor es cómo Python hace que las funciones se vinculen como métodos y se vinculen classmethod objetos a la clase; vea el descriptor cómo.

Puede proporcionar su propio descriptor para los métodos creando un objeto que tenga un __get__ método. Aquí hay uno simple que cambia a qué está vinculado el método según el contexto, si el primer argumento para __get__ es None, entonces el descriptor se vincula a una clase; de ​​lo contrario, se vincula a una instancia:

class class_or_instancemethod(classmethod):
    def __get__(self, instance, type_):
        descr_get = super().__get__ if instance is None else self.__func__.__get__
        return descr_get(instance, type_)

Esto reutiliza classmethod y solo redefine cómo maneja el enlace, delegando la implementación original para instance is None, y a la función estándar __get__ implementación de otra manera.

Tenga en cuenta que en el método en sí, es posible que deba probar a qué está destinado. isinstance(firstargument, type) es una buena prueba para esto:

>>> class X:
...     @class_or_instancemethod
...     def foo(self_or_cls):
...         if isinstance(self_or_cls, type):
...             return f"bound to the class, self_or_cls"
...         else:
...             return f"bound to the instance, {self_or_cls"
...
>>> X.foo()
"bound to the class, "
>>> X().foo()
'bound to the instance, <__main__.X object at 0x10ac7d580>'

Una implementación alternativa podría usar dos funciones, una para cuando está vinculada a una clase, la otra cuando está vinculada a una instancia:

class hybridmethod:
    def __init__(self, fclass, finstance=None, doc=None):
        self.fclass = fclass
        self.finstance = finstance
        self.__doc__ = doc or fclass.__doc__
        # support use on abstract base classes
        self.__isabstractmethod__ = bool(
            getattr(fclass, '__isabstractmethod__', False)
        )

    def classmethod(self, fclass):
        return type(self)(fclass, self.finstance, None)

    def instancemethod(self, finstance):
        return type(self)(self.fclass, finstance, self.__doc__)

    def __get__(self, instance, cls):
        if instance is None or self.finstance is None:
              # either bound to the class, or no instance method available
            return self.fclass.__get__(cls, None)
        return self.finstance.__get__(instance, cls)

Este es un método de clase con un método de instancia opcional. Úselo como si fuera un property objeto; decorar el método de instancia con @.instancemethod:

>>> class X:
...     @hybridmethod
...     def bar(cls):
...         return f"bound to the class, cls"
...     @bar.instancemethod
...     def bar(self):
...         return f"bound to the instance, self"
... 
>>> X.bar()
"bound to the class, "
>>> X().bar()
'bound to the instance, <__main__.X object at 0x10a010f70>'

Personalmente, mi consejo es tener cuidado al usar esto; El uso del mismo método que modifica el comportamiento en función del contexto puede resultar confuso. Sin embargo, existen casos de uso para esto, como la diferenciación de SQLAlchemy entre objetos SQL y valores SQL, donde los objetos de columna en un modelo cambian el comportamiento de este; ver su Atributos híbridos documentación. La implementación para esto sigue exactamente el mismo patrón que mi hybridmethod clase anterior.

No tengo idea de cuál es su caso de uso real, pero puede hacer algo como esto usando un descriptor:

class Desc(object):

    def __get__(self, ins, typ):
        if ins is None:
            print 'Called by a class.'
            return lambda : typ.__name__
        else:
            print 'Called by an instance.'
            return lambda : ins.__class__.__name__

class X(object):
    id = Desc()

x = X()
print x.id()
print X.id()

Producción

Called by an instance.
X
Called by a class.
X

Eso pueden hacerse, de manera bastante sucinta, vinculando la versión vinculada a la instancia de su método explícitamente para la instancia (en lugar de la clase). Python invocará la instancia attribute encontrado en Class().__dict__ cuando Class().foo() se llama (porque busca la instancia __dict__ antes de la clase ‘), y el método enlazado a clase que se encuentra en Class.__dict__ cuando Class.foo() se llama.

Esto tiene varios casos de uso potenciales, aunque si son antipatrones está abierto a debate:

class Test:
    def __init__(self):
        self.check = self.__check

    @staticmethod
    def check():
        print('Called as class')

    def __check(self):
        print('Called as instance, probably')

>>> Test.check()
Called as class
>>> Test().check()
Called as instance, probably

O … digamos que queremos poder abusar de cosas como map():

class Str(str):
    def __init__(self, *args):
        self.split = self.__split

    @staticmethod
    def split(sep=None, maxsplit=-1):
        return lambda string: string.split(sep, maxsplit)

    def __split(self, sep=None, maxsplit=-1):
        return super().split(sep, maxsplit)

>>> s = Str('w-o-w')
>>> s.split('-')
['w', 'o', 'w']
>>> Str.split('-')(s)
['w', 'o', 'w']
>>> list(map(Str.split('-'), [s]*3))
[['w', 'o', 'w'], ['w', 'o', 'w'], ['w', 'o', 'w']]

Valoraciones y comentarios

Recuerda que puedes dar recomendación a esta reseña si te valió la pena.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *