Вопрос по scrapy, oop, python, posix – Жизненные характеристики объекта Python

3

Примечание. Если вы знаете какой-либо (не разработанный) библиотечный код, который делает то, что я хочу, просвещайте программиста на C / C ++, я приму это как ответ.

У меня есть глобальная переменная для экземпляра следующего класса. Его цель состоит в том, чтобы позволить мне установить некоторые точки ручного прерывания для быстрого и грязногоprintf точки отладки стиля в пауке-скрапе (мне особенно нужно нарушать, когда соблюдаются определенные критерии для настройки синтаксического анализатора, есть некоторые крайне редкие аномалии входных данных) - адаптировано изэтот.

ОС OS X 10.8.

import termios, fcntl, sys, os

class DebugWaitKeypress(object):
    def __init__(self):
        self.fd = sys.stdin.fileno()
        self.oldterm = termios.tcgetattr(self.fd)
        self.newattr = termios.tcgetattr(self.fd)
        self.newattr[3] = self.newattr[3] & ~termios.ICANON & ~termios.ECHO
        termios.tcsetattr(self.fd, termios.TCSANOW, self.newattr)

        self.oldflags = fcntl.fcntl(self.fd, fcntl.F_GETFL)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK)

    def wait(self):
        sys.stdin.read(1)

    def __del__(self):
        print "called del"
        termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.oldterm)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags)

Когда я нажимаю Ctrl-C и процесс раскручивается, я получаю следующее исключение:

Exception AttributeError: "'NoneType' object has no attribute 'tcsetattr'" in <bound method DebugWaitKeypress.__del__ of <hon.spiders.custom_debug.DebugWaitKeypress object at 0x108985e50>> ignored

Думаю, я что-то упускаю из-за механики времени жизни объекта? Как исправить ситуацию. AFAIK любые экземпляры классов должны быть уничтожены раньше, чем импортированный код, не так ли? в обратном порядке объявления / определения.

Я просто проигнорировал бы это, если бы терминал не был испорчен после завершения процесса: D

edit:

Комментарий Делиана по поводу ответа Сета привел меня к пониманию, что мне нужно использовать Cmain() like function или любая другая функция / генератор, которая доминирует как корневая функция и инициализирует там контекст. Таким образом, когда процесс идет вниз__exit__ метод менеджера контекста будет вызван. И я не буду перепрограммировать поток терминала на каждомwait() вызов.

Хотя стоимость перепрограммирования потенциально нематериальна, полезно знать, как можно использовать эти важные семантики C / C ++ в python.

edit 2:

Twisted (который использует скрап) - апешит, когда возится со stdin. Поэтому мне пришлось решить проблему с файлом IO.

Ваш Ответ

2   ответа
4

Если__del__ вызывается, это происходит через некоторое время после того, как счетчик ссылок объекта становится равным нулю, и, возможно, не до тех пор, пока не завершится программа, и не в каком-либо конкретном порядке. Вы также не можете зависеть от чего-либо внешнего (особенно глобального), доступного в__del__.

В вашем случае python убрал ссылку наtermios модуль, прежде чем он называетсяDebugWaitKeyPress.__del__, Вот почему вы получаете'NoneType' object has no attribute 'tcsetattr' сообщение.termios являетсяNone к тому времени, когда вы пытаетесь его использовать.

Я полагаю, вам было бы лучше реализоватьконтекстный менеджери положи__del__ код в__exit__.

Тогда вы сможете сказать что-то вроде:

with DebugWaitKeypress(...) as thing:
    do_something_with_it(thing)
# here, __exit__() is called to do cleanup

Отobject.__del__ docs:

Due to the precarious circumstances under which __del__() methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed to sys.stderr instead. Also, when __del__() is invoked in response to a module being deleted (e.g., when execution of the program is done), other globals referenced by the __del__() method may already have been deleted or in the process of being torn down (e.g. the import machinery shutting down). For this reason, __del__() methods should do the absolute minimum needed to maintain external invariants. Starting with version 1.5, Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted; if no other references to such globals exist, this may help in assuring that imported modules are still available at the time when the __del__() method is called.

Хм, это понимание, которое мне было нужно, я думаю. Я мог бы инициализировать глобальное с классом во время scrapy 'sparse Метод, реализованный в виде генератора, я запускаю контекст в верхней части генератора, а затем, когда у меня заканчиваются URL-адреса для возврата, генератор выходит из области видимости, убираю класс с него до того, как scrapy начинает закрываться. Hassan Syed
хм, возможны ли другие конструкции с менеджерами контекста? Документация предлагает использовать конструкцию для блоков операторов. Это означает, что каждый раз, когда я хочу достичь точки останова, мне приходится дважды переконфигурировать терминал. Hassan Syed
@HassanSyed Вы, кажется, недооцениваете это. Какanything (даже friggin 'import и определения классов, хотя вам будет сложно найти хороший сценарий использования), вы можете войти в контекстный менеджер, вы можете, например, обернуть вызов к вашемуmain функционирует в одном, и он настраивает терминал до того, как что-либо произойдет, и сбрасывает конфигурацию только тогда, когдаmain осталось (обычноor из-за исключения). Так же, как вы могли бы создатьDebugWaitKeypress в стекеmain в C ++.
5

Короче:__del__ бесполезен для этой цели (и в значительной степени для любой другой цели; вероятно, вы должны забыть о ее существовании). Если вы хотите детерминированной очистки, используйте менеджер контекста.

AFAIK any class instances should be destroyed before the imported code does, no ? in reverse order of declaration/definition.

Это С ++. Забудь это. Python не заботится об этом, фактически он даже не заботится о большинстве вещей, которые являются требованиями для этого. Не существует такого понятия, как объявление во всем языке Python, и переменные уровня модуля хранятся в неупорядоченном ассоциативном массиве. Переменные не хранят объекты, они хранят ссылки (которыеnot C ++ ссылки, они в основном являются указателями без арифметики указателей) - объекты находятся в куче и ничего не знают о переменных, связываниях, операторах или порядке операторов.

Более того, когда объекты собирают мусор, иwhether it is gc'd at allне определено. Вы получаетеmostly детерминированная картина в CPython из-за подсчета ссылок, но даже там она падает, как только у вас появляются циклы. Следствием является то, что__del__ может вызываться в любой момент времени (в том числе, когда половина модуля уже снесена) или не вызываться Определение нескольких объектов__del__ ссылаться друг на друга - тоже проблема, хотя некоторые GC стараются делать правильные вещи.

Суть в том, что вы можете предположить очень мало в то время__del__ работает, так что вы не можете сделать очень много. Вы получаете последний шанс избавиться от ресурсов, которые должны были быть очищены другим способом, но это не так, и это в значительной степени так. Практическое правило:Never полагаться на это дляanything обязательный.

Вместо этого создайтеменеджер контекста и использовать его черезwith, Вы получаете детерминированную очистку, не беспокоясь о времени жизни объекта. Потому что, по правде говоря, время жизни объекта и время жизни ресурса - это две совершенно разные вещи, и они запутаны только в C ++, потому что это лучший способ управления ресурсами.in that environment, В Python RAII не применяется, вместо этого у нас есть это:

with <context manager> as var:
    # do something
# "context closed", whatever that means - for resources, usually cleanup

Кстати, вы можете определить это гораздо удобнее с помощьюcontextlib (быстро транслитерируется с вашей версии, может содержать ошибки или уродство):

from contextlib import contextmanager


@contextmanager
def debug_wait_keypress():
    fd = sys.stdin.fileno()
    oldterm = termios.tcgetattr(fd)
    newattr = termios.tcgetattr(fd)
    newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
    termios.tcsetattr(fd, termios.TCSANOW, newattr)
    oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
    try:
        yield
    finally:
        termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
        fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

Вашwait метод становится свободной функцией.

Похожие вопросы