Reputation: 90
What I want to do seems weird, in short, I am trying to build a new class and copy instance/static/class methods from another class without doing inheritance (i.e., I only want a class with only a few methods). As I looked it up on the web, it seems people were suggesting what I did in illustration (New Class), but as I tried to test it, the class method declared in the new class seems to still point to the previous class (Old Class).
This is what I did:
class OldClass(object):
current_count = 1
def __init__(self):
pass
@classmethod
def method_class(cls):
cls.current_count += 1
def method_normal(self):
return 2
# new class
class NewClass(object):
method_class_copy = OldClass.method_class
method_normal_copy = OldClass.method_normal
Checking __self__ gives the following:
class_self = getattr(NewClass.method_class_copy, '__self__', None)
print(class_self)
it returns:
<class '__main__.OldClass'>
but checking the instance method after being instantiated:
instance_self = getattr(NewClass().method_normal_copy, '__self__', None)
print(instance_self)
as you can see, it still refers to the OldClass, and if I run the class method NewClass.method_class_copy()
, the OldClass.current_count
will increase its value the way I defined.
it returns:
<__main__.NewClass object at 0x000001D8CC1AEA88>
Thanks
Upvotes: 2
Views: 81
Reputation: 52139
This is an effect of the descriptor protocol and how classmethod
uses it: A classmethod
is bound to its class by being looked up on it. Loosely speaking, the lookup some_cls.some_classmethod
returns the method with the cls
parameter pre-filled to some_class
.
Since "normal" methods are bound to their instance by being looked up on the instance, looking them up on the class does not bind them already.
>>> class Foo:
... def some_normalmethod(self): ...
... @classmethod
... def some_classmethod(cls): ...
...
>>> Foo.some_classmethod
<bound method Foo.some_classmethod of <class '__main__.Foo'>>
>>> Foo.some_normalmethod
<function __main__.Foo.some_normalmethod(self)>
Thus, "copying" a classmethod
by fetching it from its class does not work as expected (it is bound to the class already), whereas "copying" a normal method does work (it is not bound to an instance).
In order to "copy" a classmethod
, extract its underlying function and create a new classmethod
from it:
>>> bound_cm = Foo.some_classmethod # bound classmethod
>>> base_cm = bound_cm.__func__ # function underlying classmethod
>>> class Bar:
... some_classmethod = classmethod(base_cm) # new classmethod of same function
...
>>> Bar.some_classmethod
<bound method Foo.some_classmethod of <class '__main__.Bar'>>
Note that this creates a "copy" of the classmethod, not the underlying function. Certain metadata, e.g. the name Foo.some_classmethod
, still points to its origin.
If the original classmethod
object is desired, circumventing the descriptor protocol gives direct access without binding the method.
>>> Foo.__dict__['some_classmethod']
<classmethod at 0x10b6d9d00>
>>> class Qux:
... some_classmethod = Foo.__dict__['some_classmethod']
...
>>> Qux.some_classmethod
<bound method Foo.some_classmethod of <class '__main__.Qux'>>
Upvotes: 2
Reputation: 1155
You can do what you want by defining the methods as class methods, only after you build the second class.
class OldClass(object):
current_count = 1
def __init__(self):
pass
def method_class(cls):
cls.current_count += 1
def method_normal(self):
return 2
# new class
class NewClass(object):
method_class_copy = OldClass.method_class
method_normal_copy = OldClass.method_normal
OldClass.method_class = classmethod(OldClass.method_class)
NewClass.method_class_copy = classmethod(NewClass.method_class_copy)
If you test this with
class_self = getattr(OldClass.method_class, '__self__', None)
print(class_self)
instance_self = getattr(OldClass().method_normal, '__self__', None)
print(instance_self)
print()
class_self = getattr(NewClass.method_class_copy, '__self__', None)
print(class_self)
instance_self = getattr(NewClass().method_normal_copy, '__self__', None)
print(instance_self)
you get
<class '__main__.OldClass'>
<__main__.OldClass object at 0x7f1adc174ac0>
<class '__main__.NewClass'>
<__main__.NewClass object at 0x7f1adc138c70>
which is what you want.
Edit: You can also do this dynamically (use @classmethod
in OldClass
), by first making all class methods of the OldClass
static with staticmethod()
, then creating the NewClass
and then calling classmethod()
.
Upvotes: 0