Pergunta

Quero fazer algumas correspondências de padrões em listas em Python.Por exemplo, em Haskell, posso fazer algo como o seguinte:

fun (head : rest) = ...

Então, quando passo uma lista, head será o primeiro elemento, e rest serão os elementos finais.

Da mesma forma, em Python, posso descompactar tuplas automaticamente:

(var1, var2) = func_that_returns_a_tuple()

Quero fazer algo semelhante com listas em Python.No momento, tenho uma função que retorna uma lista e um pedaço de código que faz o seguinte:

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

Eu me perguntei se poderia de alguma forma fazer isso em uma linha em Python, em vez de duas.

Foi útil?

Solução

Até onde eu sei, não há como torná-lo uma liner em python atual sem apresentar outra função, por exemplo:

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

No entanto, no Python 3.0, a sintaxe especializada usada para assinaturas de argumentos e descompacagem de argumentos variédica também estará disponível para esse tipo de descompactação geral de sequência, portanto, em 3.0 você poderá escrever:

head, *rest = my_func()

Ver PEP 3132 para detalhes.

Outras dicas

Em primeiro lugar, observe que a "correspondência de padrões" das linguagens funcionais e a atribuição às tuplas mencionadas não são tão semelhantes.Nas linguagens funcionais, os padrões são usados ​​para fornecer definições parciais de uma função.Então f (x : s) = e não significa pegar a cabeça e a cauda do argumento de f e retorno e usá-los, mas isso significa que se o argumento de f é da forma x : s (para alguns x e s), então f (x : s) é igual a e.

A atribuição de python é mais parecida com uma atribuição múltipla (suspeito que essa era sua intenção original).Então você escreve, por exemplo, x, y = y, x para trocar os valores em x e y sem precisar de uma variável temporária (como faria com uma simples instrução de atribuição).Isto tem pouco a ver com correspondência de padrões, pois é basicamente uma abreviação para a execução "simultânea" de x = y e y = x.Embora o python permita sequências arbitrárias em vez de listas separadas por vírgulas, eu não sugeriria chamar essa correspondência de padrões.Com a correspondência de padrões você verifica se algo corresponde ou não a um padrão;na tarefa python você deve garantir que as sequências em ambos os lados sejam iguais.

Para fazer o que você parece querer, você normalmente (também em linguagens funcionais) usaria uma função auxiliar (como mencionada por outros) ou algo semelhante a let ou where construções (que você pode considerar como uso de funções anônimas).Por exemplo:

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

Ou, em python real:

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

Observe que isso é essencialmente igual às soluções fornecidas por outros com uma função auxiliar, exceto que esta é a linha única que você queria.No entanto, não é necessariamente melhor do que uma função separada.

(Desculpe se minha resposta for um pouco exagerada.Só acho que é importante deixar clara a distinção.)

Essa é uma abordagem muito 'pura funcional' e, como tal, é um idioma sensato em Haskell, mas provavelmente não é tão apropriado para Python. Python só tem um conceito muito limitado de padrões Dessa maneira - e suspeito que você possa precisar de um sistema de tipo um pouco mais rígido para implementar esse tipo de construção (Erlang Buffs convidados a discordar aqui).

O que você tem é provavelmente o mais próximo que você chegaria a esse idioma, mas provavelmente é melhor usar uma compreensão ou abordagem imperativa da lista, em vez de chamar recursivamente uma função com a cauda da lista.

Como tem sido declarado em algumas ocasiões antes da, Python não é realmente uma linguagem funcional. Apenas empresta idéias do mundo do FP. Não é inerentemente Cauda recursiva Na maneira como você esperaria ver incorporado na arquitetura de uma linguagem funcional, para que você tenha alguma dificuldade em fazer esse tipo de operação recursiva em um grande conjunto de dados sem usar muito espaço de pilha.

A descompacagem prolongada foi introduzida em 3.0http://www.python.org/dev/peps/pep-3132/

Ao contrário de Haskell ou ML, o Python não possui correspondência de estruturas embutidas. A maneira mais pitônica de fazer a correspondência de padrões é com um bloco de tentativa de exceção:

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

Observe que isso funciona apenas com objetos com indexação de fatia. Além disso, se a função ficar complicada, algo no corpo depois a head, tail A linha pode aumentar o IndexError, o que levará a bugs sutis. No entanto, isso permite que você faça coisas como:

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

Em Python, a recursão de cauda geralmente é melhor implementada como um loop com um acumulador, ou seja:

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

Esta é a maneira óbvia e certa de fazê -lo 99% das vezes. Não é apenas mais claro de ler, é mais rápido e funcionará em outras listas (conjuntos, por exemplo). Se houver uma exceção esperando para acontecer lá, a função falhará com prazer e entregará a cadeia.

Eu estou trabalhando em pyfpm, uma biblioteca para correspondência de padrões em Python com uma sintaxe do tipo Scala. Você pode usá -lo para descompactar objetos como este:

from pyfpm import Unpacker

unpacker = Unpacker()

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

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

Ou no arglist de uma função:

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))

Bem, por que você quer isso em 1 linha em primeiro lugar?

Se você realmente quiser, sempre pode fazer um truque assim:

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 :)

Além das outras respostas, observe que a operação head/tail equivalente em Python, incluindo a extensão da sintaxe * do python3, geralmente será menos eficiente do que a correspondência de padrões de Haskell.

As listas Python são implementadas como vetores, portanto, para obter a cauda será necessário fazer uma cópia da lista.Isso é O(n) em relação ao tamanho da lista, enquanto uma implementação usando listas vinculadas como Haskell pode simplesmente usar o ponteiro final, uma operação O(1).

A única exceção pode ser a abordagem baseada em iteradores, onde a lista não é realmente retornada, mas um iterador é.No entanto, isto pode não ser aplicável a todos os locais onde uma lista é desejada (por exemplo,iterando várias vezes).

Por exemplo, Cifra abordagem, se modificado para retornar o iterador em vez de convertê-lo em uma tupla, terá esse comportamento.Alternativamente, um método mais simples de apenas 2 itens que não depende do bytecode seria:

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]

Obviamente, você ainda precisa incluir uma função utilitária em vez de haver um bom açúcar sintático para ela.

Houve um reciepe no livro de receitas do Python para fazer isso. Parece que não consigo encontrar agora, mas aqui está o código (eu modifiquei um pouco)


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

No entanto, você deve observar que isso só funciona ao usar uma tarefa descascada devido à maneira como ele inespecta o quadro anterior ... ainda é bastante útil.

Para o seu caso de uso específico - emulando Haskell's fun (head : rest) = ..., claro. As definições de função suportam o deserto de parâmetro há algum tempo:

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

A partir do Python 3.0, como @bpowah mencionado, Python também suporta a descompactação na tarefa:

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

Observe que o asterisco (o "splat") significa "o restante do iterável", não "até o fim". O seguinte funciona bem:

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 == []
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top