Этот метод выполняет итерацию по списку терминов в базе данных, проверяет, содержатся ли термины в тексте, передаваемом в качестве аргумента, и, если он есть, заменяет его ссылкой на страницу поиска с термином в качестве параметра.
Количество терминов велико (около 100000), поэтому процесс довольно медленный, но это нормально, так как он выполняется как задание cron.Однако это приводит к стремительному увеличению потребления памяти скриптами, и я не могу найти, почему:
class SearchedTerm(models.Model):
[...]
@classmethod
def add_search_links_to_text(cls, string, count=3, queryset=None):
"""
Take a list of all researched terms and search them in the
text. If they exist, turn them into links to the search
page.
This process is limited to `count` replacements maximum.
WARNING: because the sites got different URLS schemas, we don't
provides direct links, but we inject the {% url %} tag
so it must be rendered before display. You can use the `eval`
tag from `libs` for this. Since they got different namespace as
well, we enter a generic 'namespace' and delegate to the
template to change it with the proper one as well.
If you have a batch process to do, you can pass a query set
that will be used instead of getting all searched term at
each calls.
"""
found = 0
terms = queryset or cls.on_site.all()
# to avoid duplicate searched terms to be replaced twice
# keep a list of already linkified content
# added words we are going to insert with the link so they won't match
# in case of multi passes
processed = set((u'video', u'streaming', u'title',
u'search', u'namespace', u'href', u'title',
u'url'))
for term in terms:
text = term.text.lower()
# no small word and make
# quick check to avoid all the rest of the matching
if len(text) < 3 or text not in string:
continue
if found and cls._is_processed(text, processed):
continue
# match the search word with accent, for any case
# ensure this is not part of a word by including
# two 'non-letter' character on both ends of the word
pattern = re.compile(ur'([^\w]|^)(%s)([^\w]|$)' % text,
re.UNICODE|re.IGNORECASE)
if re.search(pattern, string):
found += 1
# create the link string
# replace the word in the description
# use back references (\1, \2, etc) to preserve the original
# formatin
# use raw unicode strings (ur"string" notation) to avoid
# problems with accents and escaping
query = '-'.join(term.text.split())
url = ur'{%% url namespace:static-search "%s" %%}' % query
replace_with = ur'\1<a title="\2 video streaming" href="%s">\2</a>\3' % url
string = re.sub(pattern, replace_with, string)
processed.add(text)
if found >= 3:
break
return string
Вам, вероятно, понадобится и этот код:
class SearchedTerm(models.Model):
[...]
@classmethod
def _is_processed(cls, text, processed):
"""
Check if the text if part of the already processed string
we don't use `in` the set, but `in ` each strings of the set
to avoid subtring matching that will destroy the tags.
This is mainly an utility function so you probably won't use
it directly.
"""
if text in processed:
return True
return any(((text in string) for string in processed))
У меня действительно есть толькодва объекта со ссылками, которые могут быть подозреваемыми здесь: terms
и processed
.Но я не вижу причин, по которым они не будут собирать мусор.
РЕДАКТИРОВАТЬ:
Думаю, я должен сказать, что этот метод вызывается внутри самого метода модели Django.Я не знаю, уместно ли это, но вот код:
class Video(models.Model):
[...]
def update_html_description(self, links=3, queryset=None):
"""
Take a list of all researched terms and search them in the
description. If they exist, turn them into links to the search
engine. Put the reset into `html_description`.
This use `add_search_link_to_text` and has therefor, the same
limitations.
It DOESN'T call save().
"""
queryset = queryset or SearchedTerm.objects.filter(sites__in=self.sites.all())
text = self.description or self.title
self.html_description = SearchedTerm.add_search_links_to_text(text,
links,
queryset)
Я могу представить, что автоматическое кэширование регулярных выражений Python пожирает некоторую память.Но он должен делать это только один раз, и потребление памяти увеличивается при каждом вызове update_html_description
.
Проблема не только в том, что он потребляет много памяти, но и в том, что он не освобождает ее:каждые вызовы занимают около 3% оперативной памяти, в конечном итоге заполняя его и сбивая скрипт с «невозможно выделить память».