Python Django:在视图中向对象添加属性还是制作数据字典更好?
Posted
技术标签:
【中文标题】Python Django:在视图中向对象添加属性还是制作数据字典更好?【英文标题】:Python Django: in view is it better to add properties to an object or make a dictionary of the data? 【发布时间】:2014-01-14 13:16:02 【问题描述】:在这种情况下,我的模型并不重要,我想这是一个基本的 Python 问题。
假设我有一个项目的查询集,我想为每个项目计算一些东西以显示在模板中。
在我看来,我可以创建一个对象列表,对于每个对象,我可以在该对象上设置一个属性以进行计算,然后我可以在模板中显示它。或者我可以创建一个字典列表,只获取我需要在每个字典中显示的字段以及计算字段。在一般实践中,哪个性能更好?
为了清晰起见,一个过于简化的示例(我知道我可以从模板中调用 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)
模板.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 标准或欺骗系统。或者,我可以将这些其他查找存储在字典中,将对象存储在一个键/对中,并将各种详细信息存储在单独的键/对中。这在视图中需要做更多的工作,但我很好奇这样做是否会更好地提高性能或符合标准?
抱歉,如果我最初的问题不够清楚,或者我的示例造成了混乱。
【问题讨论】:
字典与属性在性能方面无关紧要,您需要专注于减少数据库查询的总数。查看更新的答案 【参考方案1】:肯定是方法1。
方法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
(请参阅此处的 Python 官方“PEP8”样式指南http://www.python.org/dev/peps/pep-0008/#function-names)
如果您的get_age
方法没有side-effects 并且不带任何参数,您可能希望将其设为property
,这是Python 的一种无需括号即可访问的getter 方法。
在这种情况下,将其命名为 age
是有意义的:
@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?
更新
参考您更新的问题...作为样式问题,我仍然会在模型上创建这些(last_30
等)方法,而不是在视图代码中的每个模型实例上添加临时属性。
从性能的角度来看,方法查找与字典等在内存、处理时间等方面的差异在大多数现实世界的情况下是微不足道的......到目前为止,这类代码中最大的性能考虑因素通常是 数字数据库查询数。
如果您知道要为查询集中的每个项目执行一个额外的查询(或三个),那么值得寻找在一个或多个大查询中获取所有内容的方法。
在某些情况下,您也许可以使用annotate()
方法:https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate
(我认为在您的示例中这是不可能的)
在您上面的特定代码中,您只需要查询 90 天(最旧的间隔),您可以在 Python 中过滤 60 天和 30 天的集合,而无需再次查询数据库。
但这仍然会在您的people
查询集中执行一个额外的查询每个项目。最好对所有人(或任何子集)的Login
对象进行一次大查询。由于Person
在Login
上存在外键关系,我们可以在查询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 %
【讨论】:
直接使用模型方法的另一个优点是可测试性:您可以轻松地测试单个模型方法和属性;测试整个视图“blob”可能会更加困难 是的,该模型的重点是将所有此类逻辑保存在一个地方,因此您可以在多个视图等中使用它 好的,谢谢您的回复。我了解直接在模板中调用该方法,但假设它是一组更密集的数据库计算。尽管可以这样做,但最好不要从模板中调用 DB 函数。也许我的例子很糟糕。我不应该使用类方法,而是想在视图中计算一些东西。假设我正在查找过去 30 天内特定 Person 的所有登录会话,但没有用于此的类方法。在 Python 中动态分配一个属性,比如说另一个 Queryset,还是更好吗? 为什么不为此创建一个方法,如果还没有呢?如果您不“拥有”该模型,即如果它来自第三方应用程序,则可以使用代理模型;请参阅source code of django-zinnia-blog 以获得一个很好的示例。 @Anentropic 如果可以从模板中调用person.logins_since(30)
等,一切都会变得如此简单。然而,我们必须编写自定义标签来做这样的事情......【参考方案2】:
不要打扰字典。通过查看这两种方法,我无法理解第二种方法解决了什么真正的问题。从模板的角度来看,两种方法都产生相同的结果,但第一种方法比第二种方法短得多。
但是,我在您的代码中看到了一些问题:
首先,如果你真的关心性能,你应该避免做不必要的工作。第一种方法中的年龄设置步骤并不是真正解决问题的最佳方法,它的内存使用量会随着您向数据库中添加新人而增加。
您是否知道您可以在模板中使用不接受任何参数(或者在方法的情况下仅接受“self”参数)的函数/方法,就像它们是属性一样?如果将“getAge”重命名为“age”,则可以将第一个方法代码简化为:
def method1_object_property(request):
people = Person.objects.all()
return render_to_response('template.htm', 'people': people)
另外请花点时间熟悉一下规范编写 python 代码约定的 PEP8 文档:http://www.python.org/dev/peps/pep-0008/
根据 PEP8 “getAge” 不是正确的函数名称,应该使用下划线小写,因此“get_age”是好的,而“getAge”是“unpythonic”。然而,因为这个函数基本上是动态计数的属性,你可以将它保留为“年龄”并可选地添加@property 装饰器,使其具有与python代码中的django模板相同的行为。
现在关于优化。评估时设置的 Django 查询的默认行为是将数据库返回的所有结果转换为 python 对象。因此,如果表中有 2 行,Person.objects.all() 将产生两个 Person 对象。但是如果你有 9000 行,它会产生 9000 个 Python 对象,这些对象会立即消耗大量内存。
你有两种方法来保护自己免受这种情况的影响:
首先,您可以将查询集限制为指定数量的项目,方法是硬编码以获取 5 个最新成员,或者实现分页,或者最后通过在用户输入人员搜索条件后显示个人资料。
这里的 Django 文档介绍了限制(“切片”)查询集:https://docs.djangoproject.com/en/1.6/topics/db/queries/#limiting-querysets
其次,您可以通过在查询末尾添加 .iterator()
使 django 使用惰性方法将数据库行转换为 python 对象。这将使 django 在查询集返回时将行转换为对象,这对内存更友好,但对您的代码施加了一些限制,因为您将获得生成器对象,而不是类似列表的对象。
此更改将使 Django 为结果行创建一次 Person 对象,使用它在列表中显示行,然后将其丢弃。
【讨论】:
谢谢,以后多看.iterator()
。我真正想知道的是,是否最好将一些计算或相关查询集存储为使用对象点属性方法指定的属性,或者我是否应该将这些变量存储在字典中而不是作为对象属性。说,我没有将它们作为类方法,而是作为视图中的单独查询集/计算。以上是关于Python Django:在视图中向对象添加属性还是制作数据字典更好?的主要内容,如果未能解决你的问题,请参考以下文章