views:

262

answers:

5

I'm wondering if it's possible to make a method which behaves differently when called as a class method than when called as an instance method.

For example, as a skills-improvement project, I'm writing a Matrix class (yes, I know there are perfectly good matrix classes already out there). I've created a class method for it called identity which returns an identity matrix of a specified size.

Now, when called on an instance of Matrix, it seems logical that the size shouldn't need to be specified; it should return an identity matrix of the same size as the Matrix it's called on.

In other words, I'd like to define a method which can determine whether it was called via an instance and, if so, access that instance's attributes. Unfortunately, even after digging through the documentation and a few Google searches, I haven't found anything which suggests this is possible. Does anyone know differently?

Edit:

Wow! Clearly, I'm still not quite used to first-class functions. Here's what I ended up with — thanks to Unknown for providing the key!

class Foo(object):
    def __init__(self, bar):
        self.baz = bar
        self.bar = MethodType(lambda self: self.__class__.bar(self.baz), self, self.__class__)

    @classmethod
    def bar(cls, baz):
        return 5 * baz

Foo.bar(3) # returns 15

foo = Foo(7)
foo.bar() # returns 35

Edit 2:

Just a quick note — this technique (and most of those presented below) won't work on classes which define __slots__, as you cannot reassign the method.

+2  A: 

I think the larger problem is that you are overloading the name 'bar' on class 'Foo', something python doesn't allow. The second definition of 'bar' clobbers the first definition of 'bar'.

Try to think of unique names for your classmethod and instance method. i.e.

@classmethod
def create(cls, baz):
   ...

def rubber_stamp(self):
   ...
jsamsa
+1: And -- of course -- the inherent confusion factor of variant behavior, even if "closely related".
S.Lott
And your suggestion to fix the "non-working concept code" that the OP already identified as a large problem with which he needs help is?
Jarret Hardie
IMO, the non-working concept code is a bad idea -- the fix is to rethink the API. There's no "fix".
S.Lott
+9  A: 

Questionably useful Python hacks are my forte.

from types import *

class Foo(object):
    def __init__(self):
        self.bar = methodize(bar, self)
        self.baz = 999

    @classmethod
    def bar(cls, baz):
        return 2 * baz


def methodize(func, instance):
    return MethodType(func, instance, instance.__class__)

def bar(self):
    return 4*self.baz


>>> Foo.bar(5)
10
>>> a=Foo()
>>> a.bar()
3996
Unknown
Kudos, and an upvote, for effort in the code sample, and especially for the phrase "questionably useful Python hacks are my forte" :-)
Jarret Hardie
@Jarret, its not just a quote for show either. Check out my other ones such as: http://stackoverflow.com/questions/813882/is-there-a-way-to-check-whether-function-output-is-assigned-to-a-variable-in-pyth/813938#813938
Unknown
+3  A: 

[edited: use attribute to be a more direct answer; see the helpful comment by John Fouhy]

You can use a descriptor to do what you want:

class cls_or_inst_method(object):
    def __init__(self, class_method, instance_method):
        self.class_method = class_method
        self.instance_method = instance_method

    def __get__(self, obj, objtype):
        if obj is None:
            return self.class_method
        else:
            return lambda: self.instance_method(obj)

def my_class_method(baz):
    return baz + 1

def my_instance_method(self):
    return self.baz * 2

class Foo(object):
    baz = 10
    bar = cls_or_inst_method(my_class_method, my_instance_method)

Using the above:

>>> print Foo.bar(5)
6
>>> my_foo = Foo()
>>> print my_foo.bar()
20
Benji York
To be a solution, the instance method should refer to self.baz and not take any arguments (except self), I think..
John Fouhy
A: 

@Unknown What's the difference between your's and this:

class Foo(object):

    def _bar(self, baz):
        print "_bar, baz:", baz

    def __init__(self, bar):
        self.bar = self._bar
        self.baz = bar

    @classmethod
    def bar(cls, baz):
        print "bar, baz:", baz

In [1]: import foo

In [2]: f = foo.Foo(42)

In [3]: f.bar(1)
_bar, baz: 1

In [4]: foo.Foo.bar(1)
bar, baz: 1

In [5]: f.__class__.bar(1)
bar, baz: 1
Ale
Oh! Thanks Mr. Unknown :-)
Ale
Well the difference is that you can bind whatever function you want as the method. I just used this opportunity to show off that trick. And also that there is no dangling f._bar()
Unknown
+1  A: 

You can reassign your identity method in init with short lambda function:

class Matrix(object):
    def __init__(self):
        self.identity = lambda s=self:s.__class__.identity(s)

        #...whatever initialization code you have...
        self.size = 10        

    @classmethod
    def identity(self, other):
        #...always do you matrix calculations on 'other', not 'self'...
        return other.size


m = Matrix()
print m.identity()
print Matrix.identity(m)

If you're not familiar with lambda, it creates an anonymous function. It's rarely necessary, but it can make your code more concise. The lambda line above could be rewritten:

    def identity(self):
        self.__class__.indentity(self)
    self.identity = identity
Greg