1

I have a class that in principle carries all the information about it in its class body. When instantiated, it receives additional information that together with the class attributes forms a regular instance. My problem now lies in the fact that I need to implement a method which should be called as class method when it is called from a class object but should be called as regular instance method when called from an instance:

e.g. something like


class MyClass(object):
 attribs = 1, 2, 3
 def myMethod(self, args):
 if isclass(self):
 "do class stuff"
 else:
 "do instance stuff"
MyClass.myMethod(2) #should now be called as a class method, e.g. I would normally do @classmethod
MyClass().myMethod(2) #should now be called as instance method

Of course I could declare it as staticmethod and pass either the instance or the class object explicitly, but that seems rather unpythonic and also user unfriendly.

asked Dec 24, 2019 at 14:38
1
  • 2
    While this can be done (creating a descriptor similar to classmethod) the purpose of classmethods and regular methods is usually very different. Strongly consider whether the ease of calling one unified method outweighs the ease of knowing what each separate method does. Commented Dec 24, 2019 at 14:55

2 Answers 2

3

If the methods are to behave differently, you could simply change which one is exposed by that name at initialization time:

class MyCrazyClass:
 @classmethod
 def magicmeth(cls):
 print("I'm a class")
 def _magicmeth(self):
 print("I'm an instance")
 def __init__(self):
 self.magicmeth = self._magicmeth
answered Dec 24, 2019 at 14:43
Sign up to request clarification or add additional context in comments.

8 Comments

Nice, thanks, that should do the trick. Is this still considered acceptable programming style or does it obscure too much what happens in the background?
To be honest, your whole request seems pretty odd, and I can't say I've ever seen this pattern used before. It's not bad, just strange. Perhaps if your question was a little more explicit about what exactly you're trying to do, it would be easier to make an appropriate suggestion.
Well the thing is, that by default I only operate on class objects and instance objects only come into play at certain special cases where additional information about the class object is required. Consider this example: Normally a physical unit is something like value*factor (1 Meter) and you can convert it to a diffrent unit simply by making newval = oldval * oldfactor/newfactor. But sometimes you would maybe also want a power like (1 Meter**2, which in my case would be an instance of the Meter class) but conversion would then require to know the power of the instance.
@Torilla Note that a squaremeter is not the same type as a meter. Meter*Meter is as a Meter just as a Meter*Second -- namely not at all. If you model units like this, your unit system is broken.
I am not quite sure what you mean by that. I consider any unit (SI unit for that matter) as a seperate "dimension". E.g. I have a well defined baseclass for Length, Time, Mass etc. This is all very fine. The trouble comes in when defnining a Unit from multiple baseunits (e.g. Joule == kg m²s−²) where one can operate on the class object defined by its baseunits, but can also add a power (e.g. J² == kg² m4s−4 == (kg m²s−²)² etc).
|
3

You can define a decorator that works like a regular method when called on an instance, or class method when called on a class. This requires a descriptor:

from functools import partial
class anymethod:
 """Transform a method into both a regular and class method"""
 def __init__(self, call):
 self.__wrapped__ = call
 def __get__(self, instance, owner):
 if instance is None: # called on class
 return partial(self.__wrapped__, owner)
 else: # called on instance
 return partial(self.__wrapped__, instance)
class Foo:
 @anymethod
 def bar(first):
 print(first)
Foo.bar() # <class '__main__.Foo'>
Foo().bar() # <__main__.Foo object at 0x106f86610>

Note that this behaviour will not be obvious to most programmers. Only use it if you really need it.

answered Dec 24, 2019 at 15:05

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.