2012-02-07 20 views
8

Tôi có một số vị từ, ví dụ:Pointfree chức năng kết hợp trong Python

is_divisible_by_13 = lambda i: i % 13 == 0 
is_palindrome = lambda x: str(x) == str(x)[::-1] 

và muốn một cách logic kết hợp chúng như trong:

filter(lambda x: is_divisible_by_13(x) and is_palindrome(x), range(1000,10000)) 

Câu hỏi đặt ra hiện nay: Có thể kết hợp như vậy được viết bằng một pointfree phong cách, chẳng hạn như:

filter(is_divisible_by_13 and is_palindrome, range(1000,10000)) 

Điều này tất nhiên không có hiệu quả mong muốn vì giá trị chân lý của hàm lambda là Trueandor là các toán tử ngắn mạch. Điều gần nhất tôi đưa ra là định nghĩa một lớp P là một thùng chứa vị ngữ đơn giản triển khai __call__() và có các phương thức and_()or_() để kết hợp các biến vị ngữ. Định nghĩa của P là như sau:

import copy 

class P(object): 
    def __init__(self, predicate): 
     self.pred = predicate 

    def __call__(self, obj): 
     return self.pred(obj) 

    def __copy_pred(self): 
     return copy.copy(self.pred) 

    def and_(self, predicate): 
     pred = self.__copy_pred() 
     self.pred = lambda x: pred(x) and predicate(x) 
     return self 

    def or_(self, predicate): 
     pred = self.__copy_pred() 
     self.pred = lambda x: pred(x) or predicate(x) 
     return self 

Với P bây giờ tôi có thể tạo ra một vị mới mà là sự kết hợp của các vị từ như thế này:

P(is_divisible_by_13).and_(is_palindrome) 

tương đương với chức năng lambda trên. Điều này đến gần hơn với những gì tôi muốn có, nhưng nó cũng không phải là pointfree (các điểm bây giờ là các biến vị ngữ thay vì các đối số của chúng). Bây giờ câu hỏi thứ hai là: Có cách nào tốt hơn hoặc ngắn hơn (có thể không có dấu ngoặc đơn và dấu chấm) để kết hợp các biến vị ngữ trong Python hơn là sử dụng các lớp như P và không sử dụng hàm lambda?

+0

Có vẻ như bạn đang cố gắng hết sức để làm cho một ngôn ngữ không hoạt động giống như một ngôn ngữ chức năng. Bạn có liên kết với Python không? –

+0

@Eric: Có, loại. Mã Python của tôi được nhúng trong một dự án C++ và tôi không thể chuyển sang ngôn ngữ khác. –

Trả lời

8

Bạn có thể ghi đè toán tử & (bitwise AND) bằng Python bằng cách thêm phương thức __and__ vào lớp P. Sau đó bạn có thể viết một cái gì đó như:

P(is_divisible_by_13) & P(is_palindrome) 

hoặc thậm chí

P(is_divisible_by_13) & is_palindrome 

Tương tự như vậy, bạn có thể ghi đè lên (bitwise OR) điều hành | bằng cách thêm một phương pháp __or__~ (Bitwise phủ định) điều hành bằng cách thêm phương pháp __not__. Lưu ý rằng bạn không thể ghi đè lên nhà cung cấp có sẵn and, ornot, vì vậy điều này có thể gần với mục tiêu của bạn nhất có thể. Bạn vẫn cần có một đối tượng P làm đối số ngoài cùng bên trái.

Đối với lợi ích của sự hoàn chỉnh, bạn cũng có thể ghi đè lên các biến thể tại chỗ (__iand__, __ior__) và các biến thể bên cánh phải, (__rand__, __ror__) của các nhà khai thác.

Mã ví dụ (chưa được kiểm tra, cảm thấy tự do để sửa chữa):

class P(object): 
    def __init__(self, predicate): 
     self.pred = predicate 

    def __call__(self, obj): 
     return self.pred(obj) 

    def __copy_pred(self): 
     return copy.copy(self.pred) 

    def __and__(self, predicate): 
     def func(obj): 
      return self.pred(obj) and predicate(obj) 
     return P(func) 

    def __or__(self, predicate): 
     def func(obj): 
      return self.pred(obj) or predicate(obj) 
     return P(func) 

Thêm một trick để mang lại cho bạn gần gũi hơn với chỉ miễn niết bàn là trang trí sau:

from functools import update_wrapper 

def predicate(func): 
    """Decorator that constructs a predicate (``P``) instance from 
    the given function.""" 
    result = P(func) 
    update_wrapper(result, func) 
    return result 

Sau đó bạn có thể tag các biến vị ngữ của bạn với trình trang trí predicate để biến chúng thành một phiên bản P tự động:

@predicate 
def is_divisible_by_13(number): 
    return number % 13 == 0 

@predicate 
def is_palindrome(number): 
    return str(number) == str(number)[::-1] 

>>> pred = (is_divisible_by_13 & is_palindrome) 
>>> print [x for x in xrange(1, 1000) if pred(x)] 
[494, 585, 676, 767, 858, 949] 
+0

Tôi không chắc liệu tôi có muốn quá tải các toán tử _bitwise_ với một ý nghĩa khác hay không, nhưng tôi thấy ý tưởng trang trí rất thú vị. Cảm ơn! –

+0

@ FrankS.Thomas: Các hoạt động bitwise có ý nghĩa gì với các biến vị ngữ của bạn không? Nếu không, không có gì sai với quá tải chúng. –

3

Về cơ bản, cách tiếp cận của bạn có vẻ là phương pháp khả thi duy nhất trong Python. Có một số python module on github sử dụng cùng một cơ chế để triển khai thành phần hàm không có điểm.

Tôi đã không sử dụng nó, nhưng ngay từ cái nhìn đầu tiên, giải pháp của ông trông đẹp hơn một chút (vì ông sử dụng trang trí và quá tải nhà điều hành nơi bạn sử dụng một lớp và __call__).

Nhưng khác với mã không có điểm kỹ thuật, nó chỉ là "ẩn" nếu bạn muốn. Mà có thể hoặc có thể không đủ cho bạn.

2

Bạn có thể sử dụng Infix operator recipe:

AND = Infix(lambda f, g: (lambda x: f(x) and g(x))) 
for n in filter(is_divisible_by_13 |AND| is_palindrome, range(1000,10000)): 
    print(n) 

mang

1001 
2002 
3003 
4004 
5005 
6006 
7007 
8008 
9009 
+0

Đây có lẽ là Hack Python của năm cho năm 2012. Ít nhất là đối với tôi. –

+0

@ Tamás: Công thức đã là bản hack tốt nhất của năm 2005 :-) Ngoài ra, đó là bởi Ferdinand Jamitzky, không có tôi. Tôi chỉ (abu?) Sử dụng nó cho câu trả lời của tôi. – WolframH

1

Đó sẽ là giải pháp của tôi:

class Chainable(object): 

    def __init__(self, function): 
     self.function = function 

    def __call__(self, *args, **kwargs): 
     return self.function(*args, **kwargs) 

    def __and__(self, other): 
     return Chainable(lambda *args, **kwargs: 
           self.function(*args, **kwargs) 
           and other(*args, **kwargs)) 

    def __or__(self, other): 
     return Chainable(lambda *args, **kwargs: 
           self.function(*args, **kwargs) 
           or other(*args, **kwargs)) 

def is_divisible_by_13(x): 
    return x % 13 == 0 

def is_palindrome(x): 
    return str(x) == str(x)[::-1] 

filtered = filter(Chainable(is_divisible_by_13) & is_palindrome, 
        range(0, 100000)) 

i = 0 
for e in filtered: 
    print str(e).rjust(7), 
    if i % 10 == 9: 
     print 
    i += 1 

Và đây là kết quả của tôi:

0  494  585  676  767  858  949 1001 2002 3003 
4004 5005 6006 7007 8008 9009 10101 11011 15951 16861 
17771 18681 19591 20202 21112 22022 26962 27872 28782 29692 
30303 31213 32123 33033 37973 38883 39793 40404 41314 42224 
43134 44044 48984 49894 50505 51415 52325 53235 54145 55055 
59995 60606 61516 62426 63336 64246 65156 66066 70707 71617 
72527 73437 74347 75257 76167 77077 80808 81718 82628 83538 
84448 85358 86268 87178 88088 90909 91819 92729 93639 94549 
95459 96369 97279 98189 99099 
1

Python đã có cách kết hợp hai hàm: lambda. Bạn có thể dễ dàng tạo các chức năng soạn thư và soạn thư của riêng mình:

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

filter(compose(is_divisible_by_13, is_palindrome, xrange(1000))) 
+0

nó phải là 'reduce (compose2, ff)', hoặc chỉ 'reduce (lambda f, g: lambda x: f (g (x)), ff)' – modular