Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

`SyntaxError: no binding for nonlocal 'topics_with_log_tag' found` although it's bounded

Tags:

django

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)
like image 637
AbstProcDo Avatar asked May 18 '18 13:05

AbstProcDo


People also ask

What does no binding for nonlocal mean?

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).

What is nonlocal variable in Python?

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.


1 Answers

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). 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.

Efficient database querying in Django ORM

Nevertheless the way you perform the filtering here, will usually be quite inefficient: you here first iterate over all Topics, then for each topic, you do an extra query fetching all Entrys, then for each Entry you fetch all Tags, 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.

like image 112
Willem Van Onsem Avatar answered Sep 28 '22 14:09

Willem Van Onsem