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





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






@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.

Comments

Popular posts from this blog

paramiko-expect timeout is happening after executing the command

Export result set on Dbeaver to CSV

Opening a url is failing in Swift