2011-07-13 5 views
1

Я имитирую распределенную систему, в которой все узлы следуют за некоторым протоколом. Это включает в себя оценку некоторых небольших изменений в протоколе. Вариант означает альтернативную реализацию одного метода. Все узлы всегда придерживаются той же вариации, которая определяется конфигурацией эксперимента (только одна конфигурация активна в любой момент времени). Какой самый лучший способ сделать это, не жертвуя успехом?Самый элегантный способ настройки класса в 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. Но почему-то это не чувствовать себя право. Может ли такое решение вызвать серьезную головную боль для случайного умного читателя?

+0

Поддерживать список методов? Насколько я вижу, нет ничего, что требовало бы выделенных классов. Взгляните на шаблон стратегии. – phant0m

+0

Да, это более или менее то, что * стратегия * для. Но здесь это более ограничено тем, что в любой момент каждый клиент будет использовать точно такую ​​же стратегию. Таким образом, объект, инкапсулирующий алгоритм, кажется излишним, и я хочу избежать потери ресурсов. – lRem

+0

Но у вас уже есть объект «инкапсулирования» алгоритма: класс.Имейте в виду, что каждый класс является экземпляром объекта (это метакласс, по умолчанию «тип») – phant0m

ответ

2

Вы можете создать новые подтипы с помощью функции type. Вам просто нужно дать ему пространство имен подклассов как dict.

# 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')] 
+0

Довольно приятно. Теперь я рассматриваю это или реализую * шаблон стратегии *. – lRem

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 
+0

Больше похоже на первый. Но все * варианты * независимы, поэтому для каждого из возможных вариантов понадобится один аргумент '__init__'. Один из вариантов, который я рассматривал: держите узел простым с набором вариантов по умолчанию. После настройки запустите его с помощью 'Node.dumb_method = smart_method'. Но тогда, где должен «умный_метод» жить? Быть функцией с 'self' в качестве аргумента? Быть в собственном «классе», который никогда не будет создан? – lRem

+0

@IRem: дать 'Node' все методы (+ один только что называется' method' или что-то еще) и сделать 'Node.method = None.smart_method'? – Steven

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 
+0

Спасибо, что упомянул '__slots__', хороший механизм. Но при этом присвоение классу напрямую является достаточным в этом случае и даже более эффективным. – lRem

0

Вы можете использовать metaclass для этого.

Если вы создадите класс «на лету» с именами методов в соответствии с каждым вариантом.

+0

Это, наверное, лучшее описание метаклассов, которые я видел. Хотелось бы, чтобы я мог продвинуться;) Я сделал небольшую поправку на это: пожалуйста, просмотрите рецензию. – lRem

+0

Спасибо. Есть на самом деле другие опечатки, но я слишком ленив, чтобы исправить их сейчас :-) –

0

Должен ли быть вызванный метод решаться при создании экземпляра класса или после? Предполагая, что это когда экземпляр класса создается, как насчет следующего:

class Node(): 
    def Fast(self): 
     print "Fast" 

    def Slow(self): 
     print "Slow" 

class NodeFactory(): 
    def __init__(self, method): 
     self.method = method 

    def SetMethod(self, method): 
     self.method = method 

    def New(self): 
     n = Node() 
     n.Run = getattr(n, self.method) 
     return n 

nf = NodeFactory("Fast") 

nf.New().Run() 
# Prints "Fast" 

nf.SetMethod("Slow") 

nf.New().Run() 
# Prints "Slow" 
+0

Как я добавил к вопросу: я могу идти дальше, вплоть до исправления всего класса, а не только объекта. Вызываемый метод определяется при конфигурации до создания экземпляра любого Узла и не изменяется, если нужны Узлы. – lRem

+0

Не отвечает ли мой ответ на то, что вам нужно? Если вам не нужно специально исправлять весь класс, о котором я подозреваю, вы этого не делаете. – combatdave

+0

Это так, но когда я думаю об этом, исправление класса более выгодно. Во-первых, это не означает необходимости в заводе. Во-вторых, в этом конкретном случае исправление класса гарантирует, что * Все узлы всегда следуют той же самой вариации *. – lRem

Смежные вопросы