python - لغة - مكتبة اكواد بايثون




بدائل لبيان التبديل في بايثون؟ (20)

أرغب في كتابة دالة في Python تقوم بإرجاع قيم ثابتة مختلفة استنادًا إلى قيمة فهرس الإدخال.

في اللغات الأخرى ، يمكنني استخدام عبارة switch أو case ، لكن لا يبدو أن Python تحتوي على عبارة switch . ما هي حلول بايثون الموصى بها في هذا السيناريو؟


أعتقد أن أفضل طريقة هي استخدام تعابير لغة python للحفاظ على شفرتك قابلة للاختبار . كما هو موضح في الإجابات السابقة ، أستخدم القواميس للاستفادة من هياكل الثعبان واللغة والحفاظ على كود "الحالة" معزولًا بطرق مختلفة. يوجد أدناه فصل دراسي ، ولكن يمكنك استخدام الوحدة ووحدة العرض والوظائف مباشرة. يحتوي الفصل على طرق يمكن اختبارها بالعزلة . اعتمادًا على احتياجاتك ، يمكنك اللعب باستخدام أساليب وسمات ثابتة أيضًا.

class ChoiceManager:

    def __init__(self):
        self.__choice_table = \
        {
            "CHOICE1" : self.my_func1,
            "CHOICE2" : self.my_func2,
        }

    def my_func1(self, data):
        pass

    def my_func2(self, data):
        pass

    def process(self, case, data):
        return self.__choice_table[case](data)

ChoiceManager().process("CHOICE1", my_data)

من الممكن الاستفادة من هذه الطريقة باستخدام الفئات كمفاتيح "__choice_table". وبهذه الطريقة يمكنك تجنب الاعتداء isinstance والحفاظ على جميع نظيفة واختبارها .

لنفترض أنه عليك معالجة الكثير من الرسائل أو الحزم من الشبكة أو من MQ الخاص بك. كل حزمة لها هيكلها الخاص ورمز الإدارة الخاصة بها (بطريقة عامة). مع التعليمات البرمجية المذكورة أعلاه ، يمكن القيام بشيء كالتالي:

class PacketManager:

    def __init__(self):
        self.__choice_table = \
        {
            ControlMessage : self.my_func1,
            DiagnosticMessage : self.my_func2,
        }

    def my_func1(self, data):
        # process the control message here
        pass

    def my_func2(self, data):
        # process the diagnostic message here
        pass

    def process(self, pkt):
        return self.__choice_table[pkt.__class__](pkt)

pkt = GetMyPacketFromNet()
PacketManager().process(pkt)


# isolated test or isolated usage example
def test_control_packet():
    p = ControlMessage()
    PacketManager().my_func1(p)

لا ينتشر ذلك التعقيد في تدفق التعليمات البرمجية ولكن يتم تقديمه في بنية التعليمات البرمجية .


أعجبتني إجابة مارك بيز

بما أنه يجب استخدام متغير x مرتين ، فقد قمت بتعديل وظائف lambda إلى بدون معلمات.

لا بد لي من العمل مع results[value](value)

In [2]: result = {
    ...:   'a': lambda x: 'A',
    ...:   'b': lambda x: 'B',
    ...:   'c': lambda x: 'C'
    ...: }
    ...: result['a']('a')
    ...: 
Out[2]: 'A'

In [3]: result = {
    ...:   'a': lambda : 'A',
    ...:   'b': lambda : 'B',
    ...:   'c': lambda : 'C',
    ...:   None: lambda : 'Nothing else matters'

    ...: }
    ...: result['a']()
    ...: 
Out[3]: 'A'

تحرير: لاحظت أنه يمكنني استخدام نوع بلا مع القواميس. لذلك هذا من شأنه أن يحاكي switch ; case else switch ; case else


إذا كنت تبحث عن بيان إضافي ، مثل "switch" ، فقد بنيت وحدة python التي تمد بايثون. يطلق عليه ESPY كـ "بنية محسنة لـ Python" وهو متوفر لكل من Python 2.x و Python 3.x.

على سبيل المثال ، في هذه الحالة ، يمكن تنفيذ عبارة switch بالشفرة التالية:

macro switch(arg1):
    while True:
        cont=False
        val=%arg1%
        socket case(arg2):
            if val==%arg2% or cont:
                cont=True
                socket
        socket else:
            socket
        break

التي يمكن استخدامها على هذا النحو:

a=3
switch(a):
    case(0):
        print("Zero")
    case(1):
        print("Smaller than 2"):
        break
    else:
        print ("greater than 1")

حتى إسبى ترجمها في بايثون على النحو التالي:

a=3
while True:
    cont=False
    if a==0 or cont:
        cont=True
        print ("Zero")
    if a==1 or cont:
        cont=True
        print ("Smaller than 2")
        break
    print ("greater than 1")
    break

إذا كنت تريد الإعدادات الافتراضية ، فيمكنك استخدام القاموس get(key[, default]) :

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x not found

الحلول التي أستخدمها:

مجموعة من 2 من الحلول المنشورة هنا ، والتي يسهل قراءتها وتدعم الافتراضات.

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)

أين

.get('c', lambda x: x - 22)(23)

يبحث عن "lambda x: x - 2" في الأمر ويستخدمها مع x=23

.get('xxx', lambda x: x - 22)(44)

لا يجدها في الأمر ويستخدم الافتراضي "lambda x: x - 22" مع x=44 .


بالإضافة إلى أساليب القاموس (التي أحبها حقاً ، BTW) ، يمكنك أيضًا استخدام if-elif-else للحصول على وظيفة التبديل / الحالة / الافتراضية:

if x == 'a':
    # Do the thing
elif x == 'b':
    # Do the other thing
if x in 'bc':
    # Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
    # Do yet another thing
else:
    # Do the default

هذا بالطبع ليس مطابقًا للتبديل / الحالة - لا يمكن أن تسقط بسهولة مثل الخروج من الاستراحة ؛ بيان ، ولكن يمكنك الحصول على اختبار أكثر تعقيدًا. تنسيقه هو أجمل من سلسلة من ifs المتداخلة ، على الرغم من وظيفيا هذا ما هو أقرب إلى.


تعريف:

def switch1(value, options):
  if value in options:
    options[value]()

يسمح لك باستخدام صيغة واضحة إلى حد ما ، مع الحالات المجمعة في الخريطة:

def sample1(x):
  local = 'betty'
  switch1(x, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye," + local),
      print("!")),
    })

ظللت أحاول إعادة تعريف التبديل بطريقة تسمح لي بالتخلص من "lambda:" ، لكنني استسلمت. التغيير والتبديل في التعريف:

def switch(value, *maps):
  options = {}
  for m in maps:
    options.update(m)
  if value in options:
    options[value]()
  elif None in options:
    options[None]()

سمح لي بتعيين حالات متعددة لنفس الشفرة ولتوفير خيار افتراضي:

def sample(x):
  switch(x, {
    _: lambda: print("other") 
    for _ in 'cdef'
    }, {
    'a': lambda: print("hello"),
    'b': lambda: (
      print("goodbye,"),
      print("!")),
    None: lambda: print("I dunno")
    })

يجب أن تكون كل حالة مكررة في قاموسها الخاص ؛ يدمج رمز التبديل () القواميس قبل البحث عن القيمة. إنه لا يزال أقبح مما أريد ، ولكن لديه الكفاءة الأساسية لاستخدام البحث المجزأ في التعبير ، بدلاً من حلقة من خلال جميع المفاتيح.


سأقوم بإسقاط سنتي هنا السبب في عدم وجود بيان حالة / تبديل في بايثون هو أن بايثون تتبع مبدأ "ثيريس فقط بطريقة صحيحة واحدة للقيام بشيء ما". لذا من الواضح أنه يمكنك طرح طرق مختلفة لإعادة تشغيل وظيفة التبديل / الحالة ، ولكن الطريقة البيثونية لتحقيق ذلك هي الإنشاء / if. أي

if something:
    return "first thing"
elif somethingelse:
    return "second thing"
elif yetanotherthing:
    return "third thing"
else:
    return "default thing"

لقد شعرت أن PEP 8 تستحق إيماءة هنا. واحدة من الأشياء الجميلة عن بايثون هي بساطتها وأناقتها. ويستمد هذا إلى حد كبير من المبادئ المنصوص عليها في PEP 8 ، بما في ذلك "هناك طريقة واحدة صحيحة لفعل شيء ما"


لقد قدمت (نسبيا) حل مرن وقابل للاستخدام من جديد. يمكن العثور عليها في جيثب كجست هذه . إذا كانت نتيجة وظيفة التبديل قابلة للاستدعاء ، يتم الاتصال بها تلقائيًا.


لقد وجدت أن بنية تبديل مشتركة:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

يمكن التعبير عنها في بايثون على النحو التالي:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

أو تنسيقها بطريقة أوضح:

(lambda x:
     v1 if p1(x) else
     v2 if p2(x) else
     v3)

بدلاً من أن تكون عبارة عن بيان ، فإن إصدار python هو تعبير ، يتم تقييمه إلى قيمة.


لنفترض أنك لا تريد فقط إرجاع قيمة ، ولكنك ترغب في استخدام الطرق التي تغير شيئًا ما على كائن ما. باستخدام النهج المذكور هنا سيكون:

result = {
  'a': obj.increment(x),
  'b': obj.decrement(x)
}.get(value, obj.default(x))

ما يحدث هنا هو أن python يقيم جميع الطرق في القاموس. لذا حتى إذا كانت القيمة "a" ، فسيتم زيادة الكائن وتناقصه بمقدار x.

حل:

func, args = {
  'a' : (obj.increment, (x,)),
  'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))

result = func(*args)

حتى تحصل على قائمة تحتوي على وظيفة ووسائطها. بهذه الطريقة ، يتم فقط إرجاع مؤشر الدالة وقائمة الوسيطات ، ولا يتم تقييمهما. "النتيجة" ثم تقييم استدعاء الدالة التي تم إرجاعها.


هناك نمط تعلمته من كود Twisted Python.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

يمكنك استخدامه في أي وقت تحتاج إلى إرساله على رمز مميز وتنفيذ جزء من التعليمات البرمجية الممتدة. في جهاز الدولة ، سيكون لديك وسائل state_ و ترسل على self.state . يمكن تمديد do_ عن طريق do_ من الفئة الأساسية وتحديد أساليب do_ الخاصة بك. في كثير من الأحيان لن يكون لديك حتى أساليب do_ في الصف الأساسي.

تحرير: كيف يتم ذلك بالضبط

في حالة SMTP ، ستستلم HELO من السلك. الشفرة ذات الصلة (من twisted/mail/smtp.py ، المعدلة twisted/mail/smtp.py ) تبدو كالتالي

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

سوف تتلقى ' HELO foo.bar.com ' (أو قد تحصل على 'QUIT' أو 'RCPT TO: foo' ). هذا هو tokenized إلى parts مثل ['HELO', 'foo.bar.com'] . يتم أخذ اسم البحث عن الطريقة الفعلية من parts[0] .

(تسمى الطريقة الأصلية أيضًا باسم state_COMMAND ، لأنها تستخدم نفس النمط لتطبيق جهاز الحالة ، أي getattr(self, 'state_' + self.mode) )


يمكنك استخدام القاموس:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]

Expanding on Greg Hewgill's answer - We can encapsulate the dictionary-solution using a decorator:

def case(callable):
    """switch-case decorator"""
    class case_class(object):
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

        def do_call(self):
            return callable(*self.args, **self.kwargs)

return case_class

def switch(key, cases, default=None):
    """switch-statement"""
    ret = None
    try:
        ret = case[key].do_call()
    except KeyError:
        if default:
            ret = default.do_call()
    finally:
        return ret

This can then be used with the @case -decorator

@case
def case_1(arg1):
    print 'case_1: ', arg1

@case
def case_2(arg1, arg2):
    print 'case_2'
    return arg1, arg2

@case
def default_case(arg1, arg2, arg3):
    print 'default_case: ', arg1, arg2, arg3

ret = switch(somearg, {
    1: case_1('somestring'),
    2: case_2(13, 42)
}, default_case(123, 'astring', 3.14))

print ret

والخبر السار هو أن هذا قد تم بالفعل في NeoPySwitch -module. ببساطة تثبيت باستخدام pip:

pip install NeoPySwitch

I would just use if/elif/else statements. I think that it's good enough to replace the switch statement.


If you don't worry losing syntax highlight inside the case suites, you can do the following:

exec {
    1: """
print ('one')
""", 
    2: """
print ('two')
""", 
    3: """
print ('three')
""",
}.get(value, """
print ('None')
""")

Where value is the value. In C, this would be:

switch (value) {
    case 1:
        printf("one");
        break;
    case 2:
        printf("two");
        break;
    case 3:
        printf("three");
        break;
    default:
        printf("None");
        break;
}

We can also create a helper function to do this:

def switch(value, cases, default):
    exec cases.get(value, default)

So we can use it like this for the example with one, two and three:

switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")

Just mapping some a key to some code is not really and issue as most people have shown using the dict. The real trick is trying to emulate the whole drop through and break thing. I don't think I've ever written a case statement where I used that "feature". Here's a go at drop through.

def case(list): reduce(lambda b, f: (b | f[0], {False:(lambda:None),True:f[1]}[b | f[0]]())[0], list, False)

case([
    (False, lambda:print(5)),
    (True, lambda:print(4))
])

I was really imagining it as a single statement. I hope you'll pardon the silly formatting.

reduce(
    initializer=False,
    function=(lambda b, f:
        ( b | f[0]
        , { False: (lambda:None)
          , True : f[1]
          }[b | f[0]]()
        )[0]
    ),
    iterable=[
        (False, lambda:print(5)),
        (True, lambda:print(4))
    ]
)

I hope that's valid python. It should give you drop through. of course the boolean checks could be expressions and if you wanted them to be evaluated lazily you could wrap them all in a lambda. I wouldn't be to hard to make it accept after executing some of the items in the list either. Just make the tuple (bool, bool, function) where the second bool indicates whether or not to break or drop through.



class Switch:
    def __init__(self, value): self._val = value
    def __enter__(self): return self
    def __exit__(self, type, value, traceback): return False # Allows traceback to occur
    def __call__(self, *mconds): return self._val in mconds

from datetime import datetime
with Switch(datetime.today().weekday()) as case:
    if case(0):
        # Basic usage of switch
        print("I hate mondays so much.")
        # Note there is no break needed here
    elif case(1,2):
        # This switch also supports multiple conditions (in one line)
        print("When is the weekend going to be here?")
    elif case(3,4): print("The weekend is near.")
    else:
        # Default would occur here
        print("Let's go have fun!") # Didn't use case for example purposes

class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))

الاستعمال:

while switch(n):
    if case(0):
        print "You typed zero."
        break
    if case(1, 4, 9):
        print "n is a perfect square."
        break
    if case(2):
        print "n is an even number."
    if case(2, 3, 5, 7):
        print "n is a prime number."
        break
    if case(6, 8):
        print "n is an even number."
        break
    print "Only single-digit numbers are allowed."
    break

الاختبارات:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.




python