Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to add an attribute to a function as part of the function definition?

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:

  • What is the Python equivalent of static variables inside a function? by @busybear
  • Access a function variable outside the function without using "global" by Martineau
like image 701
Patrick Artner Avatar asked Jan 03 '19 21:01

Patrick Artner


People also ask

Can a function have an attribute?

Functions already have a number of attributes, some of which are writable, e.g. func_doc , a.k.a. func.

How do you define a function attribute?

A function attribute is specified with the keyword __attribute__ followed by the attribute name and any additional arguments the attribute name requires. A function __attribute__ specification is included in the declaration or definition of a function.

What is the use of __ attribute __ in C?

The __attribute__ directive is used to decorate a code declaration in C, C++ and Objective-C programming languages. This gives the declared code additional attributes that would help the compiler incorporate optimizations or elicit useful warnings to the consumer of that code.


3 Answers

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.

like image 109
user2357112 supports Monica Avatar answered Sep 30 '22 02:09

user2357112 supports Monica


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

like image 44
Luke DeLuccia Avatar answered Sep 30 '22 04:09

Luke DeLuccia


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
like image 43
martineau Avatar answered Sep 30 '22 02:09

martineau