I'd like to fetch topics with a specified 'log' tag:
Within the nested function get_topics_with_log_tag
, I set variable topics_with_log_tag
nonlocal:
def log(request):
"""Show all topics and entries with log tags"""
topics = Topic.objects.all()
#select entries with log tag
def get_topics_with_log_tag(topics):
nonlocal topics_with_log_tag
topics_with_log_tag = []
for topic in topics:
for entry in topic.entry_set.all():
if "#log" in entry.tags:
topics_with_log_tag.append(topic)
get_topics_with_log_tag(topics)
It throw SyntaxError:
SyntaxError: no binding for nonlocal 'topics_with_log_tag' found
Actually, I did bind it topics_with_log_tag = []
The above code could be rewrite in a redundant way as
topics = Topic.objects.all()
#select entries with log tag
def get_topics_with_log_tag(topics):
# nonlocal topics_with_log_tag
topics_with_log_tag = []
for topic in topics:
for entry in topic.entry_set.all():
if "#log" in entry.tags:
topics_with_log_tag.append(topic)
return topics_with_log_tag
topics_with_log_tag = get_topics_with_log_tag(topics)
What's the problem with my usage of nonlocal
?
I found the error.
Willem Van Onsem introduces database level filter instead of the nested for loop.
The models.py
class Topic(models.Model):
"""A topic the user is learning about."""
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User)
def __str__(self):
"""Return a string representation of the model."""
return self.text
class Entry(models.Model):
"""Something specific learned about a topic"""
topic = models.ForeignKey(Topic)
title = models.CharField(max_length=200)
text = models.TextField()
tags = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
How nonlocal works. If you use nonlocal , that means that Python will, at the start of the function, look for a variable with the same name from one scope above (and beyond).
The nonlocal keyword is used to work with variables inside nested functions, where the variable should not belong to the inner function. Use the keyword nonlocal to declare that the variable is not local.
nonlocal
worksIf you use nonlocal
, that means that Python will, at the start of the function, look for a variable with the same name from one scope above (and beyond). But here you did not define such one. We can fix it with defining one, one level higher:
def log(request):
"""Show all topics and entries with log tags"""
topics = Topic.objects.all()
#select entries with log tag
topics_with_log_tag = []
def get_topics_with_log_tag(topics):
nonlocal topics_with_log_tag
topics_with_log_tag = []
for topic in topics:
for entry in topic.entry_set.all():
if "#log" in entry.tags:
topics_with_log_tag.append(topic)
get_topics_with_log_tag(topics)
You can use global
in which case you do not need to declare such variable (in that case it is declared at the upper level), but this is actually an anti-pattern as well.
Nevertheless the way you perform the filtering here, will usually be quite inefficient: you here first iterate over all Topic
s, then for each topic, you do an extra query fetching all Entry
s, then for each Entry
you fetch all Tag
s, and then you look whether one of the tags is #log
. Now imagine that you have 10 topics, that have 10 entries per topic, and 5 tags per entry. That results in 500+ queries you do at the database level. We can construct a filter like:
topics_with_log_tag = Topics.objects.filter(entry__tags__contains='#log').distinct()
or more readable (brackets are used to allow multi-line expressions):
topics_with_log_tag = (Topics.objects
.filter(entry__tags__contains='#log')
.distinct())
Note that the above will (just like your code did), also contains topics with as tags
for example '#logarithm'
. It only checks if it contains a certain substring. In order to prevent that, you will need more advanced filtering, or better tag representation (with an end marker).
For example if every topic ends with a comma (like '#foo,#bar,'
) then we could query for '#log,'
.
We can also work with regular expressions and check for a new hash character, or the end of the string.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With