سؤال

أريد أن أفعل بعض المطابقة للأنماط في القوائم في بيثون. على سبيل المثال ، في هاسكل ، يمكنني أن أفعل شيئًا مثل ما يلي:

fun (head : rest) = ...

لذلك عندما أمر في قائمة ، head سيكون العنصر الأول ، و rest ستكون العناصر الزائدة.

وبالمثل ، في Python ، يمكنني تلقائيًا تفريغ tuples:

(var1, var2) = func_that_returns_a_tuple()

أريد أن أفعل شيئًا مشابهًا مع القوائم في بيثون. في الوقت الحالي ، لدي وظيفة تُرجع قائمة ، وجزء من التعليمات البرمجية التي تقوم بما يلي:

ls = my_func()
(head, rest) = (ls[0], ls[1:])

تساءلت عما إذا كان بإمكاني القيام بذلك بطريقة ما في سطر واحد في بيثون ، بدلاً من اثنين.

هل كانت مفيدة؟

المحلول

بقدر ما أعرف أنه لا توجد طريقة لجعلها خطوة واحدة في بيثون الحالي دون تقديم وظيفة أخرى ، على سبيل المثال:

split_list = lambda lst: (lst[0], lst[1:])
head, rest = split_list(my_func())

ومع ذلك ، في Python 3.0 ، سيتوفر بناء الجملة المتخصصة المستخدمة في توقيعات الوسيطة المتنوعة وتفريغ الوسيطة لهذا النوع من تفريغ التسلسل العام أيضًا ، لذلك في 3.0 ستتمكن من الكتابة:

head, *rest = my_func()

نرى PEP 3132 للتفاصيل.

نصائح أخرى

بادئ ذي بدء ، يرجى ملاحظة أن "مطابقة النمط" للغات الوظيفية والتعيين إلى tuples التي تذكرها ليست متشابهة حقًا. في اللغات الوظيفية ، يتم استخدام الأنماط لإعطاء تعريفات جزئية للوظيفة. لذا f (x : s) = e لا يعني أخذ رأس وذيل حجة f والعودة e استخدامها ، ولكن هذا يعني ذلك إذا حجة f من النموذج x : s (للبعض x و s), ومن بعد f (x : s) مساوي ل e.

إن تعيين Python أشبه بمهمة متعددة (أظن أن هذه كانت نيتها الأصلية). لذلك تكتب ، على سبيل المثال ، x, y = y, x لتبديل القيم في x و y دون الحاجة إلى متغير مؤقت (كما تفعل مع بيان تعيين بسيط). هذا ليس له علاقة بمطابقة الأنماط لأنه في الأساس اختصار للتنفيذ "المتزامن" x = y و y = x. على الرغم من أن Python يتيح تسلسلًا تعسفيًا بدلاً من القوائم المفصولة بفاصلة ، إلا أنني لا أقترح استدعاء هذا النمط. مع مطابقة النمط ، تحقق مما إذا كان هناك شيء ما يطابق نمطًا ؛ في مهمة Python ، يجب عليك التأكد من أن التسلسلات على كلا الجانبين هي نفسها.

للقيام بما يبدو أنك تريده عادة (أيضًا في اللغات الوظيفية) ، استخدم إما وظيفة مساعدة (كما ذكر الآخرين) أو شيء مشابه let أو where بنيات (والتي يمكنك اعتبارها باستخدام وظائف مجهولة). فمثلا:

(head, tail) = (x[0], x[1:]) where x = my_func()

أو في بيثون الفعلي:

(head, tail) = (lambda x: (x[0], x[1:]))(my_func())

لاحظ أن هذا هو نفس الحلول التي قدمها الآخرون مع وظيفة مساعدة باستثناء أن هذا هو الواحد الذي تريده. ومع ذلك ، فهي ليست بالضرورة أفضل من وظيفة منفصلة.

(آسف إذا كان إجابتي قليلاً فوق القمة. أعتقد أنه من المهم توضيح التمييز.)

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

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

كما كان معلن في مناسبات قليلة قبل, ، بيثون ليست في الواقع لغة وظيفية. إنه فقط يستعير الأفكار من عالم FP. إنه ليس بطبيعته ذيل العودية في الطريقة التي تتوقع أن ترى فيها مضمنة في بنية لغة وظيفية ، لذلك ستواجه بعض الصعوبة في القيام بهذا النوع من العمليات العودية على مجموعة بيانات كبيرة دون استخدام مساحة كبيرة.

تم تقديم التفريغ الممتد في 3.0http://www.python.org/dev/peps/pep-3132/

على عكس Haskell أو ML ، لا يحتوي Python على المطابقة المدمجة للهياكل. الطريقة الأكثر بيثونيا للقيام بمطابقة الأنماط هي مع كتلة المحاولة باستثناء:

def recursive_sum(x):
    try:
        head, tail = x[0], x[1:]
        return head + recursive-sum(tail)
    except IndexError:  # empty list: [][0] raises IndexError
        return 0

لاحظ أن هذا يعمل فقط مع كائنات مع فهرسة شريحة. أيضا ، إذا أصبحت الوظيفة معقدة ، شيء في الجسم بعد، بعدما ال head, tail قد يرفع الخط الفهرس ، مما سيؤدي إلى حشرات خفية. ومع ذلك ، هذا يسمح لك أن تفعل أشياء مثل:

for frob in eggs.frob_list:
    try:
        frob.spam += 1
    except AttributeError:
        eggs.no_spam_count += 1

في Python ، يتم تنفيذ عودة الذيل بشكل أفضل بشكل أفضل كحلقة مع تراكم ، أي:

def iterative_sum(x):
    ret_val = 0
    for i in x:
        ret_val += i
    return ret_val

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

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

from pyfpm import Unpacker

unpacker = Unpacker()

unpacker('head :: tail') << (1, 2, 3)

unpacker.head # 1
unpacker.tail # (2, 3)

أو في قائمة Arglist الخاصة بالوظيفة:

from pyfpm import match_args

@match_args('head :: tail')
def f(head, tail):
    return (head, tail)

f(1)          # (1, ())
f(1, 2, 3, 4) # (1, (2, 3, 4))

حسنًا ، لماذا تريدها في سطر واحد في المقام الأول؟

إذا كنت ترغب حقًا في ذلك ، يمكنك دائمًا القيام بخدعة مثل هذا:

def x(func):
  y = func()
  return y[0], y[1:]

# then, instead of calling my_func() call x(my_func)
(head, rest) = x(my_func) # that's one line :)

علاوة على الإجابات الأخرى ، لاحظ أن عملية الرأس / الذيل المكافئة في Python ، بما في ذلك تمديد Python3 لبناء * سيكون عمومًا أقل كفاءة من مطابقة نمط هاسكل.

يتم تنفيذ قوائم Python كمتجهات ، لذلك سيحتاج الحصول على الذيل إلى أخذ نسخة من القائمة. هذا هو o (n) wrt حجم القائمة ، في حين أن تطبيق التنفيذ باستخدام قوائم مرتبطة مثل Haskell يمكن فقط استخدام مؤشر الذيل ، عملية O (1).

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

على سبيل المثال، الشفرات النهج ، إذا تم تعديله لإرجاع التكرار بدلاً من تحويله إلى tuple ، فسوف يكون لهذا السلوك. بدلاً من ذلك ، فإن طريقة أبسط عن عنصر فقط لا تعتمد على رمز Bytecode ستكون:

def head_tail(lst):
    it = iter(list)
    yield it.next()
    yield it

>>> a, tail = head_tail([1,2,3,4,5])
>>> b, tail = head_tail(tail)
>>> a,b,tail
(1, 2, <listiterator object at 0x2b1c810>)
>>> list(tail)
[3, 4]

من الواضح أنه لا يزال يتعين عليك الالتفاف في وظيفة الأداة المساعدة بدلاً من أن يكون هناك سكر النحوي اللطيف لذلك.

كان هناك reciepe في كتاب طبخ بيثون للقيام بذلك. لا يمكنني العثور عليه الآن ولكن هنا هو الكود (قمت بتعديله قليلاً)


def peel(iterable,result=tuple):
    '''Removes the requested items from the iterable and stores the remaining in a tuple
    >>> x,y,z=peel('test')
    >>> print repr(x),repr(y),z
    't' 'e' ('s', 't')
    '''
    def how_many_unpacked():
        import inspect,opcode
        f = inspect.currentframe().f_back.f_back
        if ord(f.f_code.co_code[f.f_lasti])==opcode.opmap['UNPACK_SEQUENCE']:
            return ord(f.f_code.co_code[f.f_lasti+1])
        raise ValueError("Must be a generator on RHS of a multiple assignment!!")
    iterator=iter(iterable)
    hasItems=True
    amountToUnpack=how_many_unpacked()-1
    next=None
    for num in xrange(amountToUnpack):
        if hasItems:        
            try:
                next = iterator.next()
            except StopIteration:
                next = None
                hasItems = False
        yield next
    if hasItems:
        yield result(iterator)
    else:
        yield None

ومع ذلك ، يجب أن تلاحظ أن هذا يعمل فقط عند استخدام مهمة تفريغ بسبب الطريقة التي يحتفظ بها الإطار السابق ... لا يزال مفيدًا جدًا.

لحالة الاستخدام الخاصة بك - محاكاة هاسكل fun (head : rest) = ..., ، بالتأكيد. دعمت تعريفات الوظائف تفريغ المعلمة لبعض الوقت:

def my_method(head, *rest):
    # ...

اعتبارا من بيثون 3.0 ، مثل bpowah المشار, ، يدعم Python تفريغ المهمة أيضًا:

my_list = ['alpha', 'bravo', 'charlie', 'delta', 'echo']
head, *rest = my_list
assert head == 'alpha'
assert rest == ['bravo', 'charlie', 'delta', 'echo']

لاحظ أن العلامة النجمية ("splat") تعني "ما تبقى من Iserable" ، وليس "حتى النهاية". ما يلي يعمل بشكل جيد:

first, *middle, last = my_list
assert first == 'alpha'
assert last == 'echo'
assert middle == ['bravo', 'charlie', 'delta']

first, *middle, last = ['alpha', 'bravo']
assert first == 'alpha'
assert last == 'bravo'
assert middle == []
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top