2014-03-05 73 views
0

聲明:我對Python和Django比較陌生。Python「空合併」/級聯屬性訪問

雖然我的問題不是特定於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> 

但是我注意到這種模式在我的代碼很多,我想要做的事當且僅當在一定條件呼叫鏈被滿足(即屬性不是無)或者只返回無,空集或其他類似的東西。

所以我雖然選項(如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屬性。

相反,我想要的是一個「空合併」般的操作,讓我寫這樣的而不是代碼:

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 

包裝紙用戶對象與合併讓我寫的代碼,我想。如果用戶沒有定義屬性post_set,它將表現得像一個空集/列表(關於迭代),並計算爲False。所以它可以在Django模板中使用。

因此,假設通過命名約定或顯式轉換來標記合併的對象,可以這樣做嗎?或者你會說應該避免這樣做嗎?如果避免,如果沒有大量if-else-blocks,處理可選屬性的最佳方法是什麼?

回答

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 testsinnot in索引訪問:

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

非常感謝您!很高興知道這是一個名字。我應該更多地考慮一些常見的模式。 :) – Bunkerbewohner