views:

123

answers:

5

I understand that functions can have attributes. So I can do the following:

def myfunc():
    myfunc.attribute += 1
    print(myfunc.attribute)

myfunc.attribute = 1

Is it possible by any means to make such a function behave as if it were an instance? For example, I'd like to be able to do something like this:

x = clever_wrapper(myfunc)
y = clever_wrapper(myfunc)
x.attribute = 5
y.attribute = 9
x()   # I want this to print 6 (from the 5 plus increment)
y()   # I want this to print 10 (from the 9 plus increment)

As it stands, there is only one "instance" of the function, so attribute only exists once. Modifying it by either x or y changes the same value. I'd like each of them to have their own attribute. Is that possible to do at all? If so, can you provide a simple, functional example?

It is important that I be able to access attribute from inside of the function but have the value of attribute be different depending on which "instance" of the function is called. Essentially, I'd like to use attribute as if it were another parameter to the function (so that it could change the behavior of the function) but not pass it in. (Suppose that the signature of the function were fixed so that I cannot change the parameter list.) But I need to be able to set the different values for attribute and then call the functions in sequence. I hope that makes sense.

The main answers seem to be saying to do something like this:

class wrapper(object):
    def __init__(self, target):
        self.target = target
    def __call__(self, *args, **kwargs):
        return self.target(*args, **kwargs)

def test(a):
    return a + test.attribute

x = wrapper(test)
y = wrapper(test)
x.attribute = 2
y.attribute = 3
print(x.attribute) 
print(y.attribute)
print(x(3))
print(y(7))

But that doesn't work. Maybe I've done it incorrectly, but it says that test does not have attribute. (I'm assuming that it's because wrapper actually has the attribute.)

The reason I need this is because I have a library that expects a function with a particular signature. It's possible to put those functions into a pipeline of sorts so that they're called in order. I'd like to pass it multiple versions of the same function but change their behavior based on an attribute's value. So I'd like to be able to add x and y to the pipeline, as opposed to having to implement a test1 function and a test2 function that both do almost exactly the same thing (except for the value of the attribute).

+5  A: 

You can make a class with a __call__ method which would achieve a similar thing.

Edit for clarity: Instead of making myfunc a function, make it a callable class. It walks like a function and it quacks like a function, but it can have members like a class.

Skilldrick
I need to be able to access the attribute inside of the function. Would that still be possible using your approach?
agarrett
Yes; but he's using a class now (as a function), not a simple function.
Nick T
Yes, you can access any member of a class. We're all consenting adults here, we don't have private members ;)
Wayne Werner
Yes, since `__call__` is just another method and gets self passed, you can access all attributes of self.
delnan
This doesn't work: class wrapper(object): def __init__(self, target): self.target = target def __call__(self, *args, **kwargs): return self.target(*args, **kwargs) def test(a): return a + test.attribute x = wrapper(test) y = wrapper(test) x.attribute = 2 y.attribute = 3 print(x.attribute) print(y.attribute) print(x(3)) print(y(7))So, how do I make this work? The function already exists, so I have to pass it into the class instance. I have to find a work-around that allows that.
agarrett
Crap. How do you post code in a comment? I'll just edit my original post.
agarrett
Your question was "Is it possible to make Python functions behave like instances?" The answer is no, but it is possible to make Python instances behave like functions.
Skilldrick
A: 
#!/usr/bin/env python
# encoding: utf-8

class Callable(object):
    attribute = 0

    def __call__(self, *args, **kwargs):
        return self.attribute

def main():
    c = Callable()
    c.attribute += 1
    print c()


if __name__ == '__main__':
    main()
Matt Williamson
+1 for example, -1 for mixing up class and instance variables = +0
delnan
I was under the impression creating a new instance of a class made a copy of the class's dict. Or else how would it be accessible via self.? I'm just using it as an initializer. See an example: http://gist.github.com/519150
Matt Williamson
You miss attribute += 1
shiki
There I added it, but it doesn't make a difference.
Matt Williamson
If you have two Callable objects they'll share the same attribute. You need to do `self.attribute = 0` in the constructor.
Skilldrick
That's not true. just tested it c = Callable() c.attribute += 1 c2 = Callable() c2.attribute += 5 print c() print c2()
Matt Williamson
A: 

A generator might be an alternate solution here:

def incgen(init):
    while True:
        init += 1
        print init
        yield

x = incgen(5)
y = incgen(9)

x.next() # prints 6
y.next() # prints 10
y.next() # prints 11
x.next() # prints 7

You can't dig back in to the generator and manipulate the data though.

Nick T
+2  A: 
class Callable(object):
    def __init__(self, x):
        self.x = x

    def __call__(self):
        self.x += 1
        print self.x

>> c1 = Callable(5)
>> c2 = Callable(20)
>> c1()
6
>> c1()
7
>> c2()
21
>> c2()
22
Bryan Ross
+2  A: 

A nicer way:

def funfactory( attribute ):
    def func( *args, **kwargs ):
        # stuff
        print( attribute )
        # more stuff

    return func

x = funfactory( 1 )
y = funfactory( 2 )

x( ) # 1
y( ) # 2

This works because the functions are closures, so they will grab all local variables in their scope; this causes a copy of attribute to be passed around with the function.

katrielalex
Woo! Functional :)
Skilldrick