Архив рубрики: Без рубрики

Устанавливаем ядро Linux Kernel 3.9.5 (Stable) в Linux Mint

Linux Kernel 3.9.5 является пятым техническим выпуском ядра серии 3.9, с  улучшениями и различными исправлениями ошибок. Следуйте приведенным ниже инструкциям, которые помогут вам установить новое ядро несколькими командами в терминале.

Некоторые изменения и исправления в этом последнем ядре можно посмотреть в изменения:

Установить это ядро версии 3.9.5 командами ниже, можно ​​в следующих дистрибутивах 

    Linux Mint 15/14/13

Установка ядра Linux Kernel 3.9.5

Откройте терминал, скопируйте и выполните эту последовательность команд, на свой страх и риск:


1. Переходим в папку tmp, чтобы при следующем входе в систему после установки ядра, данные пакеты были удалены автоматически:

cd /tmp 


2. Загрузка deb пакетов:


Для 32-bit (одной командой)

wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.9.5-saucy/linux-headers-3.9.5-030905-generic_3.9.5-030905.201306071748_i386.deb http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.9.5-saucy/linux-headers-3.9.5-030905_3.9.5-030905.201306071748_all.deb http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.9.5-saucy/linux-image-3.9.5-030905-generic_3.9.5-030905.201306071748_i386.deb


Для 64-bit (одной командой)

wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.9.5-saucy/linux-headers-3.9.5-030905-generic_3.9.5-030905.201306071748_amd64.deb http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.9.5-saucy/linux-headers-3.9.5-030905_3.9.5-030905.201306071748_all.deb http://kernel.ubuntu.com/~kernel-ppa/mainline/v3.9.5-saucy/linux-image-3.9.5-030905-generic_3.9.5-030905.201306071748_amd64.deb


3. После загрузки, выполните команду на их установку в системе.

sudo dpkg -i *.deb


4. Обновите GRUB (GRUB надо обновлять в той системе Linux где он установлен, если у вас их более одной)

sudo update-grub


5. По окончании установки, закройте все приложения и перезагрузите компьютер следующей командой:

sudo reboot


После входа в систему, проверьте версию актуального ядра командой:

uname -r


Вывод должен быть следующим:

Источник: https://www.kernel.org/

Автор: Роман Дмитриевич

Алгоритм Бойера — Мура

Алгоритм поиска строки Бойера — Мура считается наиболее быстрым среди алгоритмов общего назначения, предназначенных для поиска подстроки в строке. Был разработан Робертом Бойером и Джеем Муром в 1977 году. Преимущество этого алгоритма в том, что ценой некоторого количества предварительных вычислений над шаблоном (но не над строкой, в которой ведётся поиск) шаблон сравнивается с исходным текстом не во всех позициях — часть проверок пропускаются как заведомо не дающие результата.

Мы с Вами уже рассмотрели 2 подхода поиска подстроки: в лоб и Кнута — Морриса — Пратта.
И хотя последний можно отнести к правильным, сегодня мы разберем подход, являющийся классикой в решении данной задачи.
Простота, является характеристикой правильного решения и тривиальность данного подхода это подтверждает.

Основная идея алгоритм — начать поиск не с начала, а с конца подстроки. Наткнувшись на несовпадение, мы просто смещаем подстроку до самого правого вхождения данного символа.

Пример можете посмотреть в картинке заголовка 🙂

Можно заметить, что, как и в случае Кнута-Морриса-Пратта, мы так же можем осуществить предкомпиляцию выражения.

Для этого удобно в словарь заносить пары ключ = числовое значение символа(ord), значение = порядковый номер в подстроке.

А вот и реализация:

def bmPredCompil(x):
d = {}
lenX = len(x)
for i in xrange(len(x)):
# сколько символов с правого края до этой буквы
d[ord(x[i])] = lenX - i
return d

Осталось осуществить сам поиск со сдвигом:

def boyerMurSearch(s, x):
d = bmPredCompil(x)
# k - проход по s
# j - проход по x
# i - место начала прохода по s
lenX = i = j = k = len(x)
while j > 0 and i<=len(s):
# совпали, двигаемся дальше (от конца к началу)
if s[k-1] == x[j-1]:
k -= 1
j -= 1
# иначе, продвигаемся по строке на d и начинаем с правого конца подстроки снова
else:
i += d[ord(s[i])]
j = lenX
k = i
if j <= 0:# нашли
return k
return None # не нашли

Вот собственно и все! 🙂
Реализация, как и сама идея, достаточно прозрачна.

Автор: Pavel Petropavlov

Задача о путешествии шахматного коня

Задача о ходе коня — задача о нахождении маршрута шахматного коня, проходящего через все поля доски по одному разу.
Эта задача известна по крайней мере с XVIII века. Леонард Эйлер посвятил ей большую работу «Решение одного любопытного вопроса, который, кажется, не подчиняется никакому исследованию» (датируется 26 апреля 1757 года).


Когда то давно, мы уже рассматривали пример решения задачи рекурсивным образом, на примере задачи по размену денег. Пришло время вспомнить некоторые классические подходы и начать хотелось бы с задачи о путешествии шахматного коня.

Решение приведенное ниже относится к так называемым алгоритмам с возвратом.
Разделяй и властвуй -основная идея рекурсии. Т.е. мы сложную задачу делим на маленькие простые  решаем их по отдельности в цепочке. В случае данной задачи, можно рассмотреть подзадачу, которая состоит в том, чтобы либо выполнить какой-либо очередной ход, либо обнаружить, что дальнейшие ходы невозможны.

Итак, каждый ход мы будем характеризовать 3-мя числами: его порядковым номером i и парой координат x, y. История ходов будет храниться в матрице списков, в соответствующей ячейке записываем порядковый номер хода. Соответственно, если в ячейке 0, значит туда мы еще не ходили.

Важно определиться с вычислением новой координаты. Можно заметить, что у коня есть 8 позиций-кандидатов (u, v) куда может прыгнуть конь.
Будем получать новые координаты прибавляя соответствующие смещения, хранящиеся в 2х списках dx, dy и отслеживать допустимость хода (находимся в пределах доски).

Характерной чертой данного алгоритма является то, что каждый шаг, выполняемый в попытке приблизиться к полному решению, запоминается таким образом, чтобы от него можно было позднее отказаться, если выяснится, что он не может привести к полному решению. Такое действие называется возвратом. А класс алгоритмов, как не трудно догадаться, алгоритмы с возвратом.

Очень советую читать именно алгоритм, тем более Питон в этом сильно помогает. Это даст куда больше толку. чем множество лишних слов!)

#Задача о коняшке
def knightsTour(x0, y0, done, size=5):
#создаем шахматную доску в виде 2го списка
h = [[0 for j in xrange(size)] for i in xrange(size)]
#начальная координата(1го хода)
h[x0][y0] = 1

#Возможные ходы
dx = [2, 1, -1, -2, -2, -1, 1, 2]
dy = [1, 2, 2, 1, -1, -2, -2, -1]

def CanBeDone(u, v, i):
h[u][v] = i
done = tryNextMove(u, v, i)
if not done:
h[u][v] = 0
return done

def tryNextMove(x,y, i):

#eos - показывает все ли варианты возможных 8ми ходов мы рассмотрели
#done - показывает удачна ли данная ветка решения
#k - порядковый номер рассмотренной попытки из 8 допустимых
env = {'done': False, 'eos': False, 'u': x, 'v': y, 'k': -1}

def next():
x = env['u']
y = env['v']
while env['k'] < 8:
env['k'] +=1;
if env['k'] < 8:
env['u'] = x + dx[env['k']]
env['v'] = y + dy[env['k']]
if (env['u'] >= 0 and env['u']=0 and env['v'] break
env['eos'] = (env['k']==8)

if i < size**2:#если доска не заполнена
next()
while not env['eos'] and not CanBeDone(env['u'], env['v'], i+1):
next()
done = not env['eos']
else:
done = True
return done

tryNextMove(x0, y0, 1)
print h

Привожу пример заполнения шахматной доски.

Автор: Pavel Petropavlov

Поиск первого вхождение подстроки. Решение в лоб.

Поиск информации — одно из основных использований компьютера. Одна из простейших задач поиска информации — поиск точно заданной подстроки в строке. Тем не менее, эта задача чрезвычайно важна — она применяется в текстовых редакторах, СУБД, поисковых машинах…


С поиском подстроки мы встречаемся постоянно. Всем любимый ctrl+f, это именно то, что нам нужно.
Часто ли вы задумывались, а как оно работает? Многие из Вас, наверняка, скажут, что если потребуется — сделаем.
Большинство, думаю, решат эту задачу в лоб и для наглядности я покажу, как это сделать.

Прямой поиск, или, как еще часто говорят, «просто взять и поискать» — это первое решение, которое приходит в голову неискушенному программисту. Суть проста: идти по проверяемой строке A и искать в ней вхождение первого символа искомой строки X. Когда находим, делаем гипотезу, что это и есть то самое искомое вхождение. Затем остается проверять по очереди все последующие символы шаблона на совпадение с соответствующими символами строки A. Если они все совпали — значит вот оно, прямо перед нами. Но вот если какой-то из символов не совпал, то ничего не остается, как признать нашу гипотезу неверной, что возвращает нас к символу, следующему за вхождением первого символа из X.

Многие люди ошибаются в этом пункте, считая, что не надо возвращаться назад, а можно продолжать обработку строки A с текущей позиции. Почему это не так, легко продемонстрировать на примере поиска X=«AAAB» в A=«AAAAB». Первая гипотеза нас приведет к четвертому символу A: «AAAAB», где мы обнаружим несоответствие. Если не откатиться назад, то вхождение мы так и не обнаружим, хотя оно есть.
Неправильные гипотезы неизбежны, а из-за таких откатываний назад, при плохом стечении обстоятельств, может оказаться, что мы каждый символ в A проверили около |X| раз. То есть вычислительная сложность сложность алгоритма O(|X||A|). Так поиск фразы в параграфе может и затянуться…

Справедливости ради следует отметить, что если строки невелики, то такой алгоритм может работать быстрее «правильных» алгоритмов за счет более предсказуемого с точки зрения процессора поведения.

А вот и реализация:

def stringSearch(s, x):
i=j=0
lengthS = len(s)# Длина строки в которой ищем
lengthX = len(x)# -||- которую ищем
# пока не достигли одного из концов
while i<=lengthS - lengthX and j>lengthx:
# если совпали буквы продвигаемся по обеим строкам
if li[i+j]==x[j]:
j+=1
# иначе двигаемся по строке(+1), начиная с 0 символа подстроки
else:
i+=1
j=0
# если дошли до конца подстроки - нашли, иначе - нет
return i if j==lengthX else None

Алгоритм прост и тривиален, а главное, как было замечено выше, зачастую неплохо работает, особенно, когда строка поиска не сильно велика.
Разобравшись с прямым подходм, давайте  посмотрим, как решать эту задачу — правильно.
Алгоритм Кнута-Морриса-Пратта (КМП)
Алгоритм Бойера — Мура

Используемая литература:

  1. Википедия
  2. habrahabr

Автор: Pavel Petropavlov

Алгоритм Кнута-Морриса-Пратта (КМП)

Алгоритм был разработан Кнутом (Knuth) и Праттом (Pratt) и независимо от них Моррисом (Morris) в 1977 г.

Он относится к «правильным» подходам решения поставленной задачи, в отличии от тривиального подхода, рассмотренного ранее.

Данный подход хоть и считается достаточно тривиальным, описания, которые нашел я, зачастую пестрят математическими основами и доказательствами, которые сбивают с сути. Так в книге, уважаемого Никлауса Вирта, приводится описание, которое я так и не одолел.
Однако я нашел пару статей, которые достаточно информативны, они приведены в ссылках и рекомендуемы для ознакомления.

Для понимания: Префикс строки A[..i] — это строка из i первых символов строки AСуффикс строки A[j..] — это строка из |A|-j+1 последних символов. Подстроку из Aбудем обозначать как A[i..j], а A[i] — i-ый символ строки. 

В статье http://habrahabr.ru/post/111449/ сказано:

Идея КМП-поиска – при каждом несовпадении двух символов текста и образа образ сдвигается на все пройденное расстояние, так как меньшие сдвиги не могут привести к полному совпадению.

Многие люди ошибаются в этом пункте, считая, что не надо возвращаться назад, а можно продолжать обработку строки A с текущей позиции. Почему это не так легко продемонстрировать на примере поиска X=«AAAB» в A=«AAAAB». Первая гипотеза нас приведет к четвертому символу A: «AAAAB», где мы обнаружим несоответствие. Если не откатиться назад, то вхождение мы так и не обнаружим, хотя оно есть.

Для реализации данного алгоритма, нам необходимо рассмотреть, так называемую, префикс функцию.

Префикс-функция — это массив чисел, вычисляющийся, как наибольшая длина суффикса, совпадающего с её префиксом. Как пример, берем каждый возможный префикс строки и смотрим самое длинное совпадение начала с концом префикса (не учитывая тривиальное совпадение самого с собой).

Вот пример для «ababcaba»:

суффикспрефиксp
aa0
abab0
abaaba1
abababab2
ababcababc0
ababcaababca1
ababcabababcab2
ababcabaababcaba3

Итак, идея ясна? Ну тогда попробум написать префикс функцию в лоб:

def predkompil(x):
#первый символ всегда 0, поэтому заносим и пропускаем
d = {0:0}
for i in xrange(1,len(x)):
# проходоим от конца к началу
j = i
sdvig = 0
while j>0:
j -= 1
if x[j] == x[i-sdvig]:
sdvig += 1
else:
j += sdvig
sdvig = 0
d[i] = sdvig
return d

Вроде работает, но как то много сравнений и проходов. Попробуем оптимизировать.
Начать можно с замены строки j = i, на j = d[i-1]+1.

Что нам это дает? А то, что незачем просматривать каждый раз с конца и до начала.  Можно заметить, что если мы на предыдущем шаге (i-1) нашли вхождение > 0 (d[i-1]), то и начинать стоит с данной позиции (d[i-1]).

Пример:

При i=4 ABAAB -> 2, тогда на i+1, т.е. i=5  смотрим не с j=i(5), а с 3! Т.к. либо счетчик вырастет и станет 3 в случае ABAABA, либо сбросится в 0 при любой другой букве.
И хотя мы делаем меньше сравнений(пропускаем середину слова), однако весь совпадающий префикс мы проходим, что не есть хорошо.

Это уже не плохо, однако, мы с Вами так и не научились использовать  информацию, полученную на предыдущих и

О сборке мусора, деструкторах и разных питонах

В этом посте я писал почему работа с файлами и другими объектами, требующими гарантированного закрытия должна должна производиться через with. Однако кроме минуса в виде добавления в код лишнего уровеня вложенности with еще и решает только часть проблемы — если код обработки файла не локален (нужно возвращать дескриптор в вызывающий код или хранить неопределенное время) with не может помочь. И собственно никто вообще не может помочь — суровая реальность состоит в том, что python не гарантирует вызов деструктора объекта. Т.е. если вы работаете на CPython, и не создаете циклических ссылок, то за крайне редкими исключениями деструктор будет вызываться вовремя. Но если вы используете ironpython/jython/pypy то ситуация становится совсем печальна.

Для тех кто, вдруг, не в курсе — немного про С++, как пример удобной для программиста реализации деструкторов. С++ гарантирует вызов деструктора у объекта по его уничтожению чтобы не произошло (за исключением полного краха программы/пропадания питания/конца света/etc). Уничтожение наступает либо по выходу из блока в котором определена переменная, либо по удалению объекта с помощью оператора delete при выделении объекта в куче.

    // C++
{
// open file
std::fstream fs(fname, std::ios_base::out);
process_file(fs);
} // file is closed before we get beyong this line, no matter what happened

Гарантированный вызов деструктора — великое программистское благо, позволяющее не заботится о освобождении некоторых ресурсов, не загромождать код всякими with/using/try-finally & Co, и даже для объектов в куче есть всякие unique_ptr. Но все это хорошо работает только в том случае, если объект имел некую локальную область жизни(впрочем unique_ptr/shared_ptr могут и больше). Если же объект выделяется из кучи в одном месте, а освобождается в другом то можно забыть сделать ему delete — получаем утечку памяти. Не смотря на множество способов значительно сократить вероятность такого исхода (например арены) полностью исключить его нельзя.

В качестве решения этой проблемы в современных языках используются сборщики мусора. Периодически они проходятся по памяти, и тем или иным способом удаляют неиспользуемые объекты. Все бы хорошо, но у сборщиков мусора возникают небольшие или большие проблемы с вызовами деструкторов у объектов. Во-первых все сборщики мусора бессильны перед циклическими ссылками. Если объект a ссылается на b, а b ссылается на a, то не понятно в каком порядке вызывать деструкторы. Сначала у a или сначала у b. Если сначала у a, то при вызове деструктора b его ссылка на a будет не валидна и наоборот. Отсутствие информации о смысле взаимосвязей между объектами не дает сборщику мусора вызвать деструкторы в корректном порядке. Копирующие сборщики мусора пошли еще дальше. Они вообще ничего не вызывают, оставляя программиста один на один с этой проблемой и гордо подписываясь «современный сборщик мусора».

Проблема, очевидно, состоит в том что оперативная память это не единственный ресурс требующий освобождения. Еще, как минимум, есть объекты OS, мютексы, транзакции БД, и много много другого. Часть из таких объектов будет через время прикрыта другим кодом (например транзакции БД — но в любом случае они будут создавать лишнюю нагрузку на базу все это время), но объекты OS останутся с процессом до его смерти. А ведь бОльшая часть таких объектов имею локальную область видимости и при гарантированном вызове деструктор был бы идеальным местом для их освобождения. Таким образом переходя от С++ к языкам со сборкой мусора но с недетерменированным вызовом деструкторов мы выигрываем в коде освобождения памяти, но проигрываем на других объектах. Теперь вместо delete «где-то там» вы должны написать dispose/using/with/try-finally прямо тут на каждый объект. Впрочем если файл, например, является не локальным, то и это не поможет. Для примера можно открыть Экслера и глянуть как в яве правильно работать с файлами. Страница ужасного кода ради одного маленького файлика. Не уверен, что такая сборка мусора того стоила.

Итак посмотрим как себя ведут с деструкторами разные варианты питона. В качестве примера возьмем вот такую программу:

Без подсветки синтаксиса

import gc
import sys

class DestTest(object):
def __init__(self, val):
self.val = val

def __del__(self):
sys.stdout.write(str(self) + '.__del__()n')

def __str__(self):
return "DestTest({})".format(self.val)

def __repr__(self):
return "DestTest({})".format(self.val)

def simple_var():
d = DestTest("simple var")

def mdeleted_var():
d = DestTest("manyally deleted var")
del d

def simple_list():
a = [DestTest("simple list")]

def internal_exc():
try:
d = DestTest("exception_func")
raise IndexError()
except:
pass

def exception_func():
d = DestTest("exception_func")
raise IndexError()

def self_ref_list():
a = [DestTest("self-ref list")]
a.append(a)

def cycle_refs():
d1 = DestTest("cycle_ref_obj1")
d2 = DestTest("cycle_ref_obj2")
d3 = DestTest("free_obj")

d1.ref = d2
d2.ref = d1
d2.ref2 = d3

simple_var()
sys.stdout.write("after simple varn")
sys.stdout.write("n")

simple_list()
sys.stdout.write("after simple listn")
sys.stdout.write("n")

mdeleted_var()
sys.stdout.write("after manually deleted varn")
sys.stdout.write("n")

self_ref_list()
sys.stdout.write("after self ref listn")
gc.collect()
sys.stdout.write("after gc.collect()n")
sys.stdout.write("n")

internal_exc()
sys.stdout.write("after internal excn")

try:
exception_func()
except Exception as x:
pass
sys.stdout.write("after exception funcn")
gc.collect()
sys.stdout.write("after gc.collect()n")
try:
sys.exc_clear()
except AttributeError:
pass
else:
sys.stdout.write("after sys.exc_clear()n")
sys.stdout.write("n")

cycle_refs()
sys.stdout.write("after cycle refsn")
gc.collect()
sys.stdout.write("after gc.collect()n")
sys.stdout.write("n")

d = DestTest("module var")

import gc
import sys

class DestTest(object):
def __init__(self, val):
self.val = val

def __del__(self):
sys.stdout.write(str(self) + '.__del__()n')

def __str__(self):
return "DestTest({})".format(self.val)

def __repr__(self):
return "DestTest({})".format(self.val)

def simple_var():
d = DestTest("simple var")

def mdeleted_var():
d = DestTest("manyally deleted var")
del d

def simple_list():
a = [DestTest("simple list")]

def internal_exc():
try:
d = DestTest("exception_func")
raise IndexError()
except:
pass

def exception_func():
d = DestTest("exception_func")
raise IndexError()

def self_ref_list():
a = [DestTest("self-ref list")]
a.append(a)

def cycle_refs():
d1 = DestTest("cycle_ref_obj1")
d2 = DestTest("cycle_ref_obj2")
d3 = DestTest("free_obj")

d1.ref = d2
d2.ref = d1
d2.ref2 = d3

simple_var()
sys.stdout.write("after simple varn")
sys.stdout.write("n")

simple_list()
sys.stdout.write("after simple listn")
sys.stdout.write("n")

mdeleted_var()
sys.stdout.write("after manually deleted varn")
sys.stdout.write("n")

self_ref_list()
sys.stdout.write("after self ref listn")
gc.collect()
sys.stdout.write("after gc.collect()n")
sys.stdout.write("n")

internal_exc()
sys.stdout.write("after internal excn")

try:
exception_func()
except Exception as x:
pass
sys.stdout.write("after exception funcn")
gc.collect()
sys.stdout.write("after gc.collect()n")
try:
sys.exc_clear()
except AttributeError:
pass
else:
sys.stdout.write("after sys.exc_clear()n")
sys.stdout.write("n")

cycle_refs()
sys.stdout.write("after cycle refsn")
gc.collect()
sys.stdout.write("after gc.collect()n")
sys.stdout.write("n")

d = DestTest("module var")

В идеальном мире вызов деструктора у объектов класса DestTest во всех этих функциях должен происходить до выхода из соответствующей функции. Итак что получается:

python2.7

    DestTest(simple var).__del__()
after simple var

DestTest(simple list).__del__()
after simple list

DestTest(manyally deleted var).__del__()
after manually deleted var

after self ref list
DestTest(self-ref list).__del__()
after gc.collect()

after exception func
after gc.collect()
DestTest(exception_func).__del__()
after sys.exc_clear()

after cycle refs
after gc.collect()

Exception AttributeError: "'NoneType' object has no attribute 'stdout'" in
ignored

Более-менее. Деструктор у циклических ссылок не был вызван, как и ожидалось. Для уничтожения объектов, связанных с исключением, дошедшем до уровня модуля приходится вызывать sys.clear_exc(), в остальных случаях с исключениями все ок. Интересно, что питон освободил sys.stdout раньше, чем переменную d, в итоге чего ее деструктор отработал не корректно (впрочем это поведение не всегда повторяется).

python3.3

    DestTest(simple var).__del__()
after simple var

DestTest(simple list).__del__()
after simple list

DestTest(manyally deleted var).__del__()
after manually deleted var

after self ref list
DestTest(self-ref list).__del__()
after gc.collect()

DestTest(exception_func).__del__()
after exception func
after gc.collect()

after cycle refs
after gc.collect()

Exception AttributeError: "'NoneType' object has no attribute 'stdout'" in
ignore

Почти то-же самое, что и 2.7, только sys.exc_clear() больше не нужно.

ironpython2.7

    after simple var

after simple list

after manually deleted var

after self ref list
DestTest(self-ref list).__del__()
DestTest(manyally deleted var).__del__()
DestTest(simple list).__del__()
DestTest(simple var).__del__()
after gc.collect()

after exception func
DestTest(exception_func).__del__()
after gc.collect()
after sys.exc_clear()

after cycle refs
DestTest(free_obj).__del__()
DestTest(cycle_ref_obj2).__del__()
DestTest(cycle_ref_obj1).__del__()
after gc.collect()

DestTest(module var).__del__()

Удаляет все, но только при сборке мусора, до те
х пор все объекты живут где-то в памяти.

И — встречаем победителя. Сама лаконичность или мир всем вашим деструкторам от нашей Java и копирующего сборщика мусора: jython2.7a2 — Java HotSpot(TM) Client VM (Oracle Corporation) on java1.7.0_07

    after simple var

after simple list

after manually deleted var

after self ref list
after gc.collect()

after exception func
after gc.collect()
after sys.exc_clear()

after cycle refs
after gc.collect()

Правда в ява есть и другие варианты сборщика мусора, может там чуть по-лучше.

А вывод все тот-же. По возможности — используйте with. По невозможности — попробуйте поправить код, так что-бы можно было использовать with. Иначе — аккуратно вызывайте деструкторы руками

P.S. Как я люблю, когда мне предлагают делать какую-то рутинную работу в коде «аккуратно».

Ссылки:
          koder-ua.blogspot.com/2012/10/python-with.html
          en.wikipedia.org/wiki/Region-based_memory_management

Исходники этого и других постов со скриптами лежат тут — github.com/koder-ua. При использовании их, пожалуйста, ссылайтесь на koder-ua.blogspot.com.

Автор: konstantin danilov