Python bug at 3.10.11

I found a bug. A class (for example: PaddleOCR_Text_Block) has a private list field , _cache = . When add item to it , if item’s type is same type as PaddleOCR_Text_Block. the bug will happen
follow is test code

class Intersect():
    _cache = []

    def add_intersect(self, another):
        if self == another:
            print('add self? = True')
            return

        self._cache.append(another) <----- here 
        print(self._cache)
        print(another)

    @property
    def has_intersect(self):
        return len(self._cache) > 0

    @property
    def intersects(self):
        return self._cache
class PaddleOCR_Text_Block(Rect, Intersect):
    _words = None

    def __init__(self, wordsBlock):
        self._words = wordsBlock[1][0]
        points = wordsBlock[0]
        super().__init__(points[0][0], points[0][1], points[2][0], points[2][1])

    @property
    def words(self):
        print(self._words)
        if self.has_intersect:
            for c in self.intersects:
                print(c.words)
                self._words = self._words + c.words
        return self._words

    def __str__(self):
        return f'words: {self._words}, x1={self.x1}, y1={self.y1}, x2={self.x2}, y2={self.y2}'

see the screen-snaps. when add item to list, both _cache updated

This isn’t a bug, it’s just how class-level variables work. The _cache = [] assignment only executes once, so there’s only one list. Your debugger is a little misleading, there’s no copy involved. When accessing attributes on classes, it chains to every parent class.

The simplest solution would be to not define this in Intercept, so you get an error if not defined in the subclass. You could do it automatically though by defining __init_subclass__ in Intercept. This method is called on classes when they inherit, so you could set the cache attribute there to a new list.

2 Likes

Thanks for the suggestion. I had changed my code , add

    def __init_subclass__(cls):
        cls.__cache = []

full code

class Intersect():

    def __init_subclass__(cls):
        cls.__cache = []

    def add_intersect(self, another):
        if self == another:
            print('add self? = True')
            return

        self.__cache.append(another)
        print(self.__cache)
        print(another)

    @property
    def has_intersect(self):
        return len(self.__cache) > 0

    @property
    def intersects(self):
        return self.__cache

but, ‘bug ?’ still exists. see the first part of screen shot.
After tested the above code, i moved __cache to PaddleOCR_Text_Block, and tested again, but problem still. see the other parts of screen shot.

Class variable is not a private one. There is no private and public variable concept in python to my understanding. I guess what you are confused with is class variable vs instance variable.

https://docs.python.org/3/glossary.html#term-class-variable

class variable
A variable defined in a class and intended to be modified only at class level (i.e., not in an instance of the class).

I guess you are trying to create two instances of this class, and when you modify one and expect it does not change the other one. As Spencer said, there is only one list for this class, i.e., Intersect.__cache, so when you change it at an instance with add_intersect (instance method), it updates this only list and reflects on all instances.

You should not change class variable with instance method. Instead define __cache as instance variable.

Maybe try this:

class Intersect():

    def __init__(self):
        self.__cache = []

    def add_intersect(self, another):
        if self == another:
            print('add self? = True')
            return

        self.__cache.append(another)
        print(self.__cache)
        print(another)

    @property
    def has_intersect(self):
        return len(self.__cache) > 0

    @property
    def intersects(self):
        return self.__cache

I just thought of that. Based on the previous screenshots, I also noticed that ,reference types class level variables will cause the problem. If it is a value type, there seems to be no problem.

Not “reference type” or “value type”; you are mutating a mutable value with self.__cache.append. The object has no __cache attribute of its own, so self.__cache resolves to type(self).__cache: every instance mutates the same list value.

1 Like

Thanks. I think class variable in python looks like static field of a class in C#.