2016-03-10 2 views
0

Я просто поймал себя писать код так:Force подписи конкретных функций

def register(self, *, # * enforces keyword-only parameters 
      key_value_container:dict=None, # legal parameter set #1 
      key:str=None, value=None):  # legal parameter set #2 

    # enforce one of the parameter sets 
    if not ((key_value_container is not None and 
      key is None and value is None) or 
      (key is not None and value is not None and 
      key_value_container is None)): 
     raise TypeError('provide (key_value_container) or (key/value') 

    # handle each legal parameter setf 
    if key_value_container is not None: 
     for _k, _s in key_value_container.items(): 
      self.register(_k, _s) 
    else: 
     do_something_with(key, value) 

Моя цель состояла в том, чтобы иметь две подписи метода register(): он должен взять key и или какой-то контейнер value с рядом ключи и значения, например a dict.

В * в аргументе по крайней мере, заставляет меня представить именованные аргументы, но нет никакой необходимости, чтобы обеспечить заданное число аргументов или даже данный набор аргументов.

Конечно, я могу (должен) предоставить два метода с разными именами и сигнатурами вместо одного.

Но в случае, если я хочу (иметь) предоставить один метод/функцию с несколькими семантикой параметров - что является лучшим способом сделать это видимым и обеспечить его соблюдение?

Подробно:

  • это можно сделать ясно автозавершения и документации которые являются комбинации правовых параметров?
  • Как я могу проверить (с минимальным кодом шаблона), была ли предоставлена ​​одна комбинация?
+0

Итак, вы в основном хотите реализовать полиморфные методы в Python? – jonrsharpe

+0

Мне было бы достаточно иметь элегантный способ проверить и сообщить некоторые ограничения. Python уже проверяет согласованность для случая, когда вы указываете * фиксированное число * аргументов (и оно также проверяет * максимальное количество аргументов). Но на самом деле это, по крайней мере, очень близко к полиморфным методам/функциям. – frans

+0

Вы можете реализовать что-то с помощью декоратора, чтобы сделать это (отправка от одного имени до нескольких реализаций), но это, вероятно, не будет хорошо работать с автозаполнением и т. Д., Поскольку метод, который вы на самом деле вызываете, будет иметь произвольные '* args , ** интерфейс kwargs'. – jonrsharpe

ответ

1

Чтобы избежать кода шаблона, вы могли бы что-то вроде этого: написать дополнительные функции, которые определяют разные подписи. Затем используйте вспомогательную функцию redirect, которая отправляет параметры из основной функции в дополнительные функции, в зависимости от того, какая подпись соответствует параметрам.

Это все еще не особенно элегантный. Если вы обобщаете это, вам также необходимо будет рассмотреть, как обращаться с случаями, когда None является допустимым параметром, который вы, возможно, захотите передать.

from inspect import getargspec 
class Eg(object): 
    def register(self, key_value_container=None, key=None, value=None): 
     return redirect(locals(), [self.register_dict, self.register_single]) 

    def register_dict(self, key_value_container=None): 
     print('register dict', key_value_container) 

    def register_single(self, key=None, value=None): 
     print('register single', key, value) 

def redirect(params, functions): 
    provided = {k for k, v in params.items() if v is not None} 
    for function in functions: 
     if set(getargspec(function).args) == provided: 
      return function(**{k: params[k] for k in provided if k != 'self'}) 
    raise TypeError('wrong configuration provided') 
    # This error could be expanded to explain which signatures are allowed  

a = Eg() 
a.register(key='hi', value='there') 
a.register(key_value_container={'hi': 'there'}) 
+0

Это хорошо, потому что у вас все еще есть составная подпись (которая может потребоваться по определению API) и явная специализация, в то время как код шаблона из источника (очень пунша :) :) – frans

+0

это сработало для вас? Мне пришлось вычитать 'self' из argspec-'set' – frans

+0

Если' None' не делает подходящего значения по умолчанию, вы можете просто создать свой собственный объект (например, '_DEFAULT = object()' и 'def my_func (x = _DEFAULT) '), который будет выполнять ту же функцию, но без риска иметь другие значения. – acdr

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