2016-04-05 20 views
13

Trong quá trình thực hiện thuật toán "Loại bỏ biến đổi" cho chương trình Bayes 'Nets, tôi đã gặp một lỗi không mong muốn là kết quả của việc chuyển đổi bản đồ lặp lại của một chuỗi các đối tượng.bản đồ so với danh sách; tại sao lại có hành vi khác nhau?

Để đơn giản, tôi sẽ sử dụng một mảnh tương tự như mã ở đây:

>>> nums = [1, 2, 3] 
>>> for x in [4, 5, 6]: 
...  # Uses n if x is odd, uses (n + 10) if x is even 
...  nums = map(
...   lambda n: n if x % 2 else n + 10, 
...   nums) 
... 
>>> list(nums) 
[31, 32, 33] 

này chắc chắn là kết quả sai. Vì [4, 5, 6] chứa hai số chẵn, nên thêm 10 vào mỗi phần tử nhiều nhất hai lần. Tôi đã nhận được hành vi bất ngờ với điều này trong thuật toán VE là tốt, vì vậy tôi sửa đổi nó để chuyển đổi các map iterator đến một list sau mỗi lần lặp.

>>> nums = [1, 2, 3] 
>>> for x in [4, 5, 6]: 
...  # Uses n if x is odd, uses (n + 10) if x is even 
...  nums = map(
...   lambda n: n if x % 2 else n + 10, 
...   nums) 
...  nums = list(nums) 
... 
>>> list(nums) 
[21, 22, 23] 

Từ sự hiểu biết của tôi về iterables, sửa đổi này không nên thay đổi bất cứ điều gì, nhưng nó làm. Rõ ràng, chuyển đổi n + 10 cho trường hợp not x % 2 được áp dụng ít hơn một lần trong phiên bản list.

Chương trình Bayes Nets của tôi hoạt động tốt sau khi tìm lỗi này, nhưng tôi đang tìm giải thích là tại sao nó xảy ra.

+4

Vui lòng không viết mã như thế. Nó làm cho bộ não của tôi bị tổn thương. – Kevin

+0

Mã của bạn không làm những gì bạn giải thích nó nên làm. Nếu bạn muốn giữ số nếu chúng là số lẻ và cộng mười nếu chúng là số chẵn, bạn phải đặt 'nums = map (lambda n: n nếu x% 2! = 0 else n + 10, nums)' ... cần phải có một cái gì đó để đánh giá cho hàm if, nếu không nó luôn luôn đánh giá là đúng. BTW. vấn đề với mã không phải là dòng mới, như các chỉnh sửa của bạn đề xuất. Tôi nghĩ, những gì kevin phàn nàn là các dấu chấm và mũi tên ở bên trái. –

+1

Có thể là trong Python3, 'map' là một trình lặp không? – hpaulj

Trả lời

12

Câu trả lời rất đơn giản: map là hàm lazy trong Python 3, nó trả về một đối tượng có thể lặp lại (trong Python 2 nó trả về list). Hãy để tôi thêm một số đầu ra để ví dụ của bạn:

In [6]: nums = [1, 2, 3] 

In [7]: for x in [4, 5, 6]: 
    ...:  nums = map(lambda n: n if x % 2 else n + 10, nums) 
    ...:  print(x) 
    ...:  print(nums) 
    ...:  
4 
<map object at 0x7ff5e5da6320> 
5 
<map object at 0x7ff5e5da63c8> 
6 
<map object at 0x7ff5e5da6400> 

In [8]: print(x) 
6 

In [9]: list(nums) 
Out[9]: [31, 32, 33] 

Lưu ý In[8] - giá trị của x là 6. Chúng tôi cũng có thể biến đổi các lambda chức năng, truyền cho map để theo dõi giá trị của x:

In [10]: nums = [1, 2, 3] 

In [11]: for x in [4, 5, 6]: 
    ....:  nums = map(lambda n: print(x) or (n if x % 2 else n + 10), nums) 
    ....:  

In [12]: list(nums) 
6 
6 
6 
6 
6 
6 
6 
6 
6 
Out[12]: [31, 32, 33] 

map lười, nó sẽ đánh giá khi list đang được gọi. Tuy nhiên, giá trị của x6 và đó là lý do tại sao nó tạo ra kết quả khó hiểu. Đánh giá nums bên trong vòng lặp sẽ tạo ra kết quả là được mong đợi.

In [13]: nums = [1, 2, 3] 

In [14]: for x in [4, 5, 6]: 
    ....:  nums = map(lambda n: print(x) or (n if x % 2 else n + 10), nums) 
    ....:  nums = list(nums) 
    ....:  
4 
4 
4 
5 
5 
5 
6 
6 
6 

In [15]: nums 
Out[15]: [21, 22, 23] 
+0

Ah! Đó là 'x'! Cảm ơn bạn. Tôi biết vòng lặp nói chung là "lười biếng", nhưng tôi đã bỏ lỡ một phần của sự lười biếng. Có cách nào để thiết lập một biểu thức lambda để ngay lập tức đánh giá một biến trong mỗi lần lặp của một vòng lặp? (chỉnh sửa: không bao giờ nhớ, đã nhận nó!) Điều này có vẻ là một nguy cơ pha trộn lập trình chức năng và bắt buộc! – cosmicFluke

+2

"nó đánh giá khi giá trị x đã được thay đổi thành 6". Thay vào đó, nó đánh giá khi 'list' được gọi trên đối tượng bản đồ, điều này xảy ra khi' x = 6'. – Evert

+0

@Evert, vâng, tôi đang sửa chữa, cảm ơn bạn – soon

3

Nếu bạn muốn sử dụng phiên bản lười, bạn cần sửa x trong mỗi vòng lặp. functools.partial thực hiện chính xác rằng:

from functools import partial 

def myfilter(n, x): 
    return n if x % 2 else n + 10 

nums = [1, 2, 3] 
for x in [4, 5, 6]: 
    f = partial(myfilter, x=x) 
    nums = map(f, nums) 

>>> list(nums) 
[21, 22, 23] 
+0

Đó là câu trả lời cho câu hỏi tiếp theo của tôi (trên câu trả lời đầu tiên). Cảm ơn! – cosmicFluke

+0

Làm thế nào là nó lười biếng nếu bạn gọi 'list' trên' map (f, nums) 'trong mỗi lần lặp của vòng lặp? – Evert

+0

Cũng có đối số mặc định là hack: 'lambda n, x = x: ...', vì các đối số mặc định được đánh giá trong thời gian tạo hàm (lambda). –

5

Vấn đề này đã làm với bao biến x được truy cập bởi các hàm lambda bạn đang tạo. Cách phạm vi của Python hoạt động, các hàm lambda sẽ luôn sử dụng phiên bản mới nhất của x từ phạm vi bên ngoài khi chúng được gọi, chứ không phải giá trị mà nó có khi chúng được xác định.

Kể từ map là lười biếng, các chức năng lambda không được gọi cho đến sau khi vòng lặp (khi bạn tiêu thụ lồng nhau map s bằng cách thông qua họ để list) và như vậy, họ đều sử dụng giá trị cuối cùng x.

Để thực hiện mỗi chức năng lambda lưu các giá trị x đã khi chúng được định nghĩa, thêm x=x như thế này:

lambda n, x=x: n if x % 2 else n + 10 

này quy định một đối số và giá trị mặc định của nó. Mặc định sẽ được đánh giá tại thời điểm lambda được định nghĩa, vì vậy khi lambda được gọi sau (không có đối số thứ hai), thì x bên trong biểu thức sẽ là giá trị mặc định đã lưu.

Các vấn đề liên quan