3

I am trying to build some code and I have defined a function as this to test how a counter works inside of the function:

def errorPrinting(x):
    x += 1
    return x

I then use the function in some conditional logic where I want the counter to increase if the conditions are met.

x = 1

for row in arcpy.SearchCursor(fc):              

    if not row.INCLUSION_TYPE or len(row.TYPE.strip()) == 0:
        errorPrinting(x)
        print x

    elif len(row.TYPE) not in range(2,5):
        errorPrinting(x)
        print x

    elif row.INCLUSION_TYPE.upper() not in [y.upper() for y in TableList]:
        errorPrinting(x)
        print x

I'm still fairly new with using functions, so maybe I am not understanding how to return the value back outside of the function to be used in the next iteration of the for loop. It keeps returning 1 on me. Can anyone show me how to return the x outside of the function after it has been increased by one x+= 1?

Thanks, Mike

Mike
  • 4,099
  • 17
  • 61
  • 83
  • arcpy... I pity you, sir. That aside, what exactly are you trying to do inside your `errorPrinting` function? I understand you want a counter, but what are you trying to do besides increment `x`? In this code, `errorPrinting` could simply be replaced by `x += 1`, and it would work. So what were you hoping to gain by using the function? The answer to that could seriously affect some of the structural suggestions in the answers, I think. – jpmc26 Aug 30 '13 at 02:44

4 Answers4

3

You're bit by scope. You may want to check out this link for a quick primer.

You can do something simple and say x = errorPrinting(x) in all cases you call errorPrinting and get what you want. But I think there are better solutions where you'll learn more.

Consider implementing an error logger object that maintains a count for you. Then you can do logger.errorPrinting() and your instance of logger will manage the counter. You may also want to look into python's built in logging facilities.

NG.
  • 22,560
  • 5
  • 55
  • 61
3

You're not incrementing your global x, you're incrementing the local paramater that also happens to be named x! (Your parameter to errorPrinting could have been named anything. I'm calling it xLocal.)

As you can see here, x isn't incremented by the function.

>>> def inc(xLocal):
...     xLocal += 1
...     return xLocal
... 
>>> x = 4
>>> inc(x)
5
>>> x
4

You need to reassign the value of x to the return value of the function each time. Like this

x = 1
for row in arcpy.SearchCursor(fc):              

    if not row.INCLUSION_TYPE or len(row.TYPE.strip()) == 0:
        x = errorPrinting(x) # <=== here
        print x

    elif len(row.TYPE) not in range(2,5):
        x = errorPrinting(x) # <=== here
        print x

    elif row.INCLUSION_TYPE.upper() not in [y.upper() for y in TableList]:
        x = errorPrinting(x) # <=== here
        print x

Integral parameters and other primitives aren't normally passed by reference in Python. (Lists, dicts, etc. are. Modifying lists unintentionally is actually a very common mistake in Python.)

Edit: passing by "reference" and "value" isn't really correct to talk about in Python. See this nice question for more details.

So, using my previous example:

>>> x = 4
>>> x = inc(x)
>>> x
5

Note that if this had been parameter that is passed by reference, like a list, this strategy would have worked.

>>> def incList(xList):
...     for i in range(len(xList)):
...         xList[i] += 1
... 
>>> xList
[1]
>>> incList(xList)
>>> xList
[2]

Note that normal, Pythonic syntax:

for i in xList:
    i += 1

would not increment the global value.

Note: If you're looking to keep tabs on a lot of things, I also recommend the logging module that @SB. mentioned. It's super useful and makes debugging large programs a lot easier. You can get time, type of message, etc.

Community
  • 1
  • 1
vroomfondel
  • 3,056
  • 1
  • 21
  • 32
  • 1
    Careful saying "Parameters aren't normally passed by reference in python." - primitives typically by value / copy, but things like list/dict/obj are reference. – NG. Aug 29 '13 at 21:55
  • 1
    I don't particularly like the "passed by reference/passed by value" language in relation to how Python handles passing parameters. The issue is that in the underlying implementation, everything is passed the same way, however assignment distinguishes between "mutable" and "immutable" objects and immutable objects' references are replaced with a different object at assignment time. – Mark R. Wilkins Aug 29 '13 at 22:11
  • @MarkR.Wilkins very good point. I've found myself slipping into incorrect terminology. I'll edit this answer to point to a link with a little more information about this. – vroomfondel Aug 29 '13 at 22:12
  • @MarkR.Wilkins Does assignment really distinguish between anything? I would think that `x = 1` and `x = {}` work the same way. It just happens that one assigns an immutable object (represented by a literal in this case) and the other is a mutable object. A function could modify a `dict` parameter because it's mutable, but if it assigns a new `dict` to the variable instead of modifying the `dict` the variable points to, then that's not any different. Or am I missing something? – jpmc26 Aug 30 '13 at 02:50
  • Well, at least indexed assignment is implemented differently for the different types. mytuple[0] = 3 will throw an exception, while mylist[0] = 3 will not. The distinction happens at the time of the attempted assignment. – Mark R. Wilkins Aug 30 '13 at 06:35
2

Edited for the OP's benefit, since if functions are a new concept, my earlier comments may be a little hard to follow.

I personally think the nicest way to address this issue is to wrap your related code in an object.

Python is heavily based on the concept of objects, which you can think of as grouping data with functions that operate on that data. An object might represent a thing, or in some cases might just be a convenient way to let a few related functions share some data.

Objects are defined as "classes," which define the type of the object, and then you make "instances," each of which are a separate copy of the grouping of data defined in the class.

class MyPrint(object):

    def __init__(self):
        self.x = 1

    def errorPrinting(self):
        self.x += 1
        return self.x

    def myPrint(self):
        for row in arcpy.SearchCursor(fc):              

            if not row.INCLUSION_TYPE or len(row.TYPE.strip()) == 0:
                self.errorPrinting()
                print self.x

            elif len(row.TYPE) not in range(2,5):
                self.errorPrinting()
                print self.x

            elif row.INCLUSION_TYPE.upper() not in [y.upper() for y in TableList]:
                self.errorPrinting()
                print self.x

p = MyPrint()
p.myPrint()

The functions __init__(self), errorPrinting(self), and myPrint(self), are all called "methods," and they're the operations defined for any object in the class. Calling those functions for one of the class's instance objects automatically sticks a self argument in front of any arguments that contains a reference to the particular instance that the function is called for. self.x refers to a variable that's stored by that instance object, so the functions can share that variable.

What looks like a function call to the class's name:

p = MyPrint()

actually makes a new instance object of class MyPrint, calls MyPrint.__init__(<instance>), where <instance> is the new object, and then assigns the instance to p. Then, calling

p.myprint()

calls MyPrint.myprint(p).

This has a few benefits, in that variables you use this way only last as long as the object is needed, you can have multiple counters for different tasks that are doing the same thing, and scope is all taken care of, plus you're not cluttering up the global namespace or having to pass the value around between your functions.

Mark R. Wilkins
  • 1,282
  • 7
  • 15
0

The simplest fix, though perhaps not the best style:

def errorPrinting():
    global x
    x += 1

Then convert x=errorPrinting(x) to errorPrinting ()

"global x" makes the function use the x defined globally instead of creating one in the scope of the function.

The other examples are good though. Study all of them.

Fred Mitchell
  • 2,145
  • 2
  • 21
  • 29
  • Cluttering the namespace with global state for something as simple as a counter is not a good choice. – benjamin Aug 30 '13 at 05:20
  • Benjamin - It depends... In a standalone program, with a few functions used to separate concerns, and some time constraints, using global may "git 'er done" in the US comedian vernacular. Designing a class or a reusable component - horrible. Getting religious about it... – Fred Mitchell Sep 05 '13 at 01:57
  • Correct. Anyways, I can only remove my downvote (system-restriction), if you edit your answer. cu around – benjamin Sep 05 '13 at 08:20