在Python中配置类的优雅方式是什么?
我正在模拟一个分布式系统,里面的所有节点都遵循某种协议。这包括对协议中一些小变化的评估。这里的变化指的是对某个方法的不同实现。所有节点总是遵循相同的变化,这个变化是由实验配置决定的(在任何时候只有一个配置是激活的)。我想知道,怎样做最清晰,同时又不影响性能呢?
因为实验可能会很复杂,所以我显然不想使用任何条件判断。之前我用过继承的方式,比如:
class Node(object):
def dumb_method(self, argument):
# ...
def slow_method(self, argument):
# ...
# A lot more methods
class SmarterNode(Node):
def dumb_method(self, argument):
# A somewhat smarter variant ...
class FasterNode(SmarterNode):
def slow_method(self, argument):
# A faster variant ...
但现在我需要测试所有可能的变体,而不想让源代码里出现大量的类,这样会显得很乱。我还希望其他人看代码时,能轻松理解。你有什么建议吗?
编辑:我之前没有强调这一点:对于所有设想的使用场景,似乎在配置时对类进行修改是个不错的选择。我的意思是:可以通过简单的 Node.dumb_method = smart_method
来实现。但我总觉得这样做不太对劲。这种解决方案会不会让一个聪明的读者感到困惑呢?
5 个回答
0
我不太确定你是不是想做类似这样的事情(允许在运行时更换“继承”):
class Node(object):
__methnames = ('method','method1')
def __init__(self, type):
for i in self.__methnames:
setattr(self, i, getattr(self, i+"_"+type))
def dumb_method(self, argument):
# ...
def slow_method(self, argument):
# ...
n = Node('dumb')
n.method() # calls dumb_method
n = Node('slow')
n.method() # calls slow_method
或者你是在寻找像这样的东西(允许运行(因此也可以测试)这个类的所有方法):
class Node(object):
#do something
class NodeTest(Node):
def run_tests(self, ending = ''):
for i in dir(self):
if(i.endswith(ending)):
meth = getattr(self, i)
if(callable(meth)):
meth() #needs some default args.
# or yield meth if you can
2
你可以使用 __slots__
这个机制和一个工厂类。你需要为每个实验创建一个 NodeFactory
的实例,但之后它会帮你处理 Node
实例的创建。下面是一个例子:
class Node(object):
__slots__ = ["slow","dumb"]
class NodeFactory(object):
def __init__(self, slow_method, dumb_method):
self.slow = slow_method
self.dumb = dumb_method
def makenode(self):
n = Node()
n.dumb = self.dumb
n.slow = self.slow
return n
这是一个示例运行:
>>> def foo():
... print "foo"
...
>>> def bar():
... print "bar"
...
>>> nf = NodeFactory(foo, bar)
>>> n = nf.makenode()
>>> n.dumb()
bar
>>> n.slow()
foo
2
你可以使用 type
函数来创建新的子类型。你只需要把子类的命名空间以字典的形式提供给它就可以了。
# these are supposed to overwrite methods
def foo(self):
return "foo"
def bar(self):
return "bar"
def variants(base, methods):
"""
given a base class and list of dicts like [{ foo = <function foo> }]
returns types T(base) where foo was overwritten
"""
for d in methods:
yield type('NodeVariant', (base,), d)
from itertools import combinations
def subdicts(**fulldict):
""" returns all dicts that are subsets of `fulldict` """
items = fulldict.items()
for i in range(len(items)+1):
for subset in combinations(items, i):
yield dict(subset)
# a list of method variants
combos = subdicts(slow_method=foo, dumb_method=bar)
# base class
class Node(object):
def dumb_method(self):
return "dumb"
def slow_method(self):
return "slow"
# use the base and our variants to make a number of types
types = variants(Node, combos)
# instantiate each type and call boths methods on it for demonstration
print [(var.dumb_method(), var.slow_method()) for var
in (cls() for cls in types)]
# [('dumb', 'slow'), ('dumb', 'foo'), ('bar', 'slow'), ('bar', 'foo')]