method that can be called from a class and also from an instance

M

Marc Aymerich

Hi,

I want to create a method within a class that is able to accept either a class or an instance.

class MyClass(object):
@magic_decorator
def method(param):
# param can be MyClass (cls) or an instance of MyClass (self)

so I can do something like:

instance = MyClass()

MyClass.method()
instance.method()

I guess the way to go is implementing a custom decorator (@magic_decorator in my example), but, how can I know if the method has been called from the class o from an instance?

Thank you very much!!
 
P

Peter Otten

Marc said:
Hi,

I want to create a method within a class that is able to accept either a
class or an instance.

class MyClass(object):
@magic_decorator
def method(param):
# param can be MyClass (cls) or an instance of MyClass (self)

so I can do something like:

instance = MyClass()

MyClass.method()
instance.method()

I guess the way to go is implementing a custom decorator (@magic_decorator
in my example), but, how can I know if the method has been called from the
class o from an instance?

Thank you very much!!

Why would you overload a method that way?

$ cat class_or_inst.py
import functools

class desc(object):
def __init__(self, f):
self._f = f
def __get__(self, inst=None, class_=None):
if inst is not None:
return functools.partial(self._f, self=inst)
elif class_ is not None:
return functools.partial(self._f, class_=class_)
raise TypeError("nobody expects the Spanish inquisition")



class A(object):
@desc
def f(self=None, class_=None):
if self is not None:
return "instance"
elif class_ is not None:
return "class"
return "unknown"
$ python -i class_or_inst.py
A.f() 'class'
A().f() 'instance'
A.__dict__["f"].__get__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "class_or_inst.py", line 11, in __get__
raise TypeError("nobody expects the Spanish inquisition")
TypeError: nobody expects the Spanish inquisition
 
D

Dave Angel

Hi,

I want to create a method within a class that is able to accept either a class or an instance.

class MyClass(object):
@magic_decorator
def method(param):
# param can be MyClass (cls) or an instance of MyClass (self)

so I can do something like:

instance = MyClass()

MyClass.method()
instance.method()

I guess the way to go is implementing a custom decorator (@magic_decorator in my example), but, how can I know if the method has been called from the class o from an instance?

Thank you very much!!

I haven't tried it, but how about if you do a @classmethod decorator,
and then just use isinstance(param, MyClass) ?
 
T

Thomas Bach

I haven't tried it, but how about if you do a @classmethod decorator,
and then just use isinstance(param, MyClass) ?

This won't work:

In [22]: class Foo(object):
....: @classmethod
....: def bar(cls):
....: print repr(cls)
....:

In [23]: Foo.bar()
<class '__main__.Foo'>

In [24]: Foo().bar()
<class '__main__.Foo'>

Actually help(classmethod) explicitly says so:
<quote>
It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). The instance is ignored except for its class.
</quote>

I think the way to go is via the descriptor protocol[1] as suggested
by Peter.

Regards,
Thomas.


Footnotes:
[1] http://docs.python.org/3/howto/descriptor.html
 
D

Dave Angel

I haven't tried it, but how about if you do a @classmethod decorator,
and then just use isinstance(param, MyClass) ?
This won't work:

In [22]: class Foo(object):
....: @classmethod
....: def bar(cls):
....: print repr(cls)
....:

In [23]: Foo.bar()
<class '__main__.Foo'>

In [24]: Foo().bar()
<class '__main__.Foo'>

Actually help(classmethod) explicitly says so:
<quote>
It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). The instance is ignored except for its class.
</quote>

OK, thanks. I hadn't tried it, and hadn't noticed that that decorator
converts to the class.
I think the way to go is via the descriptor protocol[1] as suggested
by Peter.

Regards,
Thomas.


Footnotes:
[1] http://docs.python.org/3/howto/descriptor.html
The OP should probably use this link instead, since he's not using Python 3.

http://docs.python.org/2.7/howto/descriptor.html

Marc: I believe the descriptor stuff has changed in Python 3; I don't
use it. But if you've got to do this, and you have to do it in Python
2.x, you'd better use the 2.x documentation.
 
M

Marc Aymerich

Marc Aymerich wrote:










Why would you overload a method that way?

Yep, it's an strange pattern sure it can be done in a better way but this is the best I can think on, the context is:

I'm developing a permission system which can give general permissions for a given class and also specific permissions for a given object.

class Node(object):
@role
def has_perm(instance, user)
if is_class(instance): then global perm for user...
else: then specific perm for user...

$ cat class_or_inst.py

import functools



class desc(object):

def __init__(self, f):

self._f = f

def __get__(self, inst=None, class_=None):

if inst is not None:

return functools.partial(self._f, self=inst)

elif class_ is not None:

return functools.partial(self._f, class_=class_)

raise TypeError("nobody expects the Spanish inquisition")







class A(object):

@desc

def f(self=None, class_=None):

if self is not None:

return "instance"

elif class_ is not None:

return "class"

return "unknown"

$ python -i class_or_inst.py
'class'

'instance'
A.__dict__["f"].__get__()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "class_or_inst.py", line 11, in __get__

raise TypeError("nobody expects the Spanish inquisition")

TypeError: nobody expects the Spanish inquisition

you are a genius! I've implemented this on my code and it works as expected ! Thanks :D
 
M

Marc Aymerich

Marc Aymerich wrote:










Why would you overload a method that way?

Yep, it's an strange pattern sure it can be done in a better way but this is the best I can think on, the context is:

I'm developing a permission system which can give general permissions for a given class and also specific permissions for a given object.

class Node(object):
@role
def has_perm(instance, user)
if is_class(instance): then global perm for user...
else: then specific perm for user...

$ cat class_or_inst.py

import functools



class desc(object):

def __init__(self, f):

self._f = f

def __get__(self, inst=None, class_=None):

if inst is not None:

return functools.partial(self._f, self=inst)

elif class_ is not None:

return functools.partial(self._f, class_=class_)

raise TypeError("nobody expects the Spanish inquisition")







class A(object):

@desc

def f(self=None, class_=None):

if self is not None:

return "instance"

elif class_ is not None:

return "class"

return "unknown"

$ python -i class_or_inst.py
'class'

'instance'
A.__dict__["f"].__get__()

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "class_or_inst.py", line 11, in __get__

raise TypeError("nobody expects the Spanish inquisition")

TypeError: nobody expects the Spanish inquisition

you are a genius! I've implemented this on my code and it works as expected ! Thanks :D
 
M

Marc Aymerich

This won't work:
In [22]: class Foo(object):
....: @classmethod
....: def bar(cls):
....: print repr(cls)

In [23]: Foo.bar()
<class '__main__.Foo'>
In [24]: Foo().bar()
<class '__main__.Foo'>

Actually help(classmethod) explicitly says so:

It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). The instance is ignored except for its class.



OK, thanks. I hadn't tried it, and hadn't noticed that that decorator

converts to the class.


I think the way to go is via the descriptor protocol[1] as suggested

The OP should probably use this link instead, since he's not using Python 3.



http://docs.python.org/2.7/howto/descriptor.html



Marc: I believe the descriptor stuff has changed in Python 3; I don't

use it. But if you've got to do this, and you have to do it in Python

2.x, you'd better use the 2.x documentation.


thanks for the links Thomas and Dave, I'm going to read this documentation right now, I love to learn this kind of python 'internals' :)
 
M

Marc Aymerich

This won't work:
In [22]: class Foo(object):
....: @classmethod
....: def bar(cls):
....: print repr(cls)

In [23]: Foo.bar()
<class '__main__.Foo'>
In [24]: Foo().bar()
<class '__main__.Foo'>

Actually help(classmethod) explicitly says so:

It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). The instance is ignored except for its class.



OK, thanks. I hadn't tried it, and hadn't noticed that that decorator

converts to the class.


I think the way to go is via the descriptor protocol[1] as suggested

The OP should probably use this link instead, since he's not using Python 3.



http://docs.python.org/2.7/howto/descriptor.html



Marc: I believe the descriptor stuff has changed in Python 3; I don't

use it. But if you've got to do this, and you have to do it in Python

2.x, you'd better use the 2.x documentation.


thanks for the links Thomas and Dave, I'm going to read this documentation right now, I love to learn this kind of python 'internals' :)
 
S

Steven D'Aprano

Marc said:
Hi,

I want to create a method within a class that is able to accept either
a class or an instance.
[...]
Why would you overload a method that way?


The use-case I have is that I have a number of classes with default
state. Most instances don't override any of the state, so the instances
don't add anything except an extra conceptual layer:

instance = MyClass() # notice that there are no arguments passed
instance.method(args)

Since the instances don't have any state except for that already held by
the class, they are redundant and pointless. Just knowing the class is
enough to specify the behaviour. If I used class methods, I could do this:

MyClass.method(args)


But here's the thing -- sometimes I *do* have instances that override the
default state:

instance = MyClass(x, y, z)
instance.method(args)

Now if method is a class method, my per-instance state is ignored. So I
want a method that can be called from the class, and see the default
state, or from the instance, and see the per-instance state. Neither
classmethod, staticmethod nor ordinary instance methods do the job, but
my custom dualmethod does.

http://code.activestate.com/recipes/577030/
 
P

Peter Otten

Steven said:
Marc said:
Hi,

I want to create a method within a class that is able to accept either
a class or an instance.
[...]
Why would you overload a method that way?


The use-case I have is that I have a number of classes with default
state. Most instances don't override any of the state, so the instances
don't add anything except an extra conceptual layer:

instance = MyClass() # notice that there are no arguments passed
instance.method(args)

Since the instances don't have any state except for that already held by
the class, they are redundant and pointless. Just knowing the class is
enough to specify the behaviour. If I used class methods, I could do this:

MyClass.method(args)


But here's the thing -- sometimes I *do* have instances that override the
default state:

instance = MyClass(x, y, z)
instance.method(args)

Now if method is a class method, my per-instance state is ignored. So I
want a method that can be called from the class, and see the default
state, or from the instance, and see the per-instance state. Neither
classmethod, staticmethod nor ordinary instance methods do the job, but
my custom dualmethod does.

http://code.activestate.com/recipes/577030/

Am I reading that right that you don't invoke method() as MyClass.method()?
Then I'd probably use class attributes to store the default state and shade
them by instance attributes as needed.

class A:
state = "default"
def __init__(self, state=None):
if state is not None:
self.state = state
def method(self): return self.state

assert A().method() == "default"
assert A("special").method() == "special"

The same idea might work for the OP, too (but I'm not sure it's a good
idea):

class B:
def inst_f(self):
return "instance"
@classmethod
def f(class_):
return "class"
def __init__(self):
self.f = self.inst_f

assert B.f() == "class"
assert B().f() == "instance"
 
S

Steven D'Aprano

Am I reading that right that you don't invoke method() as
MyClass.method()?

No. I give an example and explicitly state:

You can use this class without instantiating:

Example.method('else') # returns 'something else'

Then I'd probably use class attributes to store the
default state and shade them by instance attributes as needed.

class A:
state = "default"
def __init__(self, state=None):
if state is not None:
self.state = state
def method(self): return self.state

That doesn't allow me to call A.method().

On the other hand, if method were a class method, then I could say
A.method() or A(state).method, but in both cases I would get the default.
So that isn't suitable.


[...]
The same idea might work for the OP, too (but I'm not sure it's a good
idea):

class B:

This needs to be a new-style class unless you're using Python 3.
def inst_f(self):
return "instance"
@classmethod
def f(class_):
return "class"
def __init__(self):
self.f = self.inst_f

assert B.f() == "class"
assert B().f() == "instance"

Without studying that in detail, it looks like that would be an
alternative solution to the same problem. The downsides are:

- you have two distinct but almost identical implementations of
method "f", one called "f" and one called "inst_f";

- it works by shadowing method "f" in the instance, which may
strike many people as too tricky for production software.


Me personally, I think the first objection is critical. Having to write
the same method twice, with subtle differences, is inviting bugs.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top