如何正确清理Python对象?

时间:2009-05-14 19:04:12

标签: python destructor

class Package:
    def __init__(self):
        self.files = []

    # ...

    def __del__(self):
        for file in self.files:
            os.unlink(file)
上面的

__del__(self)因AttributeError异常而失败。当__del__()被调用时,我理解Python doesn't guarantee是否存在“全局变量”(此上下文中的成员数据?)。如果是这种情况,这就是异常的原因,我该如何确保对象正确破坏?

10 个答案:

答案 0 :(得分:545)

我建议使用Python的with语句来管理需要清理的资源。使用显式close()语句的问题是,您必须担心人们忘记完全调用它或忘记将其放在finally块中以防止在发生异常时资源泄漏。 / p>

要使用with语句,请使用以下方法创建一个类:

  def __enter__(self)
  def __exit__(self, exc_type, exc_value, traceback)

在上面的示例中,您将使用

class Package:
    def __init__(self):
        self.files = []

    def __enter__(self):
        return self

    # ...

    def __exit__(self, exc_type, exc_value, traceback):
        for file in self.files:
            os.unlink(file)

然后,当有人想要使用你的课程时,他们会做以下事情:

with Package() as package_obj:
    # use package_obj

变量package_obj将是Package类型的实例(它是__enter__方法返回的值)。无论是否发生异常,都会自动调用__exit__方法。

您甚至可以将此方法更进一步。在上面的示例中,有人仍然可以使用其构造函数实例化Package而不使用with子句。你不希望这种情况发生。您可以通过创建定义__enter____exit__方法的PackageResource类来解决此问题。然后,将严格在__enter__方法内定义Package类并返回。这样,调用者永远不会在不使用with语句的情况下实例化Package类:

class PackageResource:
    def __enter__(self):
        class Package:
            ...
        self.package_obj = Package()
        return self.package_obj

    def __exit__(self, exc_type, exc_value, traceback):
        self.package_obj.cleanup()

您可以按如下方式使用:

with PackageResource() as package_obj:
    # use package_obj

答案 1 :(得分:31)

标准方法是使用atexit.register

#include <stdio.h>
#include <ncurses.h>
#include <string.h>
#include <menu.h>
#include <stdlib.h>           // added for exit() function

void fail(char *msg) {
    endwin();
    puts(msg);
    exit(EXIT_FAILURE);
}

int main(int argc, char **argv)
{
    /* Commandline argument currently unused */
    (void) argc;
    (void) argv;

    int i, c;
    (void) c;                       // c is currently unused
    char powitanie[]="SLOWNIK UNIWERSALNY";
    int szer, dlug; //wartosci dlugosci i szerokosci terminalu

    initscr(); //Inizjalizacja całości ncurses, kolory itp
    raw();
    noecho();
    keypad(stdscr, TRUE);

    /* Test to see if terminal has colors */
    if (has_colors() == false) {
        fail("Colors unavailable\n");
    }

    if (start_color() != OK) {
        fail("Unable to start colors\n");
    }

    //init_pair(1, COLOR_BLUE, COLOR_BLACK); //wybór kolorów

    getmaxyx(stdscr, szer, dlug); //pobranie rozmiarów terminalu
    move(szer/2, (dlug-strlen(powitanie))/2); //przesuwamy kursor na środek (tak aby się ładnie wydrukowało)
    //attron(COLOR_PAIR(1)); //Aktywujemy wybrane kolory
    printw(powitanie); //Drukujemy powitanie
    //attroff(COLOR_PAIR(1));//Dezaktywujemy kolory
    refresh();//Odswiezamy (inaczej się nie wyswietli)
    WINDOW * menuwin=newwin(7, dlug-12, szer-9, 6); //Definiujemy i tworzymy 'okno'
    box(menuwin, 0, 0);
    refresh();//ponownie odświeżamy aby okno się pojawiło
    wrefresh(menuwin);//odświeżamy samo okno
    keypad(menuwin, TRUE);//umozliwiamy dzialanie klawiatury w oknie

    char *opcje[] = {
        "Tlumacz z Polskiego na Angielski",
        "Tlumacz z Angielskiego na Polski",
        "Edystuj slownik",
        "Wybierz slownik",
        "Wyjdz",
    };
    int wybor;
    int zaznacz=0;

    while(1)//cala ta petla sluzy ciaglemu tworzeniu menu z podswietleniem wybranego elementu
    {
        for(i = 0; i < 5; i++) {
            if(i == zaznacz)
                wattron(menuwin, A_REVERSE);
            mvwprintw(menuwin, i+1, 1, opcje[i]);
            if (i == zaznacz)
                wattroff(menuwin, A_REVERSE);
        }

        wybor = wgetch(menuwin);
        switch(wybor)
        {
        case KEY_UP:
            zaznacz--;
            if(zaznacz < 0) zaznacz = 0;//zabezpieczenie przed wyjsciem "poza" menu
            break;
        case KEY_DOWN:
            zaznacz++;
            if(zaznacz > 4) zaznacz = 4;
            break;
        default:
            break;
        }

        if(wybor==10) break;
    }

    printw("\nWybrano:%s", opcje[zaznacz]);
    refresh();

    /* Wait for user to press enter to exit */
    getch();

    /* Need to cleanup before exit */
    endwin();

    return 0;
}

但是你应该记住,这将保留所有创建的# package.py import atexit import os class Package: def __init__(self): self.files = [] atexit.register(self.cleanup) def cleanup(self): print("Running cleanup...") for file in self.files: print("Unlinking file: {}".format(file)) # os.unlink(file) 实例,直到Python终止。

使用上面的代码进行演示,保存为 package.py

Package

答案 2 :(得分:26)

作为Clint's answer的附录,您可以使用contextlib.contextmanager简化PackageResource

@contextlib.contextmanager
def packageResource():
    class Package:
        ...
    package = Package()
    yield package
    package.cleanup()

或者,尽管可能不是Pythonic,但您可以覆盖Package.__new__

class Package(object):
    def __new__(cls, *args, **kwargs):
        @contextlib.contextmanager
        def packageResource():
            # adapt arguments if superclass takes some!
            package = super(Package, cls).__new__(cls)
            package.__init__(*args, **kwargs)
            yield package
            package.cleanup()

    def __init__(self, *args, **kwargs):
        ...

并简单地使用with Package(...) as package

为了简化操作,请将清理函数命名为close并使用contextlib.closing,在这种情况下,您可以通过Package使用未修改的with contextlib.closing(Package(...))类或覆盖其{ {1}}到更简单的

__new__

这个构造函数是继承的,所以你可以简单地继承,例如

class Package(object):
    def __new__(cls, *args, **kwargs):
        package = super(Package, cls).__new__(cls)
        package.__init__(*args, **kwargs)
        return contextlib.closing(package)

答案 3 :(得分:16)

我不认为在调用__del__之前删除实例成员是可能的。我的猜测是你的特定AttributeError的原因是在其他地方(也许你错误地删除了其他地方的self.file)。

但是,正如其他人指出的那样,你应该避免使用__del__。这样做的主要原因是__del__的实例不会被垃圾回收(只有当它们的引用次数达到0时才会被释放)。因此,如果您的实例涉及循环引用,则只要应用程序运行,它们就会存在内存中。 (虽然我可能会误解所有这些,但我必须再次阅读gc文档,但我确信它的工作方式是这样的。)

答案 4 :(得分:11)

如果代码多于显示的话,我认为问题可能在__init__

即使__del__未正确执行或引发异常,也会调用

__init__

Source

答案 5 :(得分:8)

使用try / except语句包装析构函数,如果已经丢弃了全局变量,则不会抛出异常。

修改

试试这个:

from weakref import proxy

class MyList(list): pass

class Package:
    def __init__(self):
        self.__del__.im_func.files = MyList([1,2,3,4])
        self.files = proxy(self.__del__.im_func.files)

    def __del__(self):
        print self.__del__.im_func.files

它会将文件列表填入 del 函数中,该函数在调用时保证存在。 weakref代理是为了防止Python,或者你自己以某种方式删除self.files变量(如果它被删除,那么它不会影响原始文件列表)。如果不是这种情况被删除,即使有更多的变量引用,那么你可以删除代理封装。

答案 6 :(得分:7)

更好的选择是使用weakref.finalize。请参阅Finalizer ObjectsComparing finalizers with __del__() methods上的示例。

答案 7 :(得分:5)

似乎这样做的惯用方法是提供close()方法(或类似方法),并明确地称之为。

答案 8 :(得分:5)

这是一个最小的工作骨架:

class SkeletonFixture:

    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def method(self):
        pass


with SkeletonFixture() as fixture:
    fixture.method()

重要提示:返回自我

如果你像我一样,忽略return self部分(Clint Miller's correct answer),你会盯着这个废话:

Traceback (most recent call last):
  File "tests/simplestpossible.py", line 17, in <module>                                                                                                                                                          
    fixture.method()                                                                                                                                                                                              
AttributeError: 'NoneType' object has no attribute 'method'

我花了半天时间。希望它能帮助下一个人。

答案 9 :(得分:0)

atexit.registerostrakach's answer中已经提到的标准方法。

但是,必须注意,不能依赖删除对象的顺序,如下例所示。

import atexit

class A(object):

    def __init__(self, val):
        self.val = val
        atexit.register(self.hello)

    def hello(self):
        print(self.val)


def hello2():
    a = A(10)

hello2()    
a = A(20)

在这里,顺序与创建对象的顺序相反,因为程序将输出显示为:

20
10

但是,在较大的程序中,当python的垃圾回收踢入对象时,该对象将超出其生命周期。