python函數參數的pack與unpack
上周在使用django做開發的時候用到了mixin(關於mixin我還要寫一個博客專門討論一下,現在請參見這里),其中又涉及到了一個關於函數參數打包(pack)的問題,導致延誤了開發時間,所以在這里記錄一下,稍后會說到具體的背景。
背景交代:
具體情景是這樣的,我需要一個view可以在查詢的同時可以分頁,又可以在返回的 queryset 上做更多的查詢操作。為了解決這個問題,我自己寫了一個mixiin :
class MultipleOjbectQueryPageMixin(object):
'''
query_params = [['filter , 'tags__label__contains, 'wow' ],[],[]]
'''
query_params = None
paginate_by = None
page_size_kwargs = 'size'
#新加入的方法
def get_paginate_by(self, queryset):
if self.page_size_kwargs in self.request.kwargs:
self.paginate_by = int(self.request.kwargs[self.page_size_kwargs])
return self.paginate_by
def do_query(self, queryset):
if self.query_params:
iterator = iter(self.query_params)
try:
param = iterator.next()
try:
if params[1] is not None and params[0] is not None:
#函數參數解包的問題
queryset = getattr(queryset, params[0])(**{params[1] : params[2]})
return queryset
else:
queryset = getattr(queryset,params[0])()
return queryset
except AttributeError as e:
print e
except StopIteration as e:
print e
else:
return queryset.all()
#新加入的方法
def get_queryset(self):
if self.queryset is not None:
queryset = self.queryset
if isinstance(queryset, QuerySet):
queryset = self.do_query(queryset)
elif self.model is not None:
queryset = self.do_query(self.model._default_manager)
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define"
"%(cls)s.model, %(cls)s.queryset, or override"
"%(cls)s.get_queryset()." % {
'cls' : self.__class__.__name__
}
)
ordering = self.get_ordering()
if ordering:
if isinstance(ordering, six.string_types):
ordering = (ordering, )
queryset = queryset.order_by(*ordering)
return queryset
這個 mixin只自定義了2個方法,其中一個是從url中獲取當前分頁的頁面大小,也就是page_size, 在MultipleObjectMixin類中這這個參數的名字叫做 paginate_by ,在 django.views.generic.ListView 中直接繼承了這個mixin , 不過ListView只有基本的分頁功能 (你可以直接在url中傳入/?page=1,來進行分頁,mixin中都寫好了),並沒有可以從url中獲取頁面大小,我增加的方法可以直接從url中獲取要分頁的頁面大小,然后傳入page參數就可以完成分頁。
另外一個方法比較復雜,它擁有了基本的查詢功能,我的初衷是要從url中得到博客的標簽信息,然后根據標簽查詢到相應標簽下的博客,所以就寫了一個方法,這個方法由 get_queryset(self) 這個方法來調用, 這樣就可以比較方便的在已有代碼的基礎上去做查詢,實際使用中我寫了一個功能類似於BaseListView類的View類來繼承該mixin, 然后重寫自己的get方法就可以了,在myapp.views中使用起來非常方便,直接繼承自己寫的view類,然后在類中定義需要的屬性,就可以了,myapp.views中沒有方法,看起來非常整潔,我認為這就是CBV的好處了。
如果你要說我直接自己寫get方法,直接在request中獲取tag的值,然后
pk = request.kwargs['id']
tag = Tag.objects.get(pk = pk)
blogs = tag.blogs.all()
那么我會說這完全是可以的,但是你以后需要再查詢別的東西的時候,你必須一直寫get方法,這樣的寫法,會導致CBV的優勢盪然無存,那還不如直接去寫FBV的好,大家都說FBV容易理解,其實我覺得讓我寫的話肯定是寫CBV,不喜歡看到亂糟糟的代碼。
函數參數的解包問題:
由於已經很久很久沒有寫python了,所以不免會忘記一些東西,這次跳的一個小坑就是在函數參數上出了問題。
我想實現的是這樣的效果
params = { 'tags__label__conatains' : wow'}
Blog.objects.filter(params)
上面這樣直接傳入字典是不行的
因為filter中的參數是一個有默認值的tags__table__contains 參數,
我的目的是想給它一個值,這樣在func(*args, **kwargs)里是解析不到kwargs里的,實際上傳入一個字典的結果都在args 參數里,因為我們傳入的是一個對象,而不是kv, 只有傳入test(a=1, b=2)這樣的值才會被解析到kwargs中,現在我們這樣寫
Blog.objects.filter(params.keys()[0] = params.values()[0])
還是不行
接着我們這樣寫
k = params.keys()[0]
v = params.values()[0]
Blog.objects.filter( k = v)
你會發現這樣寫可以,但是我們需要的參數變成k了,而不是 tags__label__contains
所以這樣也是不行的。
然后我們又換了一種方法
Blog.objects.Filter({ k : v})
結果這個參數又跑到args里去了,
最后正確的寫法是這樣的
Blog.objects.Filter(**{k:v})
這樣先解包就可以把帶變量的參數傳到kwargs
對於args來說是一樣的
def test(*args, **kwargs):
print args
print kwargs
s = (1,2,3,4,)
test(s)
輸出:
((1,2,3,4),)
很明顯系統把s這個元組當成了一個對象,如果你打算傳入之后對,args進行遍歷操作話,會發現args里只有一個對象,但是我明明傳入了一個有4個元素的元組啊。
正確的寫法是:
test(*s)
輸出為:
(1,2,3,4)
這個時候你就可以去遍歷了,絕對沒問題了