переключение регистра в питоне не работает; нужен другой шаблон - PullRequest
4 голосов
/ 08 октября 2010

Мне нужна помощь с кодом здесь, я хотел реализовать шаблон регистра переключателей в python, поэтому, как сказано в каком-то учебном пособии, я могу использовать словарь для этого, но вот моя проблема:

  # type can be either create or update or .. 
  message = { 'create':msg(some_data),
              'update':msg(other_data)
              # can have more
            }

  return message(type)

но это не работает для меня, потому что some_data или other_data могут иметь значение None (оно вызывает ошибку, если его нет), а функция msg должна быть простой (я не хочу помещать в нее некоторые условия).

проблема здесь в том, что функция msg () выполняется каждый раз для заполнения слова в отличие от шаблона случая переключения, который обычно на другом языке программирования не выполняет код в case, если это не совпадение.

Есть ли другой способ сделать это, или мне просто нужно сделать, если elif ...

Обновление: спасибо за все ответы, но на самом деле это больше похоже на это

message = { 'create': "blabla %s" % msg(some_data),
            'update': "blabla %s" % msg(other_data)
            'delete': "blabla %s" % diff(other_data, some_data)
           }

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

Ответы [ 9 ]

9 голосов
/ 08 октября 2010

Похоже, вы усложняете это больше, чем нужно. Вы хотите просто?

if mytype == 'create':
    return msg(some_data)
elif mytype == 'update':
    return msg(other_data)
else:
    return msg(default_data)

У вас нет для использования диктов и ссылок на функции только потому, что вы можете . Иногда скучный, явный блок if/else - это именно то, что вам нужно. Это понятно даже самым новым программистам в вашей команде и никогда не вызовет msg () без необходимости. Я также готов поспорить, что это будет быстрее, чем другое решение, над которым вы работали, за исключением случаев, когда количество случаев становится большим, а msg () молниеносно.

9 голосов
/ 08 октября 2010
message = { 'create':msg(some_data or ''),
            'update':msg(other_data or '')
            # can have more
          }

Еще лучше, чтобы предотвратить выполнение msg только для заполнения диктанта:

message = { 'create':(msg,some_data),
            'update':(msg,other_data),
            # can have more
          }
func,data=message[msg_type]
func(data)

и теперь вы можете определить более разумную msg функцию, которая может иметь аргумент, равный None:

def msg(data):
    if data is None: data=''
    ...
3 голосов
/ 08 октября 2010

Оказывает на меня шутки, и я был замешан в изобретательской игре за 5 лет до того, как начал изучать python: Удобочитаемая конструкция переключателя без лямбд или словарей .Ну что ж.Читайте ниже для другого способа сделать это.


Здесь.Есть заявление о смене.(с некоторыми хорошими исправлениями @martineau)

with switch(foo):

    @case(1)
    def _():
        print "1"

    @case(2)
    def _():
        print "2"

    @case(3)
    def _():
        print "3"

    @case(5)
    @case(6)
    def _():
        print '5 and 6'

    @case.default
    def _():
        print 'default'

Я добавлю (умеренно) взломанный стек, нарушенные декораторы и сомнительный менеджер контекста бесплатно.Это некрасиво, но функционально (и не в хорошем смысле).По сути, все, что он делает - это оборачивает логику словаря в уродливую обертку.

import inspect

class switch(object):
    def __init__(self, var):
        self.cases = {}
        self.var = var


    def __enter__(self):
        def case(value):
            def decorator(f):
                if value not in self.cases:
                    self.cases[value] = f
                return f
            return decorator

        def default(f):
            self.default = f
            return f
        case.default = default 

        f_locals = inspect.currentframe().f_back.f_locals
        self.f_locals = f_locals.copy()
        f_locals['case'] = case

    def __exit__(self, *args, **kwargs):
        new_locals = inspect.currentframe().f_back.f_locals
        new_items = [key for key in new_locals if key not in self.f_locals]
        for key in new_items:
             del new_locals[key]              # clean up
        new_locals.update(self.f_locals)      # this reverts all variables to their
        try:                                  # previous values
            self.cases[self.var]()
        except KeyError:
            try:
                getattr(self, 'default')()
            except AttributeError:
                pass

Обратите внимание, что взломанный стек на самом деле не нужен.Мы просто используем его для создания области видимости, чтобы определения, встречающиеся в операторе switch, не просачивались во входящую область и не доставляли case в пространство имен.Если вы не возражаете против утечек (например, зацикливание утечек), то вы можете удалить взлом стека и вернуть декоратор case из __enter__, используя предложение as в операторе with, чтобы получить его во вложенииОбъем.

2 голосов
/ 08 октября 2010

Вот что-то немного другое (хотя и несколько похожее на @ Tumbleweed's) и, возможно, более «объектно-ориентированное». Вместо явного использования словаря для обработки различных случаев, вы можете использовать класс Python (который содержит словарь).

Этот подход обеспечивает довольно естественный перевод операторов C / C ++ switch в код Python. Как и последний, он откладывает выполнение кода, который обрабатывает каждый случай, и позволяет предоставить код по умолчанию.

Код для каждого метода класса switch, который соответствует регистру, может состоять из нескольких строк кода вместо показанных здесь return <expression> и все они компилируются только один раз. Однако есть одно отличие или ограничение в том, что обработка в одном методе не может и не может быть выполнена автоматически, чтобы «упасть» в код следующего (что не является проблемой в примере, но будет хорошо).

class switch:
    def create(self): return "blabla %s" % msg(some_data)
    def update(self): return "blabla %s" % msg(other_data)
    def delete(self): return "blabla %s" % diff(other_data, some_data)
    def _default(self): return "unknown type_"
    def __call__(self, type_): return getattr(self, type_, self._default)()

switch = switch() # only needed once

return switch(type_)
2 голосов
/ 08 октября 2010

Вы можете скрыть оценку внутри лямбды:

message = { 'create': lambda: msg(some_data),
            'update': lambda: msg(other_data),
          }
return message[type]()

Пока все имена определены (поэтому вы не получите NameError), вы также можете структурировать это так:

message = { 'create': (msg, some_data),
            'update': (other_func, other_data),
          }
func, arg = message[type]
return func(arg)
1 голос
/ 08 октября 2010

как насчет создания нового класса и переноса данных / аргументов в объект - поэтому вместо привязки данных путем передачи аргументов - вы можете просто позволить функции решать, какие параметры ей нужны ...

class MyObj(object):
        def __init__(self, data, other_data):
                self.data = data
                self.other_data = other_data

        def switch(self, method_type):
                return {
                                "create": self.msg,
                                "update": self.msg,
                                "delete": self.delete_func,
                        }[method_type]()

        def msg(self):
                #process self.data
                return "Hello, World  !!"

        def delete_func(self):
                #process other data self.other_data or anything else....
                return True

if "__main__" == __name__:
        m1 = MyObj(1,2)
        print m1.switch('create')
        print m1.switch('delete')
1 голос
/ 08 октября 2010

Вы можете отложить выполнение сопоставления, используя lambda:

return {
    'create': lambda: msg(some_data),
    'update': lambda: msg(other_data),
    # ...
}[type]()

Если во всех случаях просто вызывается msg с разными аргументами, вы можете упростить это до:

return msg({
    'create': some_data,
    'update': other_data,
    # ...
}[type])
0 голосов
/ 08 октября 2010

Поскольку код, который вы хотите выполнить в каждом случае, взят из безопасного источника, вы можете сохранить каждый фрагмент в отдельном строковом выражении в словаре и сделать что-то вроде этого:

message = { 'create': '"blabla %s" % msg(some_data)',
            'update': '"blabla %s" % msg(other_data)',
            'delete': '"blabla %s" % diff(other_data, some_data)'
          }

return eval(message[type_])

Выражение в последней строке также может быть eval(message.get(type_, '"unknown type_"')) для предоставления значения по умолчанию. В любом случае, чтобы все было читабельно, можно было бы скрыть ужасные детали:

switch = lambda type_: eval(message.get(type_, '"unknown type_"'))

return switch(type_)

Фрагменты кода можно даже предварительно скомпилировать для большей скорости:

from compiler import compile # deprecated since version 2.6: Removed in Python 3

for k in message:
    message[k] = compile(message[k], 'message case', 'eval')
0 голосов
/ 08 октября 2010

Ах, неважно, это объяснило. Я думал об элифе http://bytebaker.com/2008/11/03/switch-case-statement-in-python/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...