Python Django:在视图中,最好是向对象添加属性还是创建数据字典?

时间:2022-08-30 14:26:52

My models don't really matter in this case, this is a fundamental Python question, I suppose.

在这种情况下,我的模型并不重要,我认为这是一个基本的Python问题。

Say I have a queryset of items and I want to calculate some things for each one to be displayed in a template.

假设我有一个项目的查询集,我想计算每个要在模板中显示的东西。

In my view, I can create a list of objects, and for each object I can set a property on that object for the calculation, then I can display that in the template. OR I can create a list of dictionaries and only get the fields I need to display in each dictionary along with the calculated field. Which is better for performance, and in general practice?

在我看来,我可以创建一个对象列表,对于每个对象,我可以在该对象上设置一个属性进行计算,然后我可以在模板中显示它。或者我可以创建一个字典列表,只获取我需要在每个字典中显示的字段以及计算字段。对于性能和一般实践中哪个更好?

An overly-simplified example for clarity (I know I can call getAge() from the template, what I am really calculated is more complex and for performance I want to do the calculations in the view code):

一个过于简化的示例,为了清晰起见(我知道我可以从模板中调用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>

Both methods work just fine so far as I can tell, I was just curious what the preferred method would be and why. Are there performance issues assigning new fields dynamically to an existing object in memory using the object dot property method (object.new_field = 'some_detail')?

到目前为止,这两种方法都可以正常工作,我只是很好奇首选方法是什么以及为什么。是否存在使用对象点属性方法(object.new_field ='some_detail')将新字段动态分配给内存中现有对象的性能问题?

UPDATE:

Yes, I know in my example I can call getAge() from template, and yes, this is the incorrect naming standard for methods which should be lowercase with underscores. I think my example is too simple and is clouding what I really want to know.

是的,我知道在我的例子中我可以从模板调用getAge(),是的,这是方法的不正确的命名标准,应该是带有下划线的小写。我认为我的例子过于简单,而且让我真正想知道的事情蒙上阴影。

What is the best way to add information to an object that I want displayed in the view that is not a part of the model layer. Say I get a QuerySet of Person objects and want to calculate how many times they have logged into my website in the last 30, 60 and 90 days. I want to create three "properties" for each Person object on the fly. I can set this in the view with

将信息添加到我想要在视图中显示的对象的最佳方法是什么,该视图不是模型层的一部分。假设我获得了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))

Then in my template I can display those "properties." I just wanted to make sure I'm not violating some Python standard or cheating the system. Alternately, I could store these other lookups in a dictionary with the object in one key/pair, and the various details in separate ones. This is a bit more work in the view, but I was curious if it is better for performance or compliance of standards to do so?

然后在我的模板中,我可以显示那些“属性”。我只是想确保我没有违反某些Python标准或欺骗系统。或者,我可以将这些其他查找存储在字典中,其中对象在一个键/对中,并且各个细节在单独的键中。在视图中这是一个更多的工作,但我很好奇是否更好的性能或标准的合规性这样做?

Sorry if my original question was not clear enough, or my example added confusion.

对不起,如果我的原始问题不够清楚,或者我的例子增加了混乱。

2 个解决方案

#1


8  

Definitely method 1.

绝对方法1。

Method 2 is pointless, you can iterate over the queryset directly in the template, there is no need to build up an intermediate 'list of dicts' in your view. eg you just can do:

方法2毫无意义,您可以直接在模板中迭代查询集,不需要在视图中建立中间的“词典列表”。你可以这样做:

def method2_dictionary(request):
    people = Person.objects.all()
    return render_to_response('template.htm', {'people': people})

in your template:

在您的模板中:

{% for p in people %}
    {{ p.first_name }}
    etc
{% endfor %}

Coming back to method 1...

回到方法1 ......

This: p.age = p.getAge() is also pointless, you can directly call the method in your template as {{ p.getAge }} (as long as your method does not take arguments) see the docs here:
https://docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls

这个:p.age = p.getAge()也没有意义,你可以直接调用模板中的方法{{p.getAge}}(只要你的方法不带参数),请看这里的文档:https: //docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls

Note that in Python we generally prefer to use 'lower-case with underscores' for method names, eg def get_age(self) and {{ p.get_age }}
(see the official 'PEP8' style guide for Python here http://www.python.org/dev/peps/pep-0008/#function-names)

请注意,在Python中,我们通常更喜欢使用'带下划线的小写'作为方法名称,例如def get_age(self)和{{p.get_age}}(请参阅官方的'PEP8'样式指南,在这里http:// www.python.org/dev/peps/pep-0008/#function-names)

If your get_age method has no side-effects and takes no arguments you may like to make it a property which is Python's way of having a getter method you can access without the parentheses.

如果你的get_age方法没有副作用并且不带任何参数,你可能希望将它作为一个属性,这是Python使用getter方法的方法,你可以在没有括号的情况下访问它。

In this case it would make sense to name it just age:

在这种情况下,将它命名为年龄是有意义的:

@property
def age(self):
    return ... # return the calculated years since date_of_birth

and in your template:

并在您的模板中:

{% for p in people %}
    {{ p.first_name }}
    {{ p.age }}
    etc
{% endfor %}

For more info about Python properties, see here:
http://docs.python.org/2/library/functions.html#property

有关Python属性的更多信息,请参见此处:http://docs.python.org/2/library/functions.html#property

Some more info in this SO question:
Real world example about how to use property feature in python?

在这个SO问题中的更多信息:关于如何在python中使用属性功能的真实世界示例?

UPDATE

Referring to your updated question... as a question of style I would still make these (last_30 etc) methods on the model rather than adding ad hoc properties onto each model instance in the view code.

参考您更新的问题...作为样式问题,我仍然会在模型上制作这些(last_30等)方法,而不是在视图代码中的每个模型实例上添加ad hoc属性。

From a performance perspective, the difference in memory, processing time etc of method lookup vs dictionaries etc is trivial in most real world situations ...by far the biggest performance consideration in this kind of code is usually the number of database queries.

从性能的角度来看,在大多数现实世界的情况下,方法查找与字典等的内存,处理时间等的差异是微不足道的...到目前为止,这种代码中最大的性能考虑通常是数据库查询的数量。

If you know you're going to do an extra query (or three) for each item in your queryset it's worth looking for ways to get everything in one or more big queries.

如果您知道要为查询集中的每个项目执行额外的查询(或三个),那么值得寻找在一个或多个大查询中获取所有内容的方法。

In some cases you may be able to use the annotate() method:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate

在某些情况下,您可以使用annotate()方法:https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate

(I don't think that's possible in your example)

(我不认为你的例子中有可能)

In your specific code above you only need to query for the 90 days (oldest interval) and you could filter the 60 and 30 days sets from that in Python without querying the db again.

在上面的特定代码中,您只需查询90天(最早的间隔),您可以在Python中过滤60天和30天的集,而无需再次查询数据库。

But that would still be doing one extra query per item in your people queryset. It'd be better to do one big query for the Login objects for all (or whatever subset) of the people. Since there is a foreign key relation for Person on Login we can use select_related() to get the Person instances in one big query when we query Login model:

但是,对于人员查询集中的每个项目,仍然会进行一次额外查询。最好为人员的所有(或任何子集)的Login对象执行一个大查询。由于Person上有一个外键关系,我们可以使用select_related()在查询Login模型时在一个大查询中获取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})

Note if you're really doing Person.objects.all() you wouldn't need the person__in filter above, only if you wanted to filter the Person set in some way.

注意,如果你真的在做Person.objects.all(),那么只要你想以某种方式过滤Person集合,你就不需要上面的person__in过滤器。

Now that we got all the data in one big query we can do what we need to on the python side to display the data. eg in the template we could use the regroup tag:

现在我们在一个大查询中获得了所有数据,我们可以在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 %}

You could take this further and write a custom tag for the login date ranges... I won't detail that here but in your template it could look something like:

你可以更进一步,为登录日期范围写一个自定义标签...我不会在这里详细说明,但在你的模板中它可能看起来像:

{% 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 %}

#2


1  

Don't bother with dictionaries. By looking at those two methods, I'm failing to understand what real problem second one solves. From template's perspective both methods produce same outcome, but first one is far shorter than second one.

不要打扰词典。通过查看这两种方法,我无法理解第二个解决的真正问题。从模板的角度来看,两种方法都产生相同的结果,但第一种方法远远短于第二种方法。

However, there are some issues I see in your code:

但是,我在您的代码中看到了一些问题:

First, if you really care about performace, you should avoid performing needless work. The age-setting step in first method is not really best way to solve problem and it's memory usage will grow as you are adding new persons to database.

首先,如果你真的关心表演,你应该避免进行不必要的工作。第一种方法中的年龄设置步骤并不是解决问题的最佳方法,而且当您向数据库添加新人时,内存使用量会增加。

Did you know that you can use functions/methods that don't accept any arguments (or just "self" argument in case of methods) in templates like they were attributes? If you rename "getAge" to "age", you can simplify first method code down to this:

你是否知道你可以在模板中使用不接受任何参数的函数/方法(或者只是方法中的“self”参数),就像它们是属性一样?如果将“getAge”重命名为“age”,则可以将第一个方法代码简化为:

def method1_object_property(request):
    people = Person.objects.all()
    return render_to_response('template.htm', {'people': people})

Also please take while to familiarize yourself with PEP8 document that regulates conventions for writing python code: http://www.python.org/dev/peps/pep-0008/

另外,请熟悉PEP8文档,该文档规定了编写python代码的约定:http://www.python.org/dev/peps/pep-0008/

As per PEP8 "getAge" is not correct function name, and underscore lowercase should be used, ergo "get_age" is good while "getAge" is "unpythonic". However because this function is basically dynamically counted attribute, you can leave it as "age" and optionally add @property decorator to it, giving it same behaviour it does in django template in python code.

根据PEP8,“getAge”不是正确的函数名,应使用下划线小写,ergo“get_age”是好的,而“getAge”是“unpythonic”。但是因为这个函数基本上是动态计数的属性,你可以将它保留为“age”,并可选择添加@property装饰器,使其具有与python代码中的django模板相同的行为。

Now about optimisation. Defaut behaviour for Django query set upon evaluation is to convert all results returned by database to python objects. So if you have 2 rows in table, Person.objects.all() will produce two Person objects. But if you have 9000 rows, it will produce 9000 python objects that will immediately consume large amounts of memory.

现在关于优化。评估时Django查询集的Defaut行为是将数据库返回的所有结果转换为python对象。因此,如果表中有2行,Person.objects.all()将生成两个Person对象。但是如果你有9000行,它将产生9000个python对象,这些对象将立即消耗大量内存。

You have two ways to defend yourself against this:

你有两种方法可以防御这种情况:

First, you can limit queryset to specified number of items, either by hardcoding it to fetch, say 5 latest members, or by implementing pagination, or finally by makind display of profiles come after user enters search criteria for persons.

首先,您可以将查询集限制为指定的项目数,可以通过将其硬编码为fetch,比如5个最新成员,或者通过实现分页,或者最后通过makind显示配置文件来在用户输入人员的搜索条件之后。

Limiting ("slicing") querysets is covered by Django docs here: https://docs.djangoproject.com/en/1.6/topics/db/queries/#limiting-querysets

这里的Django文档涵盖了限制(“切片”)查询集:https://docs.djangoproject.com/en/1.6/topics/db/queries/#limiting-querysets

Secondly, you can make django use lazy approach to turning database rows into python objects by adding .iterator() at the end of your query. This will make django turn rows into objects as they are returned by queryset which is more memory friendly, but imposes some limitations on your code because instead of list-like object it you will get generator object.

其次,您可以通过在查询结尾添加.iterator(),使django使用惰性方法将数据库行转换为python对象。这将使django将行转换为对象,因为它们由查询集返回,这对内存更友好,但对代码施加了一些限制,因为代替类似列表的对象,它将获得生成器对象。

This change will make Django create Person object for result row once, use it to display row on list, and then throw it away.

此更改将使Django为结果行创建一次Person对象,使用它来显示列表中的行,然后将其丢弃。

#1


8  

Definitely method 1.

绝对方法1。

Method 2 is pointless, you can iterate over the queryset directly in the template, there is no need to build up an intermediate 'list of dicts' in your view. eg you just can do:

方法2毫无意义,您可以直接在模板中迭代查询集,不需要在视图中建立中间的“词典列表”。你可以这样做:

def method2_dictionary(request):
    people = Person.objects.all()
    return render_to_response('template.htm', {'people': people})

in your template:

在您的模板中:

{% for p in people %}
    {{ p.first_name }}
    etc
{% endfor %}

Coming back to method 1...

回到方法1 ......

This: p.age = p.getAge() is also pointless, you can directly call the method in your template as {{ p.getAge }} (as long as your method does not take arguments) see the docs here:
https://docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls

这个:p.age = p.getAge()也没有意义,你可以直接调用模板中的方法{{p.getAge}}(只要你的方法不带参数),请看这里的文档:https: //docs.djangoproject.com/en/dev/topics/templates/#accessing-method-calls

Note that in Python we generally prefer to use 'lower-case with underscores' for method names, eg def get_age(self) and {{ p.get_age }}
(see the official 'PEP8' style guide for Python here http://www.python.org/dev/peps/pep-0008/#function-names)

请注意,在Python中,我们通常更喜欢使用'带下划线的小写'作为方法名称,例如def get_age(self)和{{p.get_age}}(请参阅官方的'PEP8'样式指南,在这里http:// www.python.org/dev/peps/pep-0008/#function-names)

If your get_age method has no side-effects and takes no arguments you may like to make it a property which is Python's way of having a getter method you can access without the parentheses.

如果你的get_age方法没有副作用并且不带任何参数,你可能希望将它作为一个属性,这是Python使用getter方法的方法,你可以在没有括号的情况下访问它。

In this case it would make sense to name it just age:

在这种情况下,将它命名为年龄是有意义的:

@property
def age(self):
    return ... # return the calculated years since date_of_birth

and in your template:

并在您的模板中:

{% for p in people %}
    {{ p.first_name }}
    {{ p.age }}
    etc
{% endfor %}

For more info about Python properties, see here:
http://docs.python.org/2/library/functions.html#property

有关Python属性的更多信息,请参见此处:http://docs.python.org/2/library/functions.html#property

Some more info in this SO question:
Real world example about how to use property feature in python?

在这个SO问题中的更多信息:关于如何在python中使用属性功能的真实世界示例?

UPDATE

Referring to your updated question... as a question of style I would still make these (last_30 etc) methods on the model rather than adding ad hoc properties onto each model instance in the view code.

参考您更新的问题...作为样式问题,我仍然会在模型上制作这些(last_30等)方法,而不是在视图代码中的每个模型实例上添加ad hoc属性。

From a performance perspective, the difference in memory, processing time etc of method lookup vs dictionaries etc is trivial in most real world situations ...by far the biggest performance consideration in this kind of code is usually the number of database queries.

从性能的角度来看,在大多数现实世界的情况下,方法查找与字典等的内存,处理时间等的差异是微不足道的...到目前为止,这种代码中最大的性能考虑通常是数据库查询的数量。

If you know you're going to do an extra query (or three) for each item in your queryset it's worth looking for ways to get everything in one or more big queries.

如果您知道要为查询集中的每个项目执行额外的查询(或三个),那么值得寻找在一个或多个大查询中获取所有内容的方法。

In some cases you may be able to use the annotate() method:
https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate

在某些情况下,您可以使用annotate()方法:https://docs.djangoproject.com/en/dev/ref/models/querysets/#annotate

(I don't think that's possible in your example)

(我不认为你的例子中有可能)

In your specific code above you only need to query for the 90 days (oldest interval) and you could filter the 60 and 30 days sets from that in Python without querying the db again.

在上面的特定代码中,您只需查询90天(最早的间隔),您可以在Python中过滤60天和30天的集,而无需再次查询数据库。

But that would still be doing one extra query per item in your people queryset. It'd be better to do one big query for the Login objects for all (or whatever subset) of the people. Since there is a foreign key relation for Person on Login we can use select_related() to get the Person instances in one big query when we query Login model:

但是,对于人员查询集中的每个项目,仍然会进行一次额外查询。最好为人员的所有(或任何子集)的Login对象执行一个大查询。由于Person上有一个外键关系,我们可以使用select_related()在查询Login模型时在一个大查询中获取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})

Note if you're really doing Person.objects.all() you wouldn't need the person__in filter above, only if you wanted to filter the Person set in some way.

注意,如果你真的在做Person.objects.all(),那么只要你想以某种方式过滤Person集合,你就不需要上面的person__in过滤器。

Now that we got all the data in one big query we can do what we need to on the python side to display the data. eg in the template we could use the regroup tag:

现在我们在一个大查询中获得了所有数据,我们可以在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 %}

You could take this further and write a custom tag for the login date ranges... I won't detail that here but in your template it could look something like:

你可以更进一步,为登录日期范围写一个自定义标签...我不会在这里详细说明,但在你的模板中它可能看起来像:

{% 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 %}

#2


1  

Don't bother with dictionaries. By looking at those two methods, I'm failing to understand what real problem second one solves. From template's perspective both methods produce same outcome, but first one is far shorter than second one.

不要打扰词典。通过查看这两种方法,我无法理解第二个解决的真正问题。从模板的角度来看,两种方法都产生相同的结果,但第一种方法远远短于第二种方法。

However, there are some issues I see in your code:

但是,我在您的代码中看到了一些问题:

First, if you really care about performace, you should avoid performing needless work. The age-setting step in first method is not really best way to solve problem and it's memory usage will grow as you are adding new persons to database.

首先,如果你真的关心表演,你应该避免进行不必要的工作。第一种方法中的年龄设置步骤并不是解决问题的最佳方法,而且当您向数据库添加新人时,内存使用量会增加。

Did you know that you can use functions/methods that don't accept any arguments (or just "self" argument in case of methods) in templates like they were attributes? If you rename "getAge" to "age", you can simplify first method code down to this:

你是否知道你可以在模板中使用不接受任何参数的函数/方法(或者只是方法中的“self”参数),就像它们是属性一样?如果将“getAge”重命名为“age”,则可以将第一个方法代码简化为:

def method1_object_property(request):
    people = Person.objects.all()
    return render_to_response('template.htm', {'people': people})

Also please take while to familiarize yourself with PEP8 document that regulates conventions for writing python code: http://www.python.org/dev/peps/pep-0008/

另外,请熟悉PEP8文档,该文档规定了编写python代码的约定:http://www.python.org/dev/peps/pep-0008/

As per PEP8 "getAge" is not correct function name, and underscore lowercase should be used, ergo "get_age" is good while "getAge" is "unpythonic". However because this function is basically dynamically counted attribute, you can leave it as "age" and optionally add @property decorator to it, giving it same behaviour it does in django template in python code.

根据PEP8,“getAge”不是正确的函数名,应使用下划线小写,ergo“get_age”是好的,而“getAge”是“unpythonic”。但是因为这个函数基本上是动态计数的属性,你可以将它保留为“age”,并可选择添加@property装饰器,使其具有与python代码中的django模板相同的行为。

Now about optimisation. Defaut behaviour for Django query set upon evaluation is to convert all results returned by database to python objects. So if you have 2 rows in table, Person.objects.all() will produce two Person objects. But if you have 9000 rows, it will produce 9000 python objects that will immediately consume large amounts of memory.

现在关于优化。评估时Django查询集的Defaut行为是将数据库返回的所有结果转换为python对象。因此,如果表中有2行,Person.objects.all()将生成两个Person对象。但是如果你有9000行,它将产生9000个python对象,这些对象将立即消耗大量内存。

You have two ways to defend yourself against this:

你有两种方法可以防御这种情况:

First, you can limit queryset to specified number of items, either by hardcoding it to fetch, say 5 latest members, or by implementing pagination, or finally by makind display of profiles come after user enters search criteria for persons.

首先,您可以将查询集限制为指定的项目数,可以通过将其硬编码为fetch,比如5个最新成员,或者通过实现分页,或者最后通过makind显示配置文件来在用户输入人员的搜索条件之后。

Limiting ("slicing") querysets is covered by Django docs here: https://docs.djangoproject.com/en/1.6/topics/db/queries/#limiting-querysets

这里的Django文档涵盖了限制(“切片”)查询集:https://docs.djangoproject.com/en/1.6/topics/db/queries/#limiting-querysets

Secondly, you can make django use lazy approach to turning database rows into python objects by adding .iterator() at the end of your query. This will make django turn rows into objects as they are returned by queryset which is more memory friendly, but imposes some limitations on your code because instead of list-like object it you will get generator object.

其次,您可以通过在查询结尾添加.iterator(),使django使用惰性方法将数据库行转换为python对象。这将使django将行转换为对象,因为它们由查询集返回,这对内存更友好,但对代码施加了一些限制,因为代替类似列表的对象,它将获得生成器对象。

This change will make Django create Person object for result row once, use it to display row on list, and then throw it away.

此更改将使Django为结果行创建一次Person对象,使用它来显示列表中的行,然后将其丢弃。