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

Discussion in 'Python' started by Marc Aymerich, Nov 22, 2012.

  1. 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!!
     
    Marc Aymerich, Nov 22, 2012
    #1
    1. Advertisements

  2. Marc Aymerich

    Peter Otten Guest

    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
    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
     
    Peter Otten, Nov 22, 2012
    #2
    1. Advertisements

  3. Marc Aymerich

    Dave Angel Guest

    I haven't tried it, but how about if you do a @classmethod decorator,
    and then just use isinstance(param, MyClass) ?
     
    Dave Angel, Nov 22, 2012
    #3
  4. Marc Aymerich

    Thomas Bach Guest

    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
     
    Thomas Bach, Nov 22, 2012
    #4
  5. Marc Aymerich

    Dave Angel Guest

    OK, thanks. I hadn't tried it, and hadn't noticed that that decorator
    converts to the class.
    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.
     
    Dave Angel, Nov 22, 2012
    #5
  6. 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...

    you are a genius! I've implemented this on my code and it works as expected ! Thanks :D
     
    Marc Aymerich, Nov 22, 2012
    #6
  7. 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...

    you are a genius! I've implemented this on my code and it works as expected ! Thanks :D
     
    Marc Aymerich, Nov 22, 2012
    #7

  8. 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' :)
     
    Marc Aymerich, Nov 22, 2012
    #8

  9. 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' :)
     
    Marc Aymerich, Nov 22, 2012
    #9

  10. 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/
     
    Steven D'Aprano, Nov 23, 2012
    #10
  11. Marc Aymerich

    Peter Otten Guest

    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"
     
    Peter Otten, Nov 23, 2012
    #11
  12. No. I give an example and explicitly state:

    You can use this class without instantiating:

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

    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.


    [...]
    This needs to be a new-style class unless you're using Python 3.
    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.
     
    Steven D'Aprano, Nov 23, 2012
    #12
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.