Same name for classmethod and instancemethod?

I would like to do something like this:

class X:

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

    def id(self):
        return self.__class__.__name__

And now call id()for the class or instance it:

>>> X.id()
'X'
>>> X().id()
'X'

Obviously this exact code doesn't work, but is there a similar way to make it work? Or any other workarounds to get this behavior without unnecessary "hacker" things?

+9
source share
6 answers

Class and instance methods are in the same namespace, and you cannot reuse such names; in this case the last definition wins id.

, ; :

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

:

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

:

(, C.f()), (, C().f()). .

-, , ; , , .

API - , Python classmethod ; . howto.

, __get__. , , , __get__ None, , :

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_)

classmethod , , instance is None, __get__.

, , , , . isinstance(firstargument, type) :

>>> 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, <class '__main__.X'>"
>>> X().foo()
'bound to the instance, <__main__.X object at 0x10ac7d580>'

, , :

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)

. , property; @<name>.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, <class '__main__.X'>"
>>> X().bar()
'bound to the instance, <__main__.X object at 0x10a010f70>'

; , , . , SQLAlchemy SQL SQL, ; . . , hybridmethod .

+11

, , - :

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()

:

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

, , ( ). Python , Class().__dict__, Class().foo() ( __dict__ ') -, Class.__dict__, Class.foo().

, , -, :

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

... , , 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']]
+8

"types" - , Python 3.4: DynamicClassAttribute

100% , , , , , , , , , ;

from types import DynamicClassAttribute

class XMeta(type):
     def __getattr__(self, value):
         if value == 'id':
             return XMeta.id  # You may want to change a bit that line.
     @property
     def id(self):
         return "Class {}".format(self.__name__)

. :

class X(metaclass=XMeta):
    @DynamicClassAttribute
    def id(self):
        return "Instance {}".format(self.__class__.__name__)

, . , , , , !

>>> X().id
'Instance X'
>>> X.id
'Class X'

...

+3

, staticmethod, class .

, :

class X:
    def id(self=None):
       if self is None:
           # It being called as a static method
       else:
           # It being called as an instance method
+1

(Python 3 only) Developing the idea of implementing @classmethodin pure Python , we can declare @class_or_instance_methodas a decorator, which is actually a class that implements the attribute descriptor protocol :

import inspect


class class_or_instance_method(object):

    def __init__(self, f):
        self.f = f

    def __get__(self, instance, owner):
        if instance is not None:
            wrt = instance
        else:
            wrt = owner

        def newfunc(*args, **kwargs):
            return self.f(wrt, *args, **kwargs)
        return newfunc

class A:
    @class_or_instance_method
    def foo(self_or_cls, a, b, c=None):
        if inspect.isclass(self_or_cls):
            print("Called as a class method")
        else:
            print("Called as an instance method")
+1
source

All Articles