Passing a function as an argument from within the same class?

Z

zealalot

So, I'm trying to come up with a way to pass a method (from the same
class) as the default argument for another method in the same class.
Unfortunately though, I keep getting "self not defined" errors since
the class hasn't been read completely before it references itself.

Is there a better way of doing this?

--- CODE ---

class SomeClass():
def doNothing(self):
pass
def function1(self):
print "Running function 1."
def function2(self, passedFunction=self.doNothing):
print "Running passed function."
passedFunction()

someObject = SomeClass()
someObject.function2(someobject.function1)

--- CODE ---

Thanks,
- Zealalot
 
B

bearophileHUGS

Zealalot, probably there are some ways to do that, but a simple one is
the following (not tested):

def function2(self, passed_function=None):
if passed_function is None:
passed_function = self.doNothing
...

Bye,
bearophile
 
C

CTO

Make doNothing a classmethod.

class SomeClass:

@classmethod
def doNothing(cls):
pass

def function1(self):
print "Running function 1"

def function2(self, passedFunction=SomeClass.doNothing):
print "Running passed function"
passedFunction()

someObject = SomeClass()
someObject.function2()
someObject.function2(someObject.function1)
 
Z

zealalot

Make doNothing a classmethod.

class SomeClass:

    @classmethod
    def doNothing(cls):
        pass

    def function1(self):
        print "Running function 1"

    def function2(self, passedFunction=SomeClass.doNothing):
        print "Running passed function"
        passedFunction()

someObject = SomeClass()
someObject.function2()
someObject.function2(someObject.function1)

It's not surprising, but I've never heard of a classmethod before.
Basically, I read that it basically removes the need for the 'self'
argument. Very cool!

And thanks for the quick response,

- Zealalot
 
P

Peter Otten

CTO said:
Make doNothing a classmethod.

class SomeClass:

@classmethod
def doNothing(cls):
pass

def function1(self):
print "Running function 1"

def function2(self, passedFunction=SomeClass.doNothing):
print "Running passed function"
passedFunction()

someObject = SomeClass()
someObject.function2()
someObject.function2(someObject.function1)

To make that run without error you have to jump through a lot of hoops:

class SomeClass(object):
@classmethod
def doNothing(cls):
pass
def function1(self):
print "Running function 1"

def function2(self, passedFunction=SomeClass.doNothing):
print "Running passed function"
passedFunction()

SomeClass.function2 = function2

someObject = SomeClass()
someObject.function2()
someObject.function2(someObject.function1)

And if you don't need access to the instance you may not need access to the
class either. In this case you can simplify:

def doNothing():
pass

class SomeClass(object):
def function1(self):
print "Running function 1"
def function2(self, passedFunction=doNothing):
print "Running passed function"
passedFunction()

If you do need information about the state of the instance you can either
pass it explicitly

class SomeClass(object):
def doNothing(self):
pass
def function1(self):
print "Running function 1"
def function2(self, passedFunction=doNothing):
print "Running passed function"
passedFunction.__get__(self)()

or (better, I think) use a sentinel as shown by Bearophile.

Peter
 
S

Steven D'Aprano

It's not surprising, but I've never heard of a classmethod before.
Basically, I read that it basically removes the need for the 'self'
argument. Very cool!

Not so.

When you call an ordinary method (an "instance method"), Python
automatically inserts the object itself as the first argument. So you
need to define the method with one extra parameter, self.

(Using the name self is just a convention. You can call it anything you
like.)

For classmethods, Python automatically inserts not the object itself, but
the object's *class* as the first argument. So you still need to define
the method with an extra parameter, only now the convention is to call is
cls rather than self.

Here's an example:

class Test(object):
x = 0 # class attribute
def __init__(self, x):
self.x = x # instance attribute
def spam(self):
print self, self.x
@classmethod
def ham(cls):
print cls, cls.x


And in use:
<class '__main__.Test'> 0


There is one other related built-in decorator, staticmethod().
staticmethod() tells Python not to pass any extra argument to the method.
That means the inside a static method, you can't refer to either the
instance (self) or the class! Needless to say, there aren't very many
uses for staticmethod().
 
S

Steven D'Aprano

So, I'm trying to come up with a way to pass a method (from the same
class) as the default argument for another method in the same class.
Unfortunately though, I keep getting "self not defined" errors since the
class hasn't been read completely before it references itself.

Is there a better way of doing this?

My first instinct is to say "Don't do that!", but let's see if there's a
way to get what you want. It's actually very easy: just put the
definition of the passed method before the method you want to use it in,
then refer to it by name *without* self.

However, there is a catch: you need to manually pass in the instance,
instead of letting Python do it for you.

class Spam(object):
def ham(self):
return "ham"
def spam(self, func=ham):
return "spam is a tasty %s-like food product" % func(self)


And in use:
'spam is a tasty ham-like food product'
 
C

CTO

Careful, bearophiles' answer remains the best one.
The only reason your example worked is that you had already had
SomeClass defined (probably from a previous experiment).

Scott is correct, and if bearophile and I ever give you conflicting
advice, take bearophile's.

A (corrected) bit of code that might serve your purpose would be
as follows:

class SomeClass:

def doNothing():
pass

def function1(self):
print "running function 1"

def function2(self, passedFunction=doNothing):
print "running passed function"
passedFunction()

again, though- bearophile's is the best, and most standard,
answer here
 
D

Dave Angel

zealalot said:
It's not surprising, but I've never heard of a classmethod before.
Basically, I read that it basically removes the need for the 'self'
argument. Very cool!

And thanks for the quick response,

- Zealalot
As you've probably figured out by now, that also gets an error, since
you can't refer to the SomeClass until the definition is complete.
Better is either the sentinel approach, or defining the function outside
the class. Note that since the signature must match the passed
function, you presumably need neither a self nor a cls. So keep it
simple and define doNothing as a top-level function.

But it's worth expanding on the notion of a classmethod. This type of
function is callable with any object, or just with the class name
itself. But it doesn't have an access to instance attributes. That's
good, but not very common. Class attributes are more common, and
they're attributes which are shared among all the objects of the class.
 
T

Terry Reedy

zealalot said:
So, I'm trying to come up with a way to pass a method (from the same
class) as the default argument for another method in the same class.
Unfortunately though, I keep getting "self not defined" errors since
the class hasn't been read completely before it references itself.

Is there a better way of doing this?

--- CODE ---

class SomeClass():
def doNothing(self):
pass
def function1(self):
print "Running function 1."
def function2(self, passedFunction=self.doNothing):
print "Running passed function."
passedFunction()

someObject = SomeClass()
someObject.function2(someobject.function1)

As Stephen D'Aprano indicated, this is very easy

class SomeClass():
def doNothing(self):
print("Doing nothing")
def function1(self):
print ("Running function 1.")
def function2(self, passedFunction=doNothing):
print ("Running passed function.")
passedFunction(self)

someObject = SomeClass()
someObject.function2()
someObject.function2(SomeClass.function1)

produces (with 3.0.1)

Running passed function.
Doing nothing
Running passed function.
Running function 1.

Key 1: a class statement introduces a new local namespace. The body of
the class statement is executed in that namespace. Default arguments
are evaluated, when a def statement is executed, in the local namespace
of that def statement. For methods, that is the local namespace of the
class. Hence, 'passedFunction = doNothing' works fine.

Key 2: When a parameter is a function, the signature of the default and
passed args are effectively part of the required type for the args. In
this case, passedFunction is a function with one parameter. When
captured, doNothing is not yet a method of the yet-to-become Someclass.
So non-defaults passed to .function2 do not have to be methods either.
In Python 3, unbound methods are simply functions anyway.

Terry Jan Reedy
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,981
Messages
2,570,188
Members
46,731
Latest member
MarcyGipso

Latest Threads

Top