Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Function attributes or mutable default values

Tags:

python

Say you have a function that needs to maintain some sort of state and behave differently depending on that state. I am aware of two ways to implement this where the state is stored entirely by the function:

  1. Using a function attribute
  2. Using a mutable default value

Using a slightly modified version of Felix Klings answer to another question, here is an example function that can be used in re.sub() so that only the third match to a regex will be replaced:

Function attribute:

def replace(match):
    replace.c = getattr(replace, "c", 0) + 1
    return repl if replace.c == 3 else match.group(0)

Mutable default value:

def replace(match, c=[0]):
    c[0] += 1
    return repl if c[0] == 3 else match.group(0)

To me the first seems cleaner, but I have seen the second more commonly. Which is preferable and why?

like image 626
Andrew Clark Avatar asked Jan 18 '23 21:01

Andrew Clark


2 Answers

I use closure instead, no side effects.

Here is the example (I've just modified the original example of Felix Klings answer):

def replaceNthWith(n, replacement):
    c = [0]
    def replace(match):
        c[0] += 1
        return replacement if c[0] == n else match.group(0)
    return replace

And the usage:

 # reset state (in our case count, c=0) for each string manipulation
 re.sub(pattern, replaceNthWith(n, replacement), str1)
 re.sub(pattern, replaceNthWith(n, replacement), str2)
 #or persist state between calls
 replace = replaceNthWith(n, replacement)
 re.sub(pattern, replace, str1)
 re.sub(pattern, replace, str2)

For mutable what should happen if somebody call replace(match, c=[])?

For attribute you broke encapsulation (yes i know that python didn't implemented in classes from diff reasons ...)

like image 71
Nicolae Dascalu Avatar answered Jan 28 '23 05:01

Nicolae Dascalu


Both ways feel strange to me. The first though is much better. But when you think about it this way: "Something with a state that can do operations with that state and additional input", it really sounds like a normal object. And when something sounds like an object, it should be an object... SO, my solution would be to use a simple object with a __call__ method:

class StatefulReplace(object):
    def __init__(self, initial_c=0):
        self.c = initial_c
    def __call__(self, match):
        self.c += 1
        return repl if self.c == 3 else match.group(0)

And then you can write in the global space or your module init:

replace = StatefulReplace(0)
like image 28
xubuntix Avatar answered Jan 28 '23 04:01

xubuntix