0

I have made a class to represent my led strip, and I would like to switch off the strip when I stop it (aka when the program stops and the object is destroyed). Hence, as I would do in C++, I created a destructor to do that. But it looks like Python call it after it destroyed the object. Then I got a segmentation fault error.
Here is my class, the destructor just have to call the function to set the colour of each LED to 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 is just a class that I made to represent an RGB colour. What would be the correct what to achieve that task in Python? I come from C++, hence my OOP method is really C++ oriented, so I have some difficulties thinking in a pythonic way.

Thanks in advance

Dark Patate
  • 113
  • 2
  • 12
  • 1
    "But it looks like Python call it after it destroyed the object." - first, no, Python doesn't do that. Second, `__del__` is a finalizer, not a destructor. Trying to rely on object destruction as a cleanup trigger is a bad idea in garbage-collected languages. – user2357112 May 27 '21 at 03:09
  • I don't know really, I read that when I was looking for an explanation. Ok, so it's a different way of thinking. The only way is to create my own function and to call it? Why I like destructor it because it's automatic, no need to call it – Dark Patate May 27 '21 at 03:18
  • The most common pattern in python for something like this would be a *context manager*, i.e. an object that supports the `with` statement – juanpa.arrivillaga May 27 '21 at 04:40
  • As an aside, stop with the double-underscores. People might have told you that double leading underscores is "private" in python. But they were wrong. It is very important to understand, Python doesnt have access modifiers, and *everything is effectively public*. The convention is to use a *single* leading underscores to signal that some attribute or method is not part of the public API – juanpa.arrivillaga May 27 '21 at 04:43
  • Ok, I'll remove the double underscores. Again it's my "C++ side" that requires privacy of attributes ^^ But that means there is a risk that my attribute is modified outside the class, isn't it? – Dark Patate May 27 '21 at 06:44

2 Answers2

2

Let's put it this way. Firstly, "...as I would do in C++" approach is not appropriate, as I'm sure you know yourself. It goes without saying that Python is totally different language. But in this particular case it should be stressed, since Python's memory management is quite different from C++. Python uses reference counting, when objects reference count goes to zero, its memory will be released (i.e. when an object is garbage collected) and so on.

Python user-defined objects sometimes do need to define __del__() method. But it is not a destructor in any sense (not in C++ sense for sure), it's finalizer. Moreover, it is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits. Yet we can invoke __del__() explicitly, it should not be for your case, as for this I would advise to make LED switch off as an explicit method, not relying on Python's internals. Just like it goes in Zen of Python (import this command).

Explicit is better than implicit.

For more information on __del__(), check this good answer. For more on reference counting check this article.

rawrex
  • 4,044
  • 2
  • 8
  • 24
  • Thanks for your answer, and yeah I totally agree (and know) that I should not "think like in C++", same as when I talk a foreign language. But it's really hard ^^ Thanks for the explanation, I will look at the references. And I should think about the *Python philosophy* more when I code :) – Dark Patate May 27 '21 at 06:30
  • @DarkPatate glad to hear. Best of luck! :) – rawrex May 27 '21 at 06:36
2

You have to be very careful when writing __del__ methods (finalizers). They can be called at virtually any time after an object is no longer referenced (it doesn’t necessarily happen immediately) and there's really no guarantee that they'll be called at interpreter exit time. If they do get called during interpreter exit, other objects (such as global variables and other modules) might already have been cleaned up, and therefore unavailable to your finalizer. They exist so that objects can clean up state (such as low-level file handles, connections, etc.), and don't function like C++ destructors. In my Python experience, you rarely need to write your own __del__ methods.

There are other mechanisms you could use here. One choice would be 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

This is still pretty explicit. If you want something a bit more implicit, you could consider using the Python context manager structure instead. To use a context manager, you use the with keyword:

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

The with statement calls the special __enter__ method to initialize the "context". When the block ends, the __exit__ method will be called to end the "context". For the case of a file, __exit__ would close the file. The key is that __exit__ will be called even if an exception occurs inside the block (kind of like finally on a try block).

You could implement __enter__ and __exit__ on your LED strip, then write:

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

and when the block ends, the __exit__ method could reset the state of all the LEDs.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • I found the way with ```__exit__``` better, in my point of view (because I don't need to call the clear function myself). Both are *quite* equivalent, or one should be preferred in Python? – Dark Patate May 27 '21 at 06:23
  • Context managers are a nice way to express the idiom of “initializing something and then cleaning it up when you’re done”, kind of like the RAII you may be used to in C++. Unlike try-finally, it makes it hard to forget to clean up. So, I’d probably prefer context managers in this kind of case. – nneonneo May 27 '21 at 06:53