在Python中配置类的优雅方式是什么?

2 投票
5 回答
1355 浏览
提问于 2025-04-16 21:25

我正在模拟一个分布式系统,里面的所有节点都遵循某种协议。这包括对协议中一些小变化的评估。这里的变化指的是对某个方法的不同实现。所有节点总是遵循相同的变化,这个变化是由实验配置决定的(在任何时候只有一个配置是激活的)。我想知道,怎样做最清晰,同时又不影响性能呢?

因为实验可能会很复杂,所以我显然不想使用任何条件判断。之前我用过继承的方式,比如:

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')]

撰写回答