6

The function attribute do_something.n is incremented each time you call the function.

It bothered me that I declared the attribute do_something.n=0 outside the function.

I answered the question Using queue.PriorityQueue, not caring about comparisons using a "function-attribute" to provide a unique counter for usage with PriorityQueue's - there is a nicer solution by MartijnPieters)

MCVE:

def do_something():
    do_something.n += 1
    return do_something.n 

# need to declare do_something.n before usign it, else 
#     AttributeError: 'function' object has no attribute 'n'
# on first call of do_something() occures
do_something.n = 0

for _ in range(10):
    print(do_something())  # prints 1 to 10

What other ways are there, to define the attribute of a function "inside" of it so you avoid the AttributeError: 'function' object has no attribute 'n' if you forget it?


Edited plenty of other ways in from comments:

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • 1
    Static variables would fit this use, but doesn't seem there's such an implementation in python. This [post](https://stackoverflow.com/questions/279561/what-is-the-python-equivalent-of-static-variables-inside-a-function) has relevant info. – busybear Jan 03 '19 at 21:43
  • @busybear interesting post ... did not think to search for "static variable" ... – Patrick Artner Jan 03 '19 at 21:46
  • 1
    You may be able do it with a decorator similar to what is shown in the second part of my answer to the question [Access a function variable outside the function without using 'global'](https://stackoverflow.com/questions/19326004/access-a-function-variable-outside-the-function-without-using-global). – martineau Jan 03 '19 at 21:49

5 Answers5

7

Not quite inside, but a decorator makes the function attribute more obvious:

def func_attr(**attrs):
    def wrap(f):
        f.__dict__.update(attrs)
        return f
    return wrap

@func_attr(n=0)
def do_something():
    do_something.n += 1
    return do_something.n

This is probably cleaner than anything that places the attribute initialization inside the function.

user2357112
  • 260,549
  • 28
  • 431
  • 505
2

What about using the built-in hasattr function?

def do_something():
    if not hasattr(do_something, 'n'):
        do_something.n = 1
    do_something.n += 1
    return do_something.n 

For reference, here is a discussion of hasattr vs a try-except:

hasattr() vs try-except block to deal with non-existent attributes

Luke DeLuccia
  • 541
  • 6
  • 16
1

This is was what I had in mind when I referred you to my answer to that other question:

def with_this_arg(func):
    def wrapped(*args, **kwargs):
        return func(wrapped, *args, **kwargs)
    return wrapped

@with_this_arg
def do_something(this):
    if not getattr(this, 'n', None):
        this.n = 0
    this.n += 1
    return this.n

for _ in range(10):
    print(do_something())  # prints 1 to 10

If you prefer the more "pythonic" EAFP style of coding—which would would be slightly faster—it could be written thusly:

@with_this_arg
def do_something(this):
    try:
        this.n += 1
    except AttributeError:  # First call.
        this.n = 1
    return this.n

Of course...

This could be combined with @user2357112's answer (if done in the proper order) into something like this which doesn't require checking or exception handling:

def func_attr(**attrs):
    def wrap(f):
        f.__dict__.update(attrs)
        return f
    return wrap

def with_this_arg(func):
    def wrapped(*args, **kwargs):
        return func(wrapped, *args, **kwargs)
    return wrapped

@func_attr(n=0)
@with_this_arg
def do_something(this):
    this.n += 1
    return this.n

for _ in range(10):
    print(do_something())  # prints 1 to 10
martineau
  • 119,623
  • 25
  • 170
  • 301
  • I am not sure I fully groke the with_this_arg - it works, but I think it is mightier then needed so to say - it is more for when you would need access to the wrapper function directly from insidethe wrapped function somehow (you could create a dict that stores all wrapped inner function as member of the wrapper-function todo ... whatever from _any_ wrapped function - maybe some kind of complicated signal_to_all thingy) - I'll keep in mind where to find this If I need it. Thanks for sharing as well. – Patrick Artner Jan 05 '19 at 08:51
  • Patrick: In a nutshell all this does is give the function a way to refer to itself without hardcoding its name every place that it does so—i.e. in a generic way. This might be important of it did it more than once, so that changing all the references isn't much work. – martineau Jan 05 '19 at 08:58
1

Here's yet another couple of ways. The first uses what some call a "functor" class to create a callable with the desired attributes—all from within the class.

This approach doesn't require handling exceptions so the only runtime overhead is from the one-time creation of its single instance.

class do_something:
    def __init__(self):
        self.n = 0

    def __call__(self, *args, **kwargs):
        do_something.n += 1
        return do_something.n

do_something = do_something()  # Allow only one instance to be created.


for _ in range(10):
    print(do_something())  # Prints 1 to 10.

The second way — which is very "pythonic" — would be to put the function in a module (which are effectively singletons). This is what I mean:

File do_something.py:

n = 0

def do_something():
    global n
    n += 1
    return n

Sample usage (in some other script):

from do_something import do_something

for _ in range(10):
    print(do_something())  # Prints 1 to 10.
martineau
  • 119,623
  • 25
  • 170
  • 301
  • If it was only about providing a number this would be ok, but I am trying to get a kindof counter for one function on each call - thanks for sharing though – Patrick Artner Jan 05 '19 at 08:45
  • 1
    Oh. You could make a decorator that counted how many times a function was called. Decorators can be implemented as classes whose instances each have their own separate "state" associated with the particular function being decorated. Get the drift? – martineau Jan 05 '19 at 08:53
0

The only way I can think of, following pythons Ask forgiveness not permission methodology is:

def do_something():
    try:
        do_something.n += 1
    except AttributeError:
        do_something.n = 1

    return do_something.n 

This will automatically generate the attribute on the first call and after that the try: codeblock will work.

There is some overhead due to try: ... catch: but that is the only way I can think of to solve this inside the function.

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • 1
    However you decide to do it, it's important to keep in mind that using function attributes is essentially the same as using global variables—and may complicate matters when used with functions that are in any way recursive or are ever used in a multithreaded context, for example. This can make code written using them subtly less "general-purpose" than they would be otherwise. – martineau Jan 04 '19 at 09:03