1

In my open source project https://github.com/WolfgangFahl/play-chess-with-a-webcam i am using the class Warp (see https://github.com/WolfgangFahl/play-chess-with-a-webcam/blob/master/src/WebApp.py and the code excerpt below) to track the warp points for a chessboard. The ideas is that the user clicks one point of a trapezoid after another until the chessboard position in in a image is known. Then the image is "warped" accordingly. To get back to the non-warped image another click will reset the warp.

This logic is implemented by counting the number of points already added to the "pointlist". The default for the pointlist is set to empty but it can also be specified via the commandline.

this is the relevant constructor snippet:

def __init__(self,pointList=[],rotation=0,bgrColor=(0, 255, 0)):
       self.pointList = pointList
       self.updatePoints()

Now there is a pytest for testing this class see https://github.com/WolfgangFahl/play-chess-with-a-webcam/blob/master/src/test_Warp.py and the code below.

When running this code directly

python3 test_Warp.py

the code runs successful on different machines, python3.x versions and travis. But if i run the whole test suite of the project

scripts/test

The behavior depends on whether the getTestWarp function has:

def getTestWarp():
    warp=Warp([])

which works. Or:

def getTestWarp():
    warp=Warp()

which leads to the testcase to fail if other tests have run before. So the context makes a difference.

How can this be?

Is it a bug or an expected behavior and explainable by some python specific way of handling default parameters and instance attributes?

Warp class

class Warp(YamlAbleMixin,JsonAbleMixin):
    """ holds the trapezoid points to be use for warping an image take from a peculiar angle """

    # construct me from the given setting
    def __init__(self,pointList=[],rotation=0,bgrColor=(0, 255, 0)):
       self.rotation=rotation
       self.bgrColor=bgrColor
       self.pointList = pointList
       self.updatePoints()

    def rotate(self,angle):
       self.rotation=self.rotation+angle
       if self.rotation>=360:
         self.rotation=self.rotation % 360

    def updatePoints(self):
      pointLen=len(self.pointList)
      if pointLen==0:
        self.points=None
      else:
        self.points=np.array(self.pointList)
      self.warping=pointLen==4

    def addPoint(self,px,py):
        """ add a point with the given px,py coordinate
        to the warp points make sure we have a maximum of 4 warpPoints if warppoints are complete when adding reset them
        this allows to support click UIs that need an unwarped image before setting new warp points.
        px,py is irrelevant for reset """
        if len(self.pointList)>=4:
          self.pointList=[]
        else:
          self.pointList.append([px,py])
        self.updatePoints()

test_Warp.py

def getTestWarp():
    warp=Warp([])
    warp.addPoint(678,25)
    warp.addPoint(1406,270)
    warp.addPoint(1136,1048)
    warp.addPoint(236,666)
    return warp



def test_WarpPoints():
    warp=getTestWarp()
    print (warp.pointList)
    print (warp.points)
    assert warp.pointList==[[678, 25], [1406, 270], [1136, 1048], [236, 666]]
    # simulate clear click
    warp.addPoint(0,0)
    warp.addPoint(679,25)
    warp.addPoint(1408,270)
    warp.addPoint(1136,1049)
    warp.addPoint(236,667)
    print (warp.pointList)
    print (warp.points)
    assert warp.pointList==[[679, 25], [1408, 270], [1136, 1049], [236, 667]]

test_WarpPoints()
Wolfgang Fahl
  • 15,016
  • 11
  • 93
  • 186
  • 1
    Uhm, isn't `pointList=[]` your problem? https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments But that's really just the very first thing that struck me. – tmt Oct 29 '19 at 15:02
  • @tmt - so that's it: the most common surprise .. it's an easter egg for people from other programming languages ... i love it ... – Wolfgang Fahl Oct 29 '19 at 15:34
  • see also https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument/1145781#1145781 – Wolfgang Fahl Nov 10 '19 at 06:48

0 Answers0