Function to behave differently on class vs on instance
Function to behave differently on class vs on instance
I'd like a particular function to be callable as a classmethod, and to behave differently when it's called on an instance.
For example, if I have a class Thing
, I want Thing.get_other_thing()
to work, but also thing = Thing(); thing.get_other_thing()
to behave differently.
class Thing
Thing.get_other_thing()
thing = Thing(); thing.get_other_thing()
I think overwriting the get_other_thing
method on initialization should work (see below), but that seems a bit hacky. Is there a better way?
get_other_thing
class Thing:
def __init__(self):
self.get_other_thing = self._get_other_thing_inst()
@classmethod
def get_other_thing(cls):
# do something...
def _get_other_thing_inst(self):
# do something else
@Poppinyoshi that's two different classes. I could also create two different classes without inheritance to solve the problem... or I could create different functions in the same class and call them separately. But that's not what I'm asking.
– DilithiumMatrix
Jun 29 at 21:27
Descriptors pass whether or not they're on an instance, but that takes a bit more code.
– Ignacio Vazquez-Abrams
Jun 29 at 21:28
I wrote an answer for this that I thought was cleaner than yours and then realized that it was just what you had done. I think what you have is cleaner than overriding
__getattribute__
, and I can't think of a better way– MoxieBall
Jun 29 at 21:38
__getattribute__
2 Answers
2
Great question! What you seek can be easily done using descriptors.
Descriptors are Python objects which implement the descriptor protocol, usually starting with __get__()
.
__get__()
They exist, mostly, to be set as a class attribute on different classes. Upon accessing them, their __get__()
method is called, with the instance and owner class passed in.
__get__()
class DifferentFunc:
"""Deploys a different function accroding to attribute access
I am a descriptor.
"""
def __init__(self, clsfunc, instfunc):
# Set our functions
self.clsfunc = clsfunc
self.instfunc = instfunc
def __get__(self, inst, owner):
# Accessed from class
if inst is None:
return self.clsfunc.__get__(None, owner)
# Accessed from instance
return self.instfunc.__get__(inst, owner)
class Test:
@classmethod
def _get_other_thing(cls):
print("Accessed through class")
def _get_other_thing_inst(inst):
print("Accessed through instance")
get_other_thing = DifferentFunc(_get_other_thing,
_get_other_thing_inst)
And now for the result:
>>> Test.get_other_thing()
Accessed through class
>>> Test().get_other_thing()
Accessed through instance
That was easy!
By the way, did you notice me using __get__
on the class and instance function? Guess what? Functions are also descriptors, and that's the way they work!
__get__
>>> def func(self):
... pass
...
>>> func.__get__(object(), object)
<bound method func of <object object at 0x000000000046E100>>
Upon accessing a function attribute, it's __get__
is called, and that's how you get function binding.
__get__
For more information, I highly suggest reading the Python manual and the "How-To" linked above. Descriptors are one of Python's most powerful features and are barely even known.
Or Why not set self.func = self._func
inside __init__
?
self.func = self._func
__init__
Setting the function on instantiation comes with quite a few problems:
self.func = self._func
self._func
__get__()
__slots__
__init__
__init__
There are many more that I didn't add as the list goes on and on.
Awesome, thanks for the details description and explanations! Do you see any particular benefit to this approach over resetting the function on instantiation?
– DilithiumMatrix
2 days ago
@DilithiumMatrix That's another good one! I really like your questions mate. Resetting the function on instantiation causes a circular reference, is unexpected when invoking
__get__
externally, doesn't behave as well with other code that expect the function to act normally and all sorts of other pitfalls.– Bharel
2 days ago
__get__
@DilithiumMatrix See for my edit.
– Bharel
2 days ago
Here is a bit hacky solution:
class Thing(object):
@staticmethod
def get_other_thing():
return 1
def __getattribute__(self, name):
if name == 'get_other_thing':
return lambda: 2
return super(Thing, self).__getattribute__(name)
print Thing.get_other_thing() # 1
print Thing().get_other_thing() # 2
If we are on class, staticmethod is executed. If we are on instance, __getattribute__
is first to be executed, so we can return not Thing.get_other_thing
but some other function (lambda
in my case)
__getattribute__
Thing.get_other_thing
lambda
Ha, very hacky (and I think hackier than mine), but also very clever. Thanks!
– DilithiumMatrix
Jun 29 at 21:38
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Isn't this what we use inheritance for. Define a separate class with your new method - which inherits from parent class, then by scope, instance will return methods from it's class definition; if not found in its definition it will go to parent class.
– Poppinyoshi
Jun 29 at 21:23