假设我有一个项目的查询集,我想计算每个要在模板中显示的东西.
在我看来,我可以创建一个对象列表,对于每个对象,我可以在该对象上设置一个属性进行计算,然后我可以在模板中显示它.或者我可以创建一个字典列表,只获取我需要在每个字典中显示的字段以及计算字段.对于性能和一般实践来说哪个更好?
一个过于简化的示例,为了清晰起见(我知道我可以从模板中调用getAge(),我真正计算的是更复杂的,对于性能,我想在视图代码中进行计算):
models.py:
class Person(models.Model):
first_name = ...
last_name = ...
date_of_birth = ...
.
.
.
def getAge(self):
return ... # return the calculated years since date_of_birth
views.py:
def method1_object_property(request):
people = Person.objects.all()
for p in people:
p.age = p.getAge()
return render_to_response('template.htm',{'people': people})
def method2_dictionary(request):
people = Person.objects.all()
data = list()
for p in people:
row = dict()
row['first_name'] = p.first_name
row['last_name'] = p.last_name
row['age'] = p.getAge()
data.append(row)
return render_to_response('template.htm',{'people': data})
template.htm:
<ul>
{% for p in people %}
{{ p.first_name }} {{ p.last_name }} (Age: {{ p.age }})
{% endfor %}
</ul>
到目前为止,这两种方法都可以正常工作,我只是好奇首选方法是什么以及为什么.是否存在使用对象点属性方法(object.new_field =’some_detail’)将新字段动态分配给内存中现有对象的性能问题?
更新:
是的,我知道在我的例子中我可以从模板调用getAge(),是的,这是方法的不正确的命名标准,应该是带有下划线的小写.我认为我的例子太简单了,而且让我真正想知道的事情蒙上阴影.
将信息添加到我想要在视图中显示的对象的最佳方法是什么,该视图不是模型层的一部分.假设我得到一个Person对象的QuerySet,并想要计算他们在过去30,60和90天登录我网站的次数.我想动态为每个Person对象创建三个“属性”.我可以在视图中设置它
for p in people:
p.last_30 = Login.objects.filter(person=p,login_date__gt=date.today()-timedelta(days=30))
p.last_60 = Login.objects.filter(person=p,login_date__gt=date.today()-timedelta(days=60))
p.last_90 = Login.objects.filter(person=p,login_date__gt=date.today()-timedelta(days=90))
然后在我的模板中,我可以显示那些“属性”.我只是想确保我没有违反某些Python标准或欺骗系统.或者,我可以将这些其他查找存储在字典中,其中对象在一个键/对中,并且各个细节在单独的键中.在视图中这是一个更多的工作,但我很好奇是否更好的性能或标准的合规性这样做?
对不起,如果我的原始问题不够清楚,或者我的例子增加了混乱.
解决方法
方法2毫无意义,您可以直接在模板中迭代查询集,无需在视图中构建中间的“词典列表”.例如,你可以这样做:
def method2_dictionary(request):
people = Person.objects.all()
return render_to_response('template.htm',{'people': people})
在您的模板中:
{% for p in people %}
{{ p.first_name }}
etc
{% endfor %}
回到方法1 ……
这个:p.age = p.getAge()也没有意义,你可以直接调用模板中的方法{{p.getAge}}(只要你的方法不带参数),请看这里的文档:
https://docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls
请注意,在Python中,我们通常更喜欢使用“带下划线的小写”作为方法名称,例如def get_age(self)和{{p.get_age}}
(参见http://www.python.org/dev/peps/pep-0008/#function-names官方’PEP8’样式指南)
如果你的get_age方法没有side-effects并且没有参数你可能想让它成为一个属性,这是Python的方法,你可以访问没有括号的getter方法.
在这种情况下,将它命名为年龄是有意义的:
@property
def age(self):
return ... # return the calculated years since date_of_birth
并在您的模板中:
{% for p in people %}
{{ p.first_name }}
{{ p.age }}
etc
{% endfor %}
有关Python属性的更多信息,请参见此处:
http://docs.python.org/2/library/functions.html#property
在这个SO问题中的更多信息:
Real world example about how to use property feature in python?
UPDATE
参考您更新的问题…作为样式问题,我仍然会在模型上创建这些(last_30等)方法,而不是在视图代码中的每个模型实例上添加ad hoc属性.
从性能的角度来看,在大多数现实世界中,方法查找与字典等的内存,处理时间等的差异是微不足道的……到目前为止,这种代码中最大的性能考虑通常是数据库查询的数量.
如果您知道要为查询集中的每个项目执行额外的查询(或三个),那么值得寻找在一个或多个大查询中获取所有内容的方法.
在某些情况下,您可以使用annotate()方法:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate
(我认为这不可能在你的例子中)
在上面的特定代码中,您只需查询90天(最早的间隔),您可以在Python中过滤60天和30天的集,而无需再次查询数据库.
但是,对于人员查询集中的每个项目,仍然会进行一次额外查询.最好为人员的所有(或任何子集)的Login对象执行一个大查询.由于登录时Person有一个外键关系,我们可以在查询Login模型时使用select_related()在一个大查询中获取Person实例:
def method3(request):
logins = Login.objects.filter(
person__in=Person.objects.all(),login_date__gt=date.today() - timedelta(days=90)
).order_by('person','login_date').select_related()
return render_to_response('template.htm',{'logins': logins})
注意,如果你真的在做Person.objects.all(),你就不需要上面的person__in过滤器,只要你想以某种方式过滤Person集.
现在我们在一个大查询中获得了所有数据,我们可以在python端执行我们需要的操作来显示数据.例如,在模板中我们可以使用regroup标签:
{% regroup logins by person as people %}
{% for person in people %}
{% with person.grouper as p %}
{{ p.first_name }}
{% for login in person.list %}
{{ login.login_date }}
{% endfor %}
{% endwith %}
{% endfor %}
你可以更进一步,为登录日期范围写一个自定义标签…我不会在这里详细说明,但在你的模板中它可能看起来像:
{% regroup logins by person as people %}
{% for person in people %}
{% with person.grouper as p %}
{{ p.first_name }}
{% logins_since person.list 60 as last_60_days %}
{% logins_since person.list 30 as last_30_days %}
{% for login in last_30_days %}
{{ login.login_date }}
{% endfor %}
{% for login in last_60_days %}
{{ login.login_date }}
{% endfor %}
{% endwith %}
{% endfor %}