方法

时间:2016-03-28 13:09:59

标签: python introspection

我想知道是否可以检索方法使用的属性。

以下是解释我要做的事情的示例:

class Foobar(object):

    bar = 123

    @property
    def foo(self):
        return self.baz * self.bar

    def get_foobar(self):
        return '{} * {} = {}'.format(self.baz, self.bar, self.foo)

我希望能够知道调用Foobar().get_foobar()需要设置self.bazself.barself.foo,而不会实际调用它。

我目前的做法是向get_foobar添加属性:

def get_foobar(self):
    return '{} * {} = {}'.format(self.baz, self.bar, self.foo)
get_foobar.requires = ['baz', 'bar', 'foo']

但是,我认为它有点多余,可能容易出错。

这样做有什么意义?

你可能想知道我为什么要这样做。

在我的具体案例中,Foobar实际上是一个django模型。而属性实际上是从数据库中检索的字段。我创建了一个View mixin,使我能够指定视图所需的字段。例如:

class SomeUserView(ModelMixin, View):
    model = User
    fields = [
        'username', 'groups__name', 'permissions__id',
        'orders__products__name', 'orders__products__price',
    ]

通过一些内省,我可以构建查询,该查询将检索视图所需的所有数据。在这种情况下,它看起来像:

User.objects.all().only(
    'username', 'groups__name', 'permissions__id',
    'orders__products__name', 'orders__products__price',
).select_related('groups', 'permissions', 'orders__products')

现在,fields属性不仅可以包含字段,还可以包含实例方法,这些方法可能需要未列出的字段。假设我有:

class User(models.Model):
    def __str__(self):
        return '{} ({})'.format(self.username, self.email)
    __str__.requires_fields = ['username', 'email']

class Permission(models.Model):
    def __str__(self):
        return self.name
    __str__.requires_fields = ['name']

class SomeUserView(ModelMixin, View):
    model = User
    fields = [
        'username', 'groups__name', 'permissions', '__str__',
        'orders__products__name', 'orders__products__price',
    ]

然后,查询将是:

User.objects.all().only(
    'username', 'groups__name', 'permissions__name', 'email',
    'orders__products__name', 'orders__products__price',
).select_related('groups', 'permissions', 'orders__products')

这样可行,但是我想避免为每个方法设置requires_fields属性,并且每次修改方法时都必须仔细更新它。

我对这种可能性没有很大希望,但仍然在问。

作为替代方案,我想我可以写一个装饰器,例如:

class Wrapper:
    def __init__(self, obj, fields):
        self.obj = obj
        self.fields = set(fields)
        self._used_fields = set()

    def __getattribute___(self, name):
        if name not in self.fields:
            raise AttributeError(
                "You forgot to set '{}' as required field".format(name))
        self._used_fields.add(name)
        return getattr(self.obj, name)

    def exit_method(self):
        if self.fields != self._used_fields:
            warnings.warn(
                "Make sure the following fields are actually needed: {}".format(
                    self.fields - self._used_fields))

def requires_fields(*fields):
    def decorator(func):
        def inner(self, *args, **kwargs):
            self_wrapper = Wrapper(self, fields)
            func(self_wrapper, *args, **kwargs)
            self_wrapper.exit_method()
        inner.__name__ = func.__name__
        inner.requires_fields = fields
    return decorator

@requires_fields('baz', 'bar', 'foo')
def get_foobar(self):
   return '{} * {} = {}'.format(self.baz, self.bar, self.foo)

这样我就可以轻松“发现错误”。但它看起来很奇怪。 :d

1 个答案:

答案 0 :(得分:1)

  

我希望能够知道调用Foobar()。get_foobar()   将要求self.baz,self.bar和self.foo设置,没有   实际上是在呼唤它。

不,当然不是,一般情况下。这是我的 foobar:

return self.foo if isHalting() else self.bar
return eval("self.foo")

但更现实的是,您有几种选择:

  1. 编译the syntax tree。检查字段节点(或您感兴趣的任何一个),然后查看它们的值。这可能很耗时。熟悉访问者模式,这可能是一种非常强大的解析方式;这就是像C或Java这样的静态语言如何引发编译错误,变量将被定义为*。

  2. 使用function.get.__closure__检查闭包这不会帮助您检索所有字段,但可以帮助您确定哪些字段已绑定到该函数,这在某些情况下可能相关

  3. 最好的选择是使用try / except语句。根据需要设置它们的样式,例如

    def foo(): s = '' try: s += str(self.baz) except: raise NoBazError("No Baz!")

    对我来说,这是最好的选择,因为它是最明确和最直接的选择。

  4. *如果您使用字段,即使是静态类型的语言也会很难,但可以检查局部变量的初始化,有时