2

I am very new to Python and was surprised to find that this section of my code:

print len(allCommunities[5].boundary)
allCommunities[5].surface = triangularize(allCommunities[5].boundary)
print len(allCommunities[5].boundary)

Outputs this:

1310
2

Below is a function I wrote in Processing (a language like Java) and ported into Python. It does what it is supposed to (triangulate a polygon) but my intention was to pass inBoundary for the function to use but not remove elements from allCommunities[5].boundary.

How should I go about preventing allCommunities[5].boundary from being modified in the function? On a side note, I would appreciate pointers if I am doing something silly otherwise in the function, still getting used to Python.

def triangularize(inBoundary):
    outSurface = []

    index = 0;
    while len(inBoundary) > 2:
        pIndex = (index+len(inBoundary)-1)%len(inBoundary);
        nIndex = (index+1)%len(inBoundary);

        bp = inBoundary[pIndex]
        bi = inBoundary[index]
        bn = inBoundary[nIndex]

        # This assumes the polygon is in clockwise order
        theta = math.atan2(bi.y-bn.y, bi.x-bn.x)-math.atan2(bi.y-bp.y, bi.x-bp.x);
        if theta < 0.0: theta += math.pi*2.0;

        # If bp, bi, and bn describe an "ear" of the polygon
        if theta < math.pi:
            inside = False;

            # Make sure other vertices are not inside the "ear"
            for i in range(len(inBoundary)):
                if i == pIndex or i == index or i == nIndex: continue;

                # Black magic point in triangle expressions
                # http://answers.yahoo.com/question/index?qid=20111103091813AA1jksL
                pi = inBoundary[i]
                ep = (bi.x-bp.x)*(pi.y-bp.y)-(bi.y-bp.y)*(pi.x-bp.x)
                ei = (bn.x-bi.x)*(pi.y-bi.y)-(bn.y-bi.y)*(pi.x-bi.x)
                en = (bp.x-bn.x)*(pi.y-bn.y)-(bp.y-bn.y)*(pi.x-bn.x)

                # This only tests if the point is inside the triangle (no edge / vertex test)
                if (ep < 0 and ei < 0 and en < 0) or (ep > 0 and ei > 0 and en > 0):
                    inside = True;
                    break

            # No vertices in the "ear", add a triangle and remove bi
            if not inside:
                outSurface.append(Triangle(bp, bi, bn))
                inBoundary.pop(index)
        index = (index+1)%len(inBoundary)
    return outSurface

print len(allCommunities[5].boundary)
allCommunities[5].surface = triangularize(allCommunities[5].boundary)
print len(allCommunities[5].boundary)
asimes
  • 5,749
  • 5
  • 39
  • 76
  • Related: [least astonishment in python, the mutable default argument](http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument) – Burhan Khalid Sep 19 '13 at 08:00
  • @BurhanKhalid: Not really. There's no default argument at all here, let alone a mutable one. This is more about Python's call-by-object-reference semantics. – user2357112 Sep 19 '13 at 08:09

2 Answers2

4

Lists in Python are mutable, and operations such as

inBoundary.pop

modify them. The easy solution is to copy the list inside the function:

def triangularize(inBoundary):
    inBoundary = list(inBoundary)
    # proceed as before
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • There is not a problem with them having the same name? – asimes Sep 19 '13 at 07:59
  • 1
    @asimes: Nope, that is not a problem. – sberry Sep 19 '13 at 07:59
  • @asimes: on the contrary, the name `inBoundary` will just get re-bound to the new value (the copy), and only inside the function. – Fred Foo Sep 19 '13 at 08:00
  • Personally I find that confusing for usage even if having two of the same variable name works – asimes Sep 19 '13 at 08:02
  • 1
    @asimes: it can be very advantageous to reuse variable names, since you can't accidentally use the "wrong" value anymore (the unvalidated input). I use this idiom all the time in input validation code, and find that it's more robust than inventing a new name. – Fred Foo Sep 19 '13 at 08:03
3

The easiest thing to do would be to make a copy of the argument coming in:

def triangularize(origBoundary):
    inBoundary = origBoundary[:]

Then the rest of your code can stay the same.

sberry
  • 128,281
  • 18
  • 138
  • 165