【问题标题】:Python descriptors with arguments?带参数的 Python 描述符?
【发布时间】:2015-08-28 20:40:59
【问题描述】:

TL;DR Python 2.7.5,当使用描述符作为装饰器时,有什么方法可以传入参数(给__init__ 方法)? 要么 如何使用带参数的方法装饰器 (as here) 访问类实例的属性? -- 不过,我认为这是不可能的,因此下面将重点放在描述符上......


加长版

我有一类对象,具有不同的“类型”属性。根据实例的“类型”,我想要一种方法是否可用。我知道一种方法是创建多个类,但我试图在创建这些对象时不要有一堆 if / else 语句。例如,我有两个几乎相同的对象 A 和 B,除了对象 B 我不想让 get_start_date() 方法可用。所以本质上,我想要的是 A 和 B 都是 MyObjects 类的实例,但具有不同的“类型”属性。

type(A) == type(B)
A.genus_type != B.genus_type

我会使用 .genus_type 属性来区分哪些方法是允许的,哪些是不允许的......

我在想我可以使用带有白名单的装饰器,例如:

def valid_for(whitelist):
    def wrap(f):
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs)
    return wrapper
return wrap

class A(object):
    @valid_for(['typeB'])
    def do_something_cool(self):
        print 'blah'

但问题是我无法访问装饰器中的实际类实例,我可以在其中测试实例类型属性。基于this SO question,我想,“我可以使用描述符!”。

于是我尝试了:

class valid_for(object):
    """ descriptor to check the type of an item, to see
    if the method is valid for that type"""
    def __init__(self, func):
        self.f = func

    def __get__(self, instance, owner):
        def wrapper(*args):
            return self.f(instance, *args)
        return wrapper

但后来我不知道如何将['typeB'] 参数传递到描述符中……默认情况下,Python 将调用的方法作为参数传递给__init__。我可以为每种类型创建硬编码描述符并将它们嵌套,但我想知道我是否会遇到this problem。假设我可以克服嵌套问题,那么执行以下操作似乎也不那么干净:

class A(object):
    @valid_for_type_b
    @valid_for_type_f
    @valid_for_type_g
    def do_something_cool(self):
        print 'blah'

这样做只是让我的func 等于列表['typeB']...

class valid_for(object):
    """ descriptor to check the type of an item, to see
    if the method is valid for that type"""
    def __init__(self, func, *args):
        self.f = func

    def __get__(self, instance, owner):
        def wrapper(*args):
            return self.f(instance, *args)
        return wrapper

class A(object):
    @valid_for(['typeB'])
    def do_something_cool(self):
        print 'blah'

而我的func 不在*args 列表中,所以我不能只是简单地交换参数(*args 为空)。

我一直在寻找提示 herehere,但没有找到任何看起来像干净或有效的解决方法。有没有一种干净的方法可以做到这一点,还是我必须使用多个类并混合各种方法?或者,现在我倾向于使用检查的实例方法,但这似乎不太干净和可重用......

class A(object):
    def _valid_for(self, whitelist):
        if self.genus_type not in whitelist:
            raise Exception

    def do_something_cool(self):
        self._valid_for(['foo'])
        print 'blah'

我使用的是 Python 2.7.5。


更新 1

根据 cmets 中的建议,我尝试了:

def valid_for2(whitelist):
    def wrap(f):
        def wrapper(*args, **kwargs):
            import pdb
            pdb.set_trace()
            print args[0].genus_type
            return f(*args, **kwargs)
        return wrapper
    return wrap

但此时,args[0]。只返回参数:

(Pdb) args[0]
args = (<FormRecord object at 0x112f824d0>,)
kwargs = {}
(Pdb) args[0].genus_type
args = (<FormRecord object at 0x112f824d0>,)
kwargs = {}

但是,按照建议使用functools 确实有效——所以我会奖励答案。 functools 中似乎有一些黑魔法可以让参数进入?


更新 2

所以更多地调查 jonrsharpe 的建议,他的方法似乎也有效,但我必须明确使用 self 而不是 args[0]。不知道为什么行为不同...

def valid_for2(whitelist):
    def wrap(f):
        def wrapper(self, *args, **kwargs):
            print self.genus_type
            return f(*args, **kwargs)
        return wrapper
    return wrap

产生与functools 相同的输出。 谢谢!

【问题讨论】:

  • 是的,我尝试过使用装饰器——但使用装饰器时,我相信我无法访问该类实例的属性...
  • 当然最大的问题是,当你装饰方法时,你无法访问该方法所在的类?您可以在wrapper 中访问self(因此测试isinstance(self, ...)type(self))(当前为args[0],但您可以明确表示),但您不能包含当前正在使用的类在whitelist 中定义。您将如何将'typeB' 解析为实际课程?
  • 所以我的术语可能会令人困惑。当我使用 'typeB' 作为上面的示例时,它被定义为类实例上的一个属性——比如 B.genus_type = 'typeB'。我希望一切都可以是一个类,所以 type(A) == type(B),但是 A.genus_type != B.genus_type。
  • 那么你为什么不检查一下例如args[0].genus_type in whitelist 里面wrapper?
  • 嗯,所以我不太了解为什么这不起作用,但我会在上面粘贴结果。基本上它只是返回 arg 列表?

标签: python python-2.7 python-decorators descriptor


【解决方案1】:

如果我正确理解你的情况,你正在寻找的是a closure——一个可以参考的函数 外部函数的本地命名空间。

由于您将['typeB'] 传递给valid_for,如

@valid_for(['typeB'])

我们应该让valid_for 成为一个返回装饰器的函数。 装饰器依次接受一个函数(新生方法)作为输入并返回另一个 (wrapper) 函数。

wrapper 下方是一个闭包,它可以访问 typelist 的值 运行时在其体内。


import functools
def valid_for(typelist):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(self, *args):
            if self.genus_type in typelist:
                return func(self, *args)
            else:
                # handle this case
                raise NotImplementedError(
                    '{} not in {}'.format(self.genus_type, typelist))
        return wrapper
    return decorator

class A(object):
    def __init__(self):
        self.genus_type = 'typeA'
    @valid_for(['typeB'])
    def do_something_cool(self):
        print 'blah'

a = A()
try:
    a.do_something_cool()
except NotImplementedError as err:
    print(err)
    # typeA not in ['typeB']

a.genus_type = 'typeB'
a.do_something_cool()
# blah

【讨论】:

    猜你喜欢
    • 2015-12-10
    • 1970-01-01
    • 1970-01-01
    • 2017-03-21
    • 1970-01-01
    • 1970-01-01
    • 2011-06-22
    • 2013-09-05
    • 1970-01-01
    相关资源
    最近更新 更多