【问题标题】:Segmentation fault in destructor with Python使用 Python 的析构函数中的分段错误
【发布时间】:2021-08-15 07:10:33
【问题描述】:

我已经创建了一个类来表示我的 LED 灯带,并且我想在停止灯带时关闭灯带(也就是当程序停止并且对象被破坏时)。因此,就像我在 C++ 中所做的那样,我创建了一个析构函数来做到这一点。但看起来 Python 在 销毁该对象之后调用它。然后我得到一个分段错误错误。
这是我的类,析构函数只需要调用函数将每个 LED 的颜色设置为 0。

class LedStrip:
    def __init__(self, led_count, led_pin, led_freq_hz, led_dma, led_invert, led_brightness, led_channel, color = MyColor(0,0,0)):
        self.__strip = Adafruit_NeoPixel(led_count, led_pin, led_freq_hz, led_dma, led_invert, led_brightness, led_channel)
        self.__color = color
        self.__strip.begin()

    def __del__(self):
        self.__color = MyColor(0,0,0)
        self.colorWipe(10)

# ATTRIBUTS (getter/setter)
    @property
    def color(self):
        return self.__color

    @color.setter
    def color(self, color):
        if isinstance(color, MyColor):
            self.__color = color
        else:
            self.__color = MyColor(0,0,0)

    def __len__(self):
        return self.__strip.numPixels()

# METHODS
    def colorWipe(self, wait_ms=50):
        """Wipe color across display a pixel at a time."""
        color = self.__color.toNum()
        for i in range(self.__strip.numPixels()):
            self.__strip.setPixelColor(i, color)
            self.__strip.show()
            time.sleep(wait_ms/1000.0)

MyColor 只是我用来表示 RGB 颜色的一个类。在 Python 中完成该任务的正确方法是什么?我来自 C++,因此我的 OOP 方法实际上是面向 C++ 的,所以我在以 pythonic 方式思考时遇到了一些困难。

提前致谢

【问题讨论】:

  • "但是看起来 Python 在销毁对象后调用了它。" - 首先,不,Python 不这样做。其次,__del__ 是终结器,而不是析构函数。在垃圾收集语言中,试图依赖对象销毁作为清理触发器是一个坏主意。
  • 我真的不知道,我在寻找解释时读到了。好的,所以这是一种不同的思维方式。唯一的方法是创建我自己的函数并调用它?为什么我喜欢析构它,因为它是自动的,不需要调用它
  • python 中最常见的模式是 上下文管理器,即支持with 语句的对象
  • 顺便说一句,请停止使用双下划线。人们可能已经告诉过你,双前导下划线在 python 中是“私有的”。但他们错了。理解这一点非常重要,Python 没有访问修饰符,并且所有内容实际上都是公开的。惯例是使用 single 前导下划线来表示某些属性或方法不是公共 API 的一部分
  • 好的,我将删除双下划线。同样是我的“C++ 方面”需要属性的隐私 ^^ 但这意味着我的属性存在在类之外被修改的风险,不是吗?

标签: python oop destructor


【解决方案1】:

让我们这样说吧。首先,“...就像我在 C++ 中所做的那样”方法是不合适的,因为我相信您自己也了解。不用说,Python 是完全不同的语言。但在这种特殊情况下,应该强调一下,因为 Python 的内存管理与 C++ 完全不同。 Python使用引用计数,当对象的引用计数变为零时,其内存将被释放(即当一个对象被垃圾回收)等等。

Python 用户定义的对象有时确实需要定义__del__() 方法。但它不是任何意义上的析构函数(肯定不是C++ 意义上的),它是finalizer。此外,不保证会为解释器退出时仍然存在的对象调用 __del__() 方法。然而,我们可以显式调用__del__(),它不应该适用于您的情况,为此我建议将 LED 关闭作为一种显式方法,而不是依赖 Python 的内部结构。就像它在 Zen of Python 中一样(import this 命令)。

显式优于隐式。

有关__del__() 的更多信息,请查看此good answer。有关引用计数的更多信息,请查看article

【讨论】:

  • 感谢您的回答,是的,我完全同意(并且知道)我不应该“像使用 C++ 一样思考”,就像我说外语时一样。但是真的很难^^谢谢解释,我会看看参考资料。当我编码时,我应该更多地考虑 Python 哲学 :)
  • @DarkPatate 很高兴听到。祝你好运! :)
【解决方案2】:

在编写__del__ 方法(终结器)时,您必须非常小心。它们几乎可以在不再引用对象后的任何时间被调用(它不一定立即发生),并且确实不能保证它们会在解释器退出时被调用。如果它们确实在解释器退出期间被调用,其他对象(例如全局变量和其他模块)可能已经被清理,因此您的终结器不可用。它们的存在是为了让对象可以清理状态(例如低级文件句柄、连接等),并且不像 C++ 析构函数那样起作用。根据我的 Python 经验,您很少需要编写自己的 __del__ 方法。

您还可以在此处使用其他机制。一种选择是try/finally

leds = LedStrip(...)
try:
    # application logic to interact with the LEDs
finally:
    leds.clear() # or whatever logic you need to clear the LEDs to zero

这仍然很明确。如果你想要一些更隐含的东西,你可以考虑使用 Python context manager 结构。要使用上下文管理器,请使用 with 关键字:

with open("file.txt", "w") as outfile:
    outfile.write("Hello!\n")

with 语句调用特殊的__enter__ 方法来初始化“上下文”。当块结束时,将调用__exit__ 方法来结束“上下文”。对于文件,__exit__ 将关闭该文件。关键是__exit__ 将被调用,即使块内发生异常(有点像try 块上的finally)。

你可以在你的LED灯条上实现__enter____exit__,然后写:

with LedStrip(...) as leds:
    # do whatever you want with the leds

当块结束时,__exit__ 方法可以重置所有 LED 的状态。

【讨论】:

  • 在我看来,我找到了__exit__ 更好的方法(因为我不需要自己调用 clear 函数)。两者都相当等效,还是应该在 Python 中首选一个?
  • 上下文管理器是一种很好的方式来表达“初始化一些东西,然后在你完成后清理它”的习惯用法,有点像你在 C++ 中可能习惯的 RAII。与 try-finally 不同,它让人很难忘记清理。所以,在这种情况下,我可能更喜欢上下文管理器。
猜你喜欢
  • 2012-02-05
  • 2016-07-05
  • 1970-01-01
  • 2012-03-09
  • 1970-01-01
  • 1970-01-01
  • 2013-04-30
  • 2018-09-24
  • 1970-01-01
相关资源
最近更新 更多