Decorator changing function status from method to

2019-04-08 23:01发布

[Updated]: Answer inline below question

I have an inspecting program and one objective is for logic in a decorator to know whether the function it is decorating is a class method or regular function. This is failing in a strange way. Below is code run in Python 2.6:

def decorate(f):
    print 'decorator thinks function is', f
    return f

class Test(object):
    @decorate
    def test_call(self):
        pass

if __name__ == '__main__':
    Test().test_call()
    print 'main thinks function is', Test().test_call

Then on execution:

decorator thinks function is <function test_call at 0x10041cd70>
main thinks function is <bound method Test.test_call of <__main__.Test object at 0x100425a90>>

Any clue on what's going wrong, and if it is possible for @decorate to correctly infer that test_call is a method?

[Answer] carl's answer below is nearly perfect. I had a problem when using the decorator on a method that subclasses call. I adapted his code to include a im_func comparison on superclass members:

ismethod = False
for item in inspect.getmro(type(args[0])):
    for x in inspect.getmembers(item):
        if 'im_func' in dir(x[1]):
            ismethod = x[1].im_func == newf
            if ismethod:
                break
    else:
        continue
    break

4条回答
Melony?
2楼-- · 2019-04-08 23:18

I tried a slightly different example, with one decorated method and one undecorated method.

def decorate(f):
  print 'decorator thinks function is', f
  return f

class Test(object):
  @decorate
  def test_call(self):
    pass
  def test_call_2(self):
    pass

if __name__ == '__main__':
  print 'main thinks function is', Test.test_call
  print 'main thinks function 2 is', Test.test_call_2

Then the output is:

decorator thinks function is <function test_call at 0x100426b18>
main thinks function is <unbound method Test.test_call>
main thinks function 2 is <unbound method Test.test_call_2>

Thus, the decorator saw a different type than the main function did, but the decorator did not change the function's type, or it would be different from the undecorated function.

查看更多
相关推荐>>
3楼-- · 2019-04-08 23:27

No, this is not possible as you have requested, because there is no inherent difference between bound methods and functions. A method is simply a function wrapped up to get the calling instance as the first argument (using Python descriptors).

A call like:

Test.test_call

which returns an unbound method, translates to

Test.__dict__[ 'test_call' ].__get__( None, spam )

which is an unbound method, even though

Test.__dict__[ 'test_call' ]

is a function. This is because functions are descriptors whose __get__ methods return methods; when Python sees one of these in the lookup chain it calls the __get__ method instead of continuing up the chain.

In effect, the 'bound-methodiness' of a function is determined at runtime, not at define-time!

The decorator simply sees the function as it is defined, without looking it up in a __dict__, so cannot tell whether it is looking at a bound method.


It might be possible to do this with a class decorator that modifies __getattribute__, but that's a particularly nasty hack. Why must you have this functionality? Surely, since you have to place the decorator on the function yourself, you could pass it an argument that says whether said function is defined within a class?

class Test:
    @decorate( method = True )
    def test_call:
        ...

@decorate( method = False )
def test_call:
    ...
查看更多
小情绪 Triste *
4楼-- · 2019-04-08 23:27

Your decorator is run before the function becomes a method. def keyword inside a class defines a function line in any other place, then the functions defined in the body of a class are added to the class as methods. Decorator operates on the function before it is processed by the class that is why your code 'fails'.

There is no way for the @decorate to see the function is actually a method. A workaround for that would be to decorate the function whatever it is (e.g. adding an attribute do_something_about_me_if_I_am_a_method ;-)) and then process it again after the class is computed (iterating over the class members and doing whatever you want with those decorated).

查看更多
够拽才男人
5楼-- · 2019-04-08 23:29

As others have said, a function is decorated before it is bound, so you cannot directly determine whether it's a 'method' or 'function'.

A reasonable way to determine if a function is a method or not is to check whether 'self' is the first parameter. While not foolproof, most Python code adheres to this convention:

import inspect
ismethod = inspect.getargspec(method).args[0] == 'self'

Here's a convoluted way that seems to automatically figure out whether the method is a bound or not. Works for a few simple cases on CPython 2.6, but no promises. It decides a function is a method if the first argument to is an object with the decorated function bound to it.

import inspect

def decorate(f):
    def detect(*args, **kwargs):
        try:
            members = inspect.getmembers(args[0])
            members = (x[1].im_func for x in members if 'im_func' in dir(x[1]))
            ismethod = detect in members
        except:
            ismethod = False
        print ismethod

        return f(*args, **kwargs)
    return detect

@decorate
def foo():
    pass

class bar(object):
    @decorate
    def baz(self):
        pass

foo() # prints False
bar().baz() # prints True
查看更多
登录 后发表回答