2

I have a function that I want to use multiple times, on every use I want a variable outside of the function to increment. Along the lines of -

def funtion1(mylists,x):
    row=1
    for elm in mylists:
        ws.write(row,x,elm)
        row+=1
    x += 1

col=0
function1(mylist1,col)
function1(mylist2,col)
function1(mylist3,col)

etc

I thought col would increment on every use, but it stays at 0. Should I be returning a value from the function?

PTrem2
  • 35
  • 6
  • When passing immutable data types, such as integers, they follow similar semantics to "pass-by-value". i.e. `x` is local to the function, and changing it does not affect the value of `col`. If it was a mutable object, and you changed a member value within the function - you would see your expected behaviour. –  Dec 06 '17 at 17:36
  • 2
    you could define the variable as global or return the value – Ora Dec 06 '17 at 17:37
  • 1
    [good resource](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html) – RSHAP Dec 06 '17 at 17:49

3 Answers3

3

You are incrementing a temporary variable x in the function namespace, therefore col is not modified. If you want to change col you can:

  • use a class with a classmethod and a class attribute
  • use a decorator as in Paul Panzer's answer
  • return the value of x and affect it to col
  • use a global statement.

If you are unfamiliar with namespace check this link

First using a class with a classmethod and a class attribute:

class functions:
    col = 0

    @classmethod
    def function1(cls, mylists):
        row=1
        for elm in mylists:
            ws.write(row, cls.col,elm)
            row+=1
        cls.col += 1

functions.function1(mylist1)
functions.function1(mylist2)
functions.function1(mylist3)

This would be my preferred option as the namespaces are not polluted and the code is cleaner than with returning a value.

Now returning a value:

def funtion1(mylists,x):
    row=1
    for elm in whichlist:
        ws.write(row,x,elm)
        row=row+1
    return x + 1

col = 0
col = function1(mylist1,col)
col = function1(mylist2,col)
col = function1(mylist3,col)

Or with a global:

def function1(mylists):
    global col
    row=1
    for elm in mylists:
        ws.write(row,col,elm)
        row+=1
    col += 1

col=0
function1(mylist1)
function1(mylist2)
function1(mylist3)
Jacques Gaudin
  • 15,779
  • 10
  • 54
  • 75
2

You could create a function attribute to hold the counter. Here is a decorator that does this:

import functools as ft

def I_can_count(f):
    @ft.wraps(f)
    def wrapper(*args, **kwds):
        wrapper.times_called += 1
        return f(*args, **kwds)
    wrapper.times_called = 0
    return wrapper

@I_can_count
def f(x):
    print x

print(f.times_called)
f(100)
print(f.times_called)
f(100)
print(f.times_called)

# 0
# 100
# 1
# 100
# 2

Please note that the purpose of the decorator here is twofold: (1) it adds convenience; but more importantly (2) it protects the namespace where the function or rather its wrapper finds itself. Without that the following accident can happen:

def f(x)
    f.times_called += 1
    print(x)
f.times_called = 0

f(100)
# 100
f.times_called
# 1
g = f
del f
g(100)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 2, in f
# NameError: name 'f' is not defined
Paul Panzer
  • 51,835
  • 3
  • 54
  • 99
  • I like that solution. Out of curiosity what advantage does it have over class encapsulation? – Jacques Gaudin Dec 06 '17 at 18:46
  • @JacquesGaudin I wouldn't insist on that it's better. It's a bit shorter; I'd guess a bit more light weight (I may be wrong there). OTOH the class approach tends to be a bit more readable especially when there are additional parameters (like a starting value for the counter). The point of decorators is of course that they are reusable - just add a single line before any function def and your function has the new features. In your class solution you have to retype the incrementing for each new function, or if subclassing at least call super. Finally, decorators can be created using classes. – Paul Panzer Dec 06 '17 at 19:08
1

Two options, pure functions which don't modify anything outside their scope, Most recommended

as our friend Jacques Gaudin has added: https://stackoverflow.com/a/47680265/7579116

the other one, less fancy, and not recommended but is what you asked for is to declare x as global

x = 0
def funtion1(mylists,x):
    global x
    row=1
    for elm in whichlist:
        ws.write(row,x,elm)
        row=row+1
    x += 1

function1(mylist1,col)
function1(mylist2,col)
function1(mylist3,col)
Shailyn Ortiz
  • 766
  • 4
  • 14