Reputation: 10101
I've got a class (node
) to which I want to assign a specific set of functions (in this case a1
, a2
, b1
, and b2
) based on a parameter of the class (operatingMode
).
The situation is that I have a motor that has many different operating modes. Each operating mode allows for certain functions to be performed, but not others. The assignment of functions to various modes is done in a way that does not lend itself nicely to creating classes for each operating mode.
Below is my pass at a solution, but it doesn't work.
Any thoughts?
def a1(self):
return 'a1'
def a2(self):
return 'a2'
def b1(self):
return 'b1'
def b2(self):
return b2
class node(object):
def __init__(self,operatingMode):
self.operatingMode=operatingMode
if self.operatingMode=='A':
self.a1function=a1
self.a2function=a2
print 'Operating Mode \'A\' functions loaded'
if self.operatingMode=='B':
self.b1function=b1
self.b2function=b2
print 'Operating Mode \'B\' functions loaded'
def setOperatingMode(self,operatingMode):
self.operatingMode=operatingMode
self.__init__(self,operatingMode)
Running this in my terminal lets me call it, but I have to state elbow twice:
In [65]: elbow=node('A')
Operating Mode 'A' functions loaded
In [66]: elbow.a1function(elbow)
Out[66]: 'a1'
trying to run elbow.setOperatingMode('B')
yields an error.
Upvotes: 0
Views: 263
Reputation: 880797
In response to this comment:
This is for motor control, so I'm trying to limit overhead... operating modes will be changed much more infrequently than individual commands will be called.
Python instances can change their class. You could use this to change operating modes without needing to use an if
-clause to check the mode:
class Base(object):
# Put any methods shared by ANode and BNode here.
pass
class ANode(Base):
def a1(self):
return 'a1'
def a2(self):
return 'a2'
class BNode(Base):
def b1(self):
return 'b1'
def b2(self):
return 'b2'
elbow = ANode()
print(elbow.a1())
# a1
knee = ANode()
print(knee.a1())
# a1
elbow.__class__ = BNode
print(knee.a1())
# a1
print(elbow.b2())
# b2
elbow.a1()
# AttributeError: 'BNode' object has no attribute 'a1'
On the positive side, this is the fastest suggestion I've posted.
Notice there are no if
-statements in the code above. Once the class changes, all the available methods change along with it "instantly", purely due to normal Python method calling semantics.
If Node
is defined as in the decorator solution,
In [33]: elbow = Node('A')
In [34]: %timeit elbow.a1()
1000000 loops, best of 3: 288 ns per loop
While, if knee
is defined using ANode
,
In [36]: knee = ANode()
In [37]: %timeit knee.a1()
10000000 loops, best of 3: 126 ns per loop
So this solution is more than 2x as fast at calling methods than the decorator solution.
Switching speed is comparable:
In [38]: %timeit elbow.operatingMode = 'B'
10000000 loops, best of 3: 71.7 ns per loop
In [39]: %timeit knee.__class__ = BNode
10000000 loops, best of 3: 78.7 ns per loop
Caveat: One thing that is going to plague all the solutions I've posted is that after a switch the names the the available methods change. That means when you program using these classes, you have to keep track of the state of the instance before you can even know what methods are available. That is awkward.
Your program will be much much simpler if ANode
and BNode
have exactly the same interface -- all the same attribute and method names -- with just the definitions of those methods to changing (when the mode -- or class -- is changed).
Regarding this comment:
I've got about a hundred functions and 7 operation modes. Of those functions, about 10 are shared between all operation modes, 75 are shared by multiple modes, and 15 are exclusive to a particular mode. The problem is that the 75 aren't allocated to the modes nicely: Some might be in modes 1,4 and 7, others in 2,4,5, and 7, others in 1 and 5.
You can define the methods outside the classes, and then "hook them up" to Mode-based classes like this:
def a1(self):
return 'a1'
def a2(self):
return 'a2'
def b1(self):
return 'b1'
def b2(self):
return 'b2'
class Base(object):
# Put the 10 methods share by all modes here
def common1(self):
pass
class ANode(Base):
a1 = a1
a2 = a2
class BNode(Base):
b1 = b1
b2 = b2
class CNode(Base):
a1 = a1
b2 = b2
Upvotes: 1
Reputation: 880797
Perhaps using a checkMode
decorator would be a cleaner way -- this avoids __getattr__
and type.MethodType
magic:
def checkMode(mode):
def deco(func):
def wrapper(self):
if self.operatingMode == mode:
return func(self)
else:
raise TypeError('Wrong operating Mode')
return wrapper
return deco
class Node(object):
def __init__(self, operatingMode):
self.operatingMode = operatingMode
@checkMode('A')
def a1(self):
return 'a1'
@checkMode('A')
def a2(self):
return 'a2'
@checkMode('B')
def b1(self):
return 'b1'
@checkMode('B')
def b2(self):
return 'b2'
with the code above, we can do this:
elbow = Node('A')
print(elbow.a1())
# a1
knee = Node('A')
print(knee.a1())
# a1
elbow.operatingMode = 'B'
print(knee.a1()) # knee is still in operatingMode A
# a1
print(elbow.b2())
# b2
elbow.a1()
# TypeError: Wrong operating Mode
Explanation:
The decorator syntax works as follows:
@deco
def func(): ...
is equivalent to
def func(): ...
func = deco(func)
Above, checkMode
is a function which returns a decorator, deco
.
deco
then decorates the methods a1
, a2
, etc., so that
a1 = deco(a1)
Thus, a1
is the func passed to deco
. deco(a1)
, in turn, returns a new method, generically called wrapper
. This new method gets assigned to a1
by the statement a1 = deco(a1)
. So a1
is now the method wrapper
. So when you call elbow.a1()
, the code in wrapper
gets executed.
Upvotes: 1
Reputation: 880797
Using types.MethodType
and __getattr__
:
import types
def a1(self):
return 'a1'
def a2(self):
return 'a2'
def b1(self):
return 'b1'
def b2(self):
return 'b2'
class Node(object):
def __init__(self, operatingMode):
self.operatingMode = operatingMode
def __getattr__(self, attr):
if self.operatingMode=='A':
if attr == 'a1function':
return types.MethodType(a1, self)
if attr == 'a2function':
return types.MethodType(a2, self)
elif self.operatingMode=='B':
if attr == 'b1function':
return types.MethodType(b1, self)
if attr == 'b2function':
return types.MethodType(b2, self)
else:
raise AttributeError()
Then
elbow = Node('A')
print(elbow.a1function())
elbow.operatingMode = 'B'
print(elbow.b2function())
yields
a1
b2
Upvotes: 2
Reputation: 366113
Your entire design is very bizarre, and you probably need to take a step back and explain what you're trying to do so someone can help you with a better design.
But if you just want to make your design work, there are two problems with your current code:
self.a1function = a1
This sets self.a1function
to a regular function, not a bound method. You can explicitly create a bound method like this:
self.a1function=types.MethodType(a1, self, self.__class__)
Or you can set a1function
to be a wrapper around a1
. Or, more simply, do the wrapping dynamically, which means you can fake the bound-method-ness with a closure, which is arguably more readable:
def __getattr__(self, attr):
if attr == 'a1function' and self.operating_mode == 'A':
return lambda: a1(self)
Meanwhile:
trying to run elbow.setOperatingMode('B') yields an error.
You really need to post the traceback, or at least the error string, instead of just saying "yields an error". In this case, it tells you very explicitly what the error is, so you don't need to guess:
TypeError: __init__() takes exactly 2 arguments (3 given)
The problem is that in this line:
self.__init__(self,operatingMode)
… you're passing self
twice. It's the object you call the method on, and it's also the first parameter.
Calling __init__
from another method is a bad idea anyway, but if you really want to, it's the same as calling any other method:
self.__init__(operatingMode)
Upvotes: 0