【问题标题】:Finding a function's parameters in Python在 Python 中查找函数的参数
【发布时间】:2011-03-23 11:30:30
【问题描述】:

我希望能够询问一个类的__init__ 方法它的参数是什么。直接的方法如下:

cls.__init__.__func__.__code__.co_varnames[:code.co_argcount]

但是,如果类有任何装饰器,这将不起作用。它将给出装饰器返回的函数的参数列表。我想深入了解原始的__init__ 方法并获取那些原始参数。在装饰器的情况下,装饰器函数将在装饰器返回的函数的闭包中找到:

cls.__init__.__func__.__closure__[0]

但是,如果闭包中还有其他事情,那就更复杂了,装饰者可能会不时做这些事情:

def Something(test):
    def decorator(func):
        def newfunc(self):
            stuff = test
            return func(self)
        return newfunc
    return decorator

def test():
    class Test(object):
        @Something(4)
        def something(self):
            print Test
    return Test

test().something.__func__.__closure__
(<cell at 0xb7ce7584: int object at 0x81b208c>, <cell at 0xb7ce7614: function object at 0xb7ce6994>)

然后我必须决定是要来自装饰器的参数还是来自原始函数的参数。装饰器返回的函数可以有*args**kwargs 作为其参数。如果有多个装饰器,我必须决定哪个是我关心的?

那么,即使函数可能被修饰,找到函数参数的最佳方法是什么?另外,将装饰器链向下返回到装饰函数的最佳方法是什么?

更新:

这就是我现在的实际操作方式(为了保护被告的身份,名字已被更改):

import abc
import collections

IGNORED_PARAMS = ("self",)
DEFAULT_PARAM_MAPPING = {}
DEFAULT_DEFAULT_PARAMS = {}

class DICT_MAPPING_Placeholder(object):
    def __get__(self, obj, type):
        DICT_MAPPING = {}
        for key in type.PARAMS:
            DICT_MAPPING[key] = None
        for cls in type.mro():
            if "__init__" in cls.__dict__:
                cls.DICT_MAPPING = DICT_MAPPING
                break
        return DICT_MAPPING

class PARAM_MAPPING_Placeholder(object):
    def __get__(self, obj, type):
        for cls in type.mro():
            if "__init__" in cls.__dict__:
                cls.PARAM_MAPPING = DEFAULT_PARAM_MAPPING
                break
        return DEFAULT_PARAM_MAPPING

class DEFAULT_PARAMS_Placeholder(object):
    def __get__(self, obj, type):
        for cls in type.mro():
            if "__init__" in cls.__dict__:
                cls.DEFAULT_PARAMS = DEFAULT_DEFAULT_PARAMS
                break
        return DEFAULT_DEFAULT_PARAMS

class PARAMS_Placeholder(object):
    def __get__(self, obj, type):
        func = type.__init__.__func__
        # unwrap decorators here
        code = func.__code__
        keys = list(code.co_varnames[:code.co_argcount])
        for name in IGNORED_PARAMS:
            try: keys.remove(name)
            except ValueError: pass
        for cls in type.mro():
            if "__init__" in cls.__dict__:
                cls.PARAMS = tuple(keys)
                break
        return tuple(keys)

class BaseMeta(abc.ABCMeta):
    def __init__(self, name, bases, dict):
        super(BaseMeta, self).__init__(name, bases, dict)
        if "__init__" not in dict:
            return
        if "PARAMS" not in dict:
            self.PARAMS = PARAMS_Placeholder()
        if "DEFAULT_PARAMS" not in dict:
            self.DEFAULT_PARAMS = DEFAULT_PARAMS_Placeholder()
        if "PARAM_MAPPING" not in dict:
            self.PARAM_MAPPING = PARAM_MAPPING_Placeholder()
        if "DICT_MAPPING" not in dict:
            self.DICT_MAPPING = DICT_MAPPING_Placeholder()


class Base(collections.Mapping):
    __metaclass__ = BaseMeta
    """
    Dict-like class that uses its __init__ params for default keys.

    Override PARAMS, DEFAULT_PARAMS, PARAM_MAPPING, and DICT_MAPPING
    in the subclass definition to give non-default behavior.

    """
    def __init__(self):
        pass
    def __nonzero__(self):
        """Handle bool casting instead of __len__."""
        return True
    def __getitem__(self, key):
        action = self.DICT_MAPPING[key]
        if action is None:
            return getattr(self, key)
        try:
            return action(self)
        except AttributeError:
            return getattr(self, action)
    def __iter__(self):
        return iter(self.DICT_MAPPING)
    def __len__(self):
        return len(self.DICT_MAPPING)

print Base.PARAMS
# ()
print dict(Base())
# {}

此时,Base 报告四个 contants 的无意义值,并且实例的 dict 版本为空。但是,如果您是子类,则可以覆盖这四个中的任何一个,或者您可以将其他参数包含到__init__

class Sub1(Base):
    def __init__(self, one, two):
        super(Sub1, self).__init__()
        self.one = one
        self.two = two

Sub1.PARAMS
# ("one", "two")
dict(Sub1(1,2))
# {"one": 1, "two": 2}

class Sub2(Base):
    PARAMS = ("first", "second")
    def __init__(self, one, two):
        super(Sub2, self).__init__()
        self.first = one
        self.second = two

Sub2.PARAMS
# ("first", "second")
dict(Sub2(1,2))
# {"first": 1, "second": 2}

【问题讨论】:

  • 你为什么想要那个?
  • 这很困难的事实应该向您表明这不是正确的做法。
  • 我希望能够将类的对象用作字典,并明确控制通过__getitem____iter__ 公开的键。 __init__ 的参数是很好的默认键,所以我以编程方式提取这些键。我只是想解决一些极端情况,比如涉及描述符时。
  • 您能否给出一个简短的代码示例来说明您想要做什么?具体来说,我会对您如何将__init__ 的参数映射到暴露的键感兴趣。我问是因为我们很可能能够解决这个问题而不是解决它。
  • 这通常是不可能的,因为装饰器根本不需要保留对装饰函数的引用。

标签: python function closures decorator


【解决方案1】:

考虑这个装饰器:

def rickroll(old_function):
    return lambda junk, junk1, junk2: "Never Going To Give You Up"

class Foo(object):
    @rickroll
    def bar(self, p1, p2):
        return p1 * p2

print Foo().bar(1, 2)

在其中,rickroll 装饰器采用 bar 方法,将其丢弃,用一个新函数替换它,该函数忽略其不同名称(可能还有编号!)的参数,而是返回经典歌曲中的一行。

没有对原始函数的进一步引用,垃圾收集器可以随时来删除它。

在这种情况下,我看不出如何找到参数名称 p1 和 p2。在我的理解中,即使是 Python 解释器本身也不知道它们曾经被称为什么。

【讨论】:

  • 这很好。我绝对没有考虑过返回完全不同的、不相关的函数的装饰器。你对缺乏参考是正确的。这是有道理的,但同时在类中定义的函数会消失似乎不直观。你给了我一些思考。谢谢!
  • 我在stackoverflow.com/questions/3481872/…中一直以更具体的方式探讨这个话题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-20
  • 2017-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-16
  • 1970-01-01
相关资源
最近更新 更多