2013-05-24 39 views
10

Tôi có một loạt hàm và tôi đang cố gắng tạo một hàm bao gồm thành phần của các phần tử trong mảng của tôi. Cách tiếp cận của tôi là:Chức năng soạn thảo trong python

def compose(list): 
    if len(list) == 1: 
     return lambda x:list[0](x) 
    list.reverse() 
    final=lambda x:x 
    for f in list: 
     final=lambda x:f(final(x)) 
    return final 

Phương pháp này dường như không hoạt động, trợ giúp sẽ được đánh giá cao.

(Tôi đang đảo ngược danh sách vì đây là thứ tự của phần tôi muốn các chức năng được)

Trả lời

5

Nó không làm việc bởi vì tất cả các chức năng ẩn danh mà bạn tạo trong vòng tham khảo các biến vòng lặp cùng và do đó chia sẻ giá trị cuối cùng của nó.

Là một sửa chữa nhanh chóng, bạn có thể thay thế việc chuyển nhượng với:

final = lambda x, f=f, final=final: f(final(x)) 

Hoặc, bạn có thể trả lại lambda từ một hàm:

def wrap(accum, f): 
    return lambda x: f(accum(x)) 
... 
final = wrap(final, f) 

Để hiểu những gì đang xảy ra, hãy thử thí nghiệm này :

>>> l = [lambda: n for n in xrange(10)] 
>>> [f() for f in l] 
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9] 

Kết quả này khiến nhiều người cho rằng kết quả là [0, 1, 2, ...]. Tuy nhiên, tất cả các điểm lambdas đều có cùng biến số n và tất cả đều tham chiếu đến giá trị cuối cùng của nó, đó là 9. Trong trường hợp của bạn, tất cả các phiên bản của final được cho là kết thúc để chỉ cùng một số f và thậm chí tệ hơn, đến cùng một số final.

Chủ đề của lambdas và cho các vòng trong Python là already covered on SO.

+0

Cảm ơn câu trả lời, nó thực sự làm việc cho tôi. Tôi đã sử dụng phương pháp thứ hai. Bạn có thể giải thích những gì bạn có nghĩa là "đóng cửa cuối cùng tham khảo cùng một tế bào f", và cũng có thể bạn hãy giải thích phương pháp đầu tiên. – Starless

+0

@Không sao tôi đã cập nhật câu trả lời với lời giải thích dài hơn. – user4815162342

12
def compose (*functions): 
    def inner(arg): 
     for f in reversed(functions): 
      arg = f(arg) 
     return arg 
    return inner 

Ví dụ: Câu trả lời

>>> def square (x): 
     return x ** 2 
>>> def increment (x): 
     return x + 1 
>>> def half (x): 
     return x/2 

>>> composed = compose(square, increment, half) # square(increment(half(x))) 
>>> composed(5) # square(increment(half(5))) = square(increment(2.5)) = square(3.5) = 12,25 
12.25 
2

Poke là tốt, nhưng bạn cũng có thể sử dụng gói functional mà đi kèm với một phương pháp soạn.

+0

Tôi đang xem xét việc triển khai một bản thân mình – Starless

+0

@ Không sao Nó có nhiều công thức nấu ăn. Giống như bất kỳ nhà văn, bạn phải đọc để cải thiện những gì bạn viết. – Marcin

+0

FWIK, functional.compose chỉ hỗ trợ hai đối số. – georg

2

Bạn cũng có thể tạo ra một loạt các chức năng và sử dụng giảm:

def f1(x): return x+1 
def f2(x): return x+2 
def f3(x): return x+3 

x = 5 

# Will print f3(f2(f1(x))) 
print reduce(lambda acc, x: x(acc), [f1, f2, f3], x) 

# As a function: 
def compose(*funcs): 
    return lambda x: reduce(lambda acc, f: f(acc), funcs, x) 

f = compose(f1, f2, f3) 
17

Cách tiếp cận dễ nhất sẽ là người đầu tiên viết một thành phần của 2 chức năng:

def compose2(f, g): 
    return lambda *a, **kw: f(g(*a, **kw)) 

Và sau đó sử dụng reduce để soạn nhiều chức năng hơn:

def compose(*fs): 
    return reduce(compose2, fs) 

Hoặc bạn có thể sử dụng some library, đã chứa hàm compose.

+0

Câu trả lời hay - bao gồm một ví dụ đơn giản, cộng với tham chiếu đến một lib. Tôi cũng muốn đề xuất [jaraco.functools] (http://pythonhosted.org/jaraco.functools/#jaraco.functools.compose) làm một thư viện khác để xem xét. –

7

thực hiện đệ quy

Dưới đây là một việc thực hiện đệ quy, mà tôi đã chưa nhìn thấy:

def compose(*funcs): 
    def inner(data, funcs=funcs): 
     return inner(funcs[-1](data), funcs[:-1]) if funcs else data 
    return inner 

Tôi không mong đợi nó sẽ rất performant, tuy nhiên, vì nó làm cho một tuple mới đối số mỗi cuộc gọi đệ quy.

So sánh tất cả các gợi ý:

Hãy kiểm tra một số trong những triển khai và xác định đó là performant nhất, lần đầu tiên một số chức năng đối số duy nhất (Cảm ơn bạn poke):

def square (x): 
    return x ** 2 

def increment (x): 
    return x + 1 

def half (x): 
    return x/2 

Đây là triển khai của chúng tôi, tôi nghi ngờ phiên bản lặp lại là phiên bản thứ hai hiệu quả nhất (soạn thảo thủ công sẽ tự nhiên là nhanh nhất).

from functools import reduce 

def recursive_compose(*funcs): 
    def inner(data, funcs=funcs): 
     return inner(funcs[-1](data), funcs[:-1]) if funcs else data 
    return inner 

def iterative_compose(*functions): 
    def inner(arg): 
     for f in reversed(functions): 
      arg = f(arg) 
     return arg 
    return inner 

def _compose2(f, g): 
    return lambda *a, **kw: f(g(*a, **kw)) 

def reduce_compose1(*fs): 
    return reduce(_compose2, fs) 

def reduce_compose2(*funcs): 
    """bug fixed - added reversed()""" 
    return lambda x: reduce(lambda acc, f: f(acc), reversed(funcs), x) 

Và để kiểm tra sau đây:

import timeit 
composes = (recursive_compose, iterative_compose, 
      reduce_compose1, reduce_compose2) 

def manual(): 
    return square(increment(half(5))) 

print('manual compose', min(timeit.repeat(manual)), manual()) 

for compose in composes: 
    fn = lambda: compose(square, increment, half)(5) 
    result = min(timeit.repeat(fn)) 
    print(compose.__name__, result, fn()) 

Kết quả

Và chúng tôi nhận được kết quả như sau (cùng độ lớn và tỷ lệ bằng Python 2 và 3):

manual compose 0.607658714056015 12.25 
recursive_compose 1.929560380987823 12.25 
iterative_compose 1.3319460819475353 12.25 
reduce_compose1 2.0850532418116927 12.25 
reduce_compose2 1.5899418010376394 12.25 

Và kỳ vọng của tôi đã được xác nhận: nhanh nhất là tất nhiên, thành phần chức năng thủ công theo sau là việc thực hiện lặp lại. Phiên bản đệ quy chậm hơn nhiều - có khả năng vì khung ngăn xếp mới được tạo bởi mỗi cuộc gọi hàm và một bộ hàm mới được tạo cho mỗi hàm.

2

Một lót:

compose = lambda *F: reduce(lambda f, g: lambda x: f(g(x)), F) 

sử dụng Ví dụ:

f1 = lambda x: x+3 
f2 = lambda x: x*2 
f3 = lambda x: x-1 
g = compose(f1, f2, f3) 
assert(g(7) == 15) 
0

Đây là phiên bản của tôi

def compose(*fargs): 
    def inner(arg): 
     if not arg: 
      raise ValueError("Invalid argument") 
     if not all([callable(f) for f in fargs]): 
      raise TypeError("Function is not callable") 
     return reduce(lambda arg, func: func(arg), fargs, arg) 
    return inner 

Một ví dụ về cách nó được sử dụng

def calcMean(iterable): 
    return sum(iterable)/len(iterable) 


def formatMean(mean): 
    return round(float(mean), 2) 


def adder(val, value): 
    return val + value 


def isEven(val): 
    return val % 2 == 0 

if __name__ == '__main__': 
    # Ex1 

    rand_range = [random.randint(0, 10000) for x in range(0, 10000)] 

    isRandIntEven = compose(calcMean, formatMean, 
          partial(adder, value=0), math.floor.__call__, isEven) 

    print(isRandIntEven(rand_range)) 
Các vấn đề liên quan