Python“null coalescing”/级联属性访问

时间:2014-03-05 12:27:35

标签: python django

免责声明:我对Python和Django比较陌生。

虽然我的问题不是Django特有的,但我经常在这种情况下遇到它。偶尔我会在我的网站上添加新功能,这些功能假设经过身份验证的用户和只有经过身份验证的用户才能访问的属性。然后当我在另一个我没有登录的浏览器中访问该页面时,我收到“AttributeNotFound”错误,因为该用户实际上是一个AnonymousUser。

那么什么,没什么大不了的。只需检查是否已通过身份验证:

def my_view(request):
    if request.user.is_authenticated():
        posts = user.post_set.filter(flag=True).all()
    else:
        posts = None

    return render(request, 'template.html', {posts: posts})

然后在模板中:

<h1>Your Posts</h1>
<ul>
{% for post in posts %}
    <li>{{ post.name }}</li>
{%else%}
    <p>No posts found.</p>
{%endfor%}
</ul>

但是我在我的代码中注意到了这种模式,如果遇到调用链中的某个条件(即属性不是None),或者只返回None,空集或类似的东西,我想要做一些事情。

所以我立即选择了(在Scala中找到),但是由于python中lambda表达式的语法繁琐,我不太喜欢结果:

# create a user option somewhere in a RequestContext
request.user_option = Some(user) if user.is_authenticated() else Empty()

# access it like this in view
posts = request.user_option.map(lambda u: u.post_set.filter(flag=True).all()).get_or_else(None)

代码被选项语法模糊,几乎隐藏了实际的意图。此外,我们必须知道必须对用户进行身份验证才能拥有post_set属性。

相反,我想要的是一个“Null coalescing”类操作符,它允许我编写这样的代码:

def my_view(request):
    user_ = Coalesce(user)
    posts = user_.post_set.filter(flag=True).all() # -> QuerySet | None-like
    return render(request, 'template.html', {posts: posts})

然后我开始编写一个包装类,实际上允许我这样做:

class Coalesce:
    def __init__(self, inst):
        self.inst = inst

    def __call__(self, *args, **kwargs):
        return Coalesce(None)

    def __iter__(self):
        while False:
            yield None

    def __str__(self):
        return ''

    def __getattr__(self, name):
        if hasattr(self.inst, name):
            return getattr(self.inst, name)
        else:
            return Coalesce(None)

    def __len__(self):
        return 0

使用Coalesce包装用户对象允许我按照自己的意愿编写代码。如果用户没有定义post_set属性,它将表现得像一个空集/列表(关于iterables)并计算为False。所以它在Django模板中工作。

因此,假设通过命名约定或显式转换来标记coalesce'd对象,这是可以做的还是你应该避免它?如果避免,在没有大量if-else-blocks的情况下处理可选属性的最佳方法是什么?

1 个答案:

答案 0 :(得分:1)

您的解决方案是Null object pattern的变体。只要明确使用它,我认为使用它是完全没问题的。特别是当它提高代码的可读性时。

您还可以使用以下方法扩展Coalesce类,以允许像first_post = user_.post_set.filter(flag=True)[0]这样的索引访问

def __getitem__(self, i):
    try:
        return self.inst[i]
    except (TypeError, IndexError, KeyError):
        return Coalesce(None)

对于membership tests innot in

def __contains__(self, i):
    try:
        return i in self.inst
    except (TypeError, IndexError, KeyError):
        return False