pfletch101
Posts: 527
Joined: Sat Feb 24, 2018 4:09 am
Location: Illinois, USA

SOLVED - Frustrating problem with python code in Flask application

Wed Nov 06, 2019 11:59 pm

The behavior of the following chunk of code has me tearing my hair out! It is from one of the 'routes' in a fairly complex Flask application, which I am writing to schedule Wemo devices. This is only a section of a much longer piece of code, and its purpose is to handle edits to the weekly 'pattern' for turning on (or off) a device at a given time. On the form that it is attached to, each day of the week is represented by a check box. If the name of the relevant checkbox ("Day_0" through "Day_6") exists in request.form (which is a dictionary), the box was checked. If the box was checked when the form was originally displayed, fields['pattern'][n] (where n corresponds to the Day #) is set to 1, if not, it is set to 0. In this code, 'changed' should be set to True if any of the check boxes has changed value.

The problem I am currently facing is that the code works as it should if a box that was originally displayed checked is unchecked by the user, but does not respond correctly if a box that was unchecked is checked by the user. The bizarre thing is that the newly checked box is correctly detected, and the first debugging flash duly appears when the form reloads. This flash also correctly reports that the relevant fields['pattern'][n] value is 0. However, the test a couple of lines below, which should succeed (since the value is not equal to one), fails, and changed is not set to True (and the second debugging flash does not appear). Can anyone make any sense out of this?

Code: Select all

if 'Repeat' in request.form:
    count=0
    for daynum in range(0,7):
        fldname='Day_'+ str(daynum)
        if fldname in request.form:
            flash(fldname + ': ' + str(fields['pattern'][daynum])) # added for debugging
            mypattern.set_day(daynum)
            count +=  1
            if fields['pattern'][daynum] != 1:
                changed=True
                flash('changed should be True') # added for debugging
        elif fields['pattern'][daynum] == 1:
            mypattern.reset_day(daynum)
            changed=True
    if count == 0:
        errors=True
        flash('Repeating actions must repeat on at least one day of the week')
    else:
        action.pattern=mypattern.encode()

pfletch101
Posts: 527
Joined: Sat Feb 24, 2018 4:09 am
Location: Illinois, USA

Re: SOLVED - Frustrating problem with python code in Flask application

Thu Nov 07, 2019 9:20 pm

The explanation for the weird behavior was that the contents of the fields['pattern'] array was being changed when I changed the corresponding setting in the mypattern Class instance, so, by the time the code got to testing it, it was the same as the relevant setting from request.form. Because of the way the code was written, this only affected its behavior when a previously checked box had been unchecked.

The basic problem was that, in writing the Class of which mypattern was an instance, I had not taken account of the way Python passes objects: the Class keeps the representation of the daily settings for an instance in an internal bytearray. The function that I had used to initialize fields['pattern'] should have been returning a copy of this array, but was actually returning a reference to the instance's internal array. Changing the setting in the instance also changed it in fields['pattern'], since this was actually pointing to the same array. I am not used to Classes allowing you to do this sort of thing!

billio
Posts: 70
Joined: Thu Dec 15, 2011 8:25 am
Contact: Website

Re: SOLVED - Frustrating problem with python code in Flask application

Thu Nov 07, 2019 10:57 pm

Although I haven't seen your code and my apologies if I am wrong, but I expect you were doing something like this :

Code: Select all

class A() :
    b = {1:0,2:0}
    def __init__(self) :
        self.b = A.b
    def up(self,c,d) :
        self.b[1] = c
        self.b[2] = d
        print(f"{A.b},{self.b}")
The output being :

Code: Select all

>>> x = A()
>>> x.b
{1: 0, 2: 0}
>>> A.b
{1: 0, 2: 0}
>>> x.up(1,1)
{1: 1, 2: 1},{1: 1, 2: 1}
This issue isn't really what classes do but rather what the assignment operator = does. It copies the dictionary as an object not as a copy of an object. You can test this with :

>>> A.b is x.b
True
>>> id(A.b)
140688454482624
>>> id(x.b)
140688454482624

Normally one would set up the instance variable in the __init__() function, not copying it from the Class variables. then each instance is initialised with a separate dictionary.

Code: Select all

class A() :
    def __init__(self) :
        self.b = {1:0,2:0}
    def up(self,c,d) :
        self.b[1] = c
        self.b[2] = d

pfletch101
Posts: 527
Joined: Sat Feb 24, 2018 4:09 am
Location: Illinois, USA

Re: SOLVED - Frustrating problem with python code in Flask application

Thu Nov 07, 2019 11:46 pm

billio wrote:
Thu Nov 07, 2019 10:57 pm
Although I haven't seen your code and my apologies if I am wrong, but I expect you were doing something like this :
Well, not quite. :) Here is a copy of the relevant parts of the corrected Class. I was vaguely aware of the issue, which is why the original code contains the extra assignment to a temporary variable. As you point out, however, this is equivalent to doing a direct assignment - and equally wrong!

Code: Select all

class DayPattern:

    def __init__(self,bitfield):
        self._pattern=bytearray(7)
        if bitfield is None:
            bitfield = 0
        elif bitfield < 0:
            raise ValueError('Cannot Initialize pattern with negative number')
        value = bitfield
        for count in range(0,7):
            self._pattern[count] = value & 1
            value = value >> 1

### Various other functions

    def array(self):  # this was the problem function - problem solved by returning A COPY OF the internal array
#      retvar = self._pattern  # Original code
#      return retvar # Original code
        return self._pattern.copy() # if we don't do it this way, a reference to the internal array is passed

    def from_array(self, arr):
        if len(arr) != 7:
            raise ValueError('Passed Array must contain exactly 7 items')
        for count in range(0,7):
            if arr[count]:
                self._pattern[count]=1
            else:
                self._pattern[count]=0



billio
Posts: 70
Joined: Thu Dec 15, 2011 8:25 am
Contact: Website

Re: SOLVED - Frustrating problem with python code in Flask application

Fri Nov 08, 2019 4:20 pm

Yes I have tripped up over that a few times. You can also avoid the problem with :

Code: Select all

return bytearray(self._pattern)
# creates a new bytearray

Return to “Python”