Lập trình hàm đã là một chủ đề được quan tâm trong lĩnh vực phát triển trong nhiều năm và rất nhiều ngôn ngữ đã bao gồm các cách áp dụng một số nguyên tắc của nó, bao gồm cả Python. Nhưng lập trình hàm là gì? . Để có thể sử dụng đầy đủ mô hình này, các nguyên tắc và yếu tố nhất định cần được hỗ trợ bởi ngôn ngữ. Một số nguyên tắc chính có trong bất kỳ ngôn ngữ nào là hàm thuần túy, tính bất biến, hàm bậc cao, ứng dụng một phần, đánh giá lười biếng, khớp mẫu và kiểm tra kiểu. Tất cả điều này được hỗ trợ trong Python, nhưng bài viết này sẽ tập trung vào ba điều đầu tiên được đề cập. Việc sử dụng mô hình này thoạt nghe có vẻ hơi khó hiểu, nhưng nó có một số ưu điểm giúp mã sạch hơn, mạnh mẽ hơn và dễ bảo trì hơn
chức năng thuần túy
Một chức năng có thể được coi là một hoạt động với một số loại đầu vào, sẽ trả về một đầu ra. Các hàm thuần túy là những hàm mà cùng một đầu vào sẽ luôn trả về cùng một đầu ra. Chúng có thể dự đoán được và do đó dễ làm việc với
Tất nhiên, trong Python, việc tạo các hàm thuần túy là có thể. Xem xét ví dụ sau
def powerOfTwo[x]:
return x**2
Hàm này thuần túy, bất kể nó được thực hiện bao nhiêu lần với cùng một đầu vào cho
y = 3
def powerOfTwo[]:
return y**2
2, kết quả sẽ giống nhau. Nếu chức năng được thay đổi thànhy = 3
def powerOfTwo[]:
return y**2
Hàm đã trở nên không trong sạch, vì nó đang sử dụng các giá trị bên ngoài cho chính nó. Vì vậy, thực hiện nó nhiều lần có thể đưa ra các kết quả đầu ra khác nhau khi
y = 3
def powerOfTwo[]:
return y**2
3 thay đổitính bất biến
Tính bất biến là một thuộc tính của một giá trị, mà một khi được xác định thì không thể thay đổi được. Điều này có thể làm giảm một số tính linh hoạt trong cách cấu trúc chương trình, nhưng nó cho phép tránh mọi tác dụng phụ và thay đổi không mong muốn đối với giá trị được sử dụng
Trong Python, đạt được tính bất biến có vẻ khá dễ dàng, nhưng nó đòi hỏi một số chú ý đến cách khai báo được sử dụng. Python có hai nhóm kiểu dữ liệu chính. có thể thay đổi và bất biến
Các kiểu dữ liệu không thể thay đổi đề cập đến các kiểu dữ liệu mà một khi đã khai báo thì không thể thay đổi được. Điều đó không có nghĩa là một biến không thể được gán lại, nhưng việc tạo một bản sao của một khai báo hiện có dẫn đến một khai báo hoàn toàn mới [gán mới trong bộ nhớ]. Hãy xem xét những điều sau đây
Các biến
y = 3
def powerOfTwo[]:
return y**2
4 và y = 3
def powerOfTwo[]:
return y**2
5 sẽ lưu trữ cùng một giá trị, nhưng chúng là các khai báo hoàn toàn khác nhau [sử dụng các khoảng trống khác nhau trong bộ nhớ]. Vì vậy, gán lại y = 3
def powerOfTwo[]:
return y**2
5 cho một cái gì đó nhưvăn bản sẽ không bị ảnh hưởng theo bất kỳ cách nào. Điều đó có vẻ hiển nhiên nhưng không phải lúc nào cũng đúng. Các kiểu dữ liệu bất biến là int, float, decimal, bool, string, tuple và range
Các kiểu dữ liệu có thể thay đổi là những kiểu dữ liệu đã được khai báo có thể được thay đổi. Danh sách là một trong những kiểu dữ liệu này. Trong trường hợp của danh sách, sự thay đổi này cho phép thêm, xóa hoặc thay thế các giá trị trong danh sách. Đồng thời, việc gán một biến mới, từ một khai báo danh sách đã tồn tại, sẽ không dẫn đến định nghĩa danh sách mới mà thay vào đó, một bản sao nông sẽ được tạo [bộ nhớ được chia sẻ]. Những thay đổi được thực hiện trong một danh sách sẽ ảnh hưởng đến danh sách khác. Lấy những thứ sau
Cả
y = 3
def powerOfTwo[]:
return y**2
7 và y = 3
def powerOfTwo[]:
return y**2
8 sẽ kết thúc giống nhau. Hành vi này có thể là một trở ngại để đạt được mã hoàn toàn bất biến, vì vậy chúng phải được sử dụng cẩn thận. Các cách để tránh điều này là tạo các bản sao sâu trong quá trình gán hoặc tránh khai báo danh sách dựa trên danh sách hiện có. Các kiểu dữ liệu có thể thay đổi khác là danh sách, từ điển, bộ và lớp do người dùng định nghĩaNếu muốn có tính bất biến hoàn toàn, nên tránh các loại dữ liệu có thể thay đổi hoặc trong trường hợp các lớp dữ liệu sử dụng đối số cố định có thể hữu ích. Tuy nhiên, điều đó sẽ có rất nhiều hạn chế, do đó, một giải pháp thay thế là sử dụng các kỹ thuật tránh bất kỳ đột biến nào đối với các loại dữ liệu có thể thay đổi
Các hàm bậc cao hơn
Các loại hàm này là những hàm có thể lấy các hàm khác làm đối số của chúng hoặc kết quả là trả về một hàm. Với sự trợ giúp của những điều này, các chức năng có thể được kết hợp với nhau và có thể đạt được hành vi phức tạp hơn. Khi có chức năng bậc cao hơn, người ta nói rằng ngôn ngữ coi chức năng là công dân hạng nhất. Python là một trong những ngôn ngữ đó. Trong Python, việc thêm một hàm làm tham số của một hàm khác là chuyện nhỏ và hoạt động giống như bất kỳ tham số nào khác. Lấy ví dụ
def sumFive[x]:
return x + 5
def doTwice[func, *val]:
return func[func[*val]]
print[doTwice[sumFive,5]]
Chức năng phổ biến
Các hàm thuần túy, tính bất biến và các hàm bậc cao hơn là những thứ có thể đạt được trong Python và do đó, các nguyên tắc lập trình hàm có thể được áp dụng trong python. Nhưng, nếu đó là thứ duy nhất có sẵn, thì việc sử dụng lập trình chức năng trong Python sẽ trở nên phức tạp và sẽ yêu cầu mã hóa nhiều hơn dự kiến. Do đó, có rất nhiều hàm phổ biến hữu ích [xem funcools] sử dụng các nguyên tắc lập trình hàm cho phép làm việc với cấu trúc dữ liệu và luồng chương trình. Các chức năng được đề cập là bản đồ, bộ lọc, thu nhỏ, đây là những chức năng chính cho phép tiếp cận chức năng trong khi viết mã
bản đồ
là một chức năng nhận một chức năng khác và sau đó áp dụng nó cho từng phần tử của một đối tượng có thể lặp lại. Ngoài ra, bản đồ hỗ trợ nhiều lần lặp lại dưới dạng thuộc tính. Chức năng có thể được mô tả như
y = 3
def powerOfTwo[]:
return y**2
9Trong trường hợp có nhiều lần lặp, hàm được chuyển tới bản đồ sẽ chấp nhận số đối số bằng số lần lặp được truyền. Ngoài ra, bản đồ sẽ ngừng lặp lại khi sử dụng hết lần lặp ngắn nhất. Một điều cần lưu ý là sau khi bản đồ được thực thi, giá trị kết quả sẽ cần được chuyển đổi thành trình vòng lặp mong muốn. Có thể thấy, hàm này cho phép tạo một iterator mới từ một iterator hiện có. Đồng thời, nó có một số lợi thế rõ ràng so với mệnh đề for truyền thống [hoặc một lúc]. Xem xét ví dụ sau
list1 = [1,2,3,4,5,6,7]
for index, value in enumerate[list1]:
list1[index] = value*2
Trong khối mã này, những gì đang được thực hiện là lấy một danh sách và sau đó cố gắng sao chép từng giá trị của nó. Bản thân mã có vẻ ổn, nhưng có một số điều cần xem xét. Rõ ràng là một tuyên bố hiện có một đột biến. Để duy trì cách tiếp cận lập trình chức năng, cách tiếp cận này sẽ không được chấp nhận. Ngoài ra, thao tác này đi kèm với rủi ro nếu mã thay đổi mạnh trong tương lai. Sử dụng bản đồ, mã sẽ kết thúc như thế này
list1 = [1,2,3,4,5,6,7]
def byTwo[x]:
return x*2
list2 = list[map[byTwo,list1]]
Trong trường hợp này, các thao tác không hiển thị rõ ràng, thay vào đó, một danh sách mới được tạo bằng bản đồ. Ngoài ra, mã tạo danh sách mới ngắn hơn và súc tích hơn. Thậm chí có thể nói rằng nó dễ đọc hơn
lọc
khá giống với bản đồ, một chức năng và một lần lặp [lần này nhiều lần lặp không được hỗ trợ] được thực hiện. Tuy nhiên, thay vì áp dụng hàm cho từng phần tử của iterable để thay đổi giá trị của chúng, hàm sẽ xác định xem giá trị có được đưa vào iterable mới được tạo hay không. Nếu hàm trả về giá trị trung thực, phần tử được đánh giá sẽ ở lại; . Bộ lọc được định nghĩa như sau
def sumFive[x]:
return x + 5
def doTwice[func, *val]:
return func[func[*val]]
print[doTwice[sumFive,5]]
0Cũng giống như bản đồ, sau khi bộ lọc được thực thi, vẫn cần phải chuyển đổi kết quả thành lần lặp mong muốn. Ưu điểm của việc sử dụng bộ lọc so với cấu trúc for/while truyền thống vẫn giống như trước đây. Hãy xem xét những điều sau đây
list1 = [1,2,3,4,5,6,7]
list2 = []
for val in list1:
if val % 2 == 0:
list2.append[val]
# list2 will equal [2,4,6]
Lần này, tất cả các phần tử chẵn đang được thêm vào một danh sách mới. Cách tiếp cận hoạt động tốt và khai báo đầu tiên không bị thao túng, nhưng vẫn tồn tại đột biến trong khai báo thứ hai [danh sách trống]. Sử dụng bộ lọc sẽ tránh được những vấn đề này và kết thúc với mã trông sạch hơn
def isEven[x]:
return x % 2 == 0
list1 = [1,2,3,4,5,6,7]
list2 = list[filter[isEven, list1]]
# list2 will equal [2,4,6]
giảm
là một chức năng khá đặc biệt. Trước hết, nó không có sẵn dưới dạng bản đồ hoặc bộ lọc, bạn cần nhập nó từ một mô-đun có tên là funcools. Thứ hai, chức năng này khá linh hoạt, nó cho phép chuyển đổi một iterable sang bất kỳ cấu trúc mong muốn nào khác [có thể là một int, một đối tượng, một từ điển, v.v.]. Giảm được định nghĩa là
def sumFive[x]:
return x + 5
def doTwice[func, *val]:
return func[func[*val]]
print[doTwice[sumFive,5]]
1Cách nó được sử dụng khá đơn giản; . Hàm cần 2 thuộc tính; . Nó có thể hơi khó hiểu, lấy ví dụ sau
from functools import reduce
def sum[prev, current]:
return prev + current
result = reduce[sum, [1,2,3,4,5]]
kết quả trong trường hợp này sẽ có giá trị là
def sumFive[x]:
return x + 5
def doTwice[func, *val]:
return func[func[*val]]
print[doTwice[sumFive,5]]
2. Phân tách toàn bộ hoạt động, những điều sau đây sẽ được quan sát. def sumFive[x]:
return x + 5
def doTwice[func, *val]:
return func[func[*val]]
print[doTwice[sumFive,5]]
3. Đó là một cuộc gọi đệ quy trên toàn bộ chức năng có thể lặp lại được cung cấp. Khi không có giá trị ban đầu nào được cung cấp, giá trị ban đầu sẽ là giá trị đầu tiên của lần lặp. Với một giá trị ban đầuresult = reduce[sum, [1,2,3],10]
Kết quả sẽ tương đương với.
def sumFive[x]:
return x + 5
def doTwice[func, *val]:
return func[func[*val]]
print[doTwice[sumFive,5]]
4. Có thể thấy, giá trị ban đầu được lấy trong lệnh gọi hàm đầu tiên thay vì phần tử đầu tiên của iterable. Theo cách tương tự, như các hàm trước đã khám phá, ưu điểm của việc sử dụng reduce là chúng cho phép viết mã ngắn hơn và giúp duy trì một số dạng bất biến đối với dữ liệu được sử dụng. Ngoài ra, đối với một số thao tác rất đơn giản có thể được thực hiện với rút gọn, đã có sẵn một số chức năng giúp đơn giản hóa quy trình. Ví dụ: để tính tổng tất cả các giá trị trong danh sách, có thể sử dụng sumCác chức năng khác có thể được sử dụng là prod, max, min, len, all, any, v.v. Tốt hơn là sử dụng cái này thay vì giảm nếu chúng đáp ứng nhu cầu của chúng tôi. Điều quan trọng cần lưu ý là một số chức năng này được tích hợp sẵn trong python và những chức năng khác cần được nhập từ funcools
hiểu
Như đã thấy trước đây, bản đồ và bộ lọc có thể được sử dụng làm cách tạo các lần lặp mới từ các lần lặp hiện có. Chúng là những cách khá hay để có cách tiếp cận chức năng hơn trong mã của chúng ta, nhưng chúng không phải là cách duy nhất để đạt được điều tương tự trong Python. Hiểu là một cách xây dựng các lần lặp trong python từ những cái hiện có với cấu trúc đơn giản và ngắn gọn. Có bốn loại hiểu trong python
- hiểu danh sách
- hiểu từ điển
- Đặt mức độ hiểu
- hiểu máy phát điện
Sử dụng bản đồ, cách sử dụng như sau
list1 = [1,2,3,4,5,6,7]
def byTwo[x]:
return x*2
list2 = list[map[byTwo,list1]]
Với khả năng hiểu danh sách, điều này có thể được thay đổi thành
Như có thể thấy, nó ngắn gọn hơn nhiều [bản đồ có thể ngắn gọn như thế này khi sử dụng lambdas, còn được gọi là hàm ẩn danh] và quan trọng hơn, không cần chuyển đổi kết quả thành danh sách, điều đó được ngụ ý trong cấu trúc. Phá vỡ điều này, ba phần tồn tại
- [ ] đại diện cho trình vòng lặp kết quả.
5 dành cho danh sách,def sumFive[x]: return x + 5 def doTwice[func, *val]: return func[func[*val]] print[doTwice[sumFive,5]]
6 dành cho từ điển và bộ, vàdef sumFive[x]: return x + 5 def doTwice[func, *val]: return func[func[*val]] print[doTwice[sumFive,5]]
7 dành cho trình tạodef sumFive[x]: return x + 5 def doTwice[func, *val]: return func[func[*val]] print[doTwice[sumFive,5]]
- x*2 là hoạt động, nó có thể là bất kỳ loại hoạt động nào và rất phức tạp [càng đơn giản càng tốt]. Không có tuyên bố được cho phép, mặc dù. Trong trường hợp từ điển, một chìa khóa. giá trị cần được xác định
- đối với x trong list1 là phép lặp được thực hiện, nó có thể có nhiều dạng và có thể lặp lại trên bất kỳ lần lặp nào, ngay cả khi việc hiểu tạo ra thứ gì đó khác với nó
Một điểm khác biệt so với bản đồ là việc hỗ trợ lặp lại nhiều lần lặp cùng lúc không đơn giản và đòi hỏi nhiều công việc hơn
Bây giờ, những gì về lọc?
y = 3
def powerOfTwo[]:
return y**2
0Điều đó sẽ trả về tất cả các số chẵn, giống như chức năng bộ lọc. Điều kiện được trình bày thêm một khía cạnh khác cho điều này và nếu muốn có một câu lệnh khác, cấu trúc sẽ thay đổi một chút như thế này
y = 3
def powerOfTwo[]:
return y**2
1Nhìn chung, khả năng hiểu là một giải pháp thay thế tốt cho ánh xạ và bộ lọc, và thậm chí còn tốt hơn vì hầu hết thời gian nó dẫn đến mã ngắn hơn và súc tích hơn. Tuy nhiên, mọi thứ có thể trở nên rất phức tạp với khả năng hiểu và nếu không cẩn thận, người ta có thể kết thúc với mã rất khó đọc. Vì vậy, một cách thực hành tốt là giữ cho chúng đơn giản và nếu cần các thao tác phức tạp, tốt nhất là có một chức năng khác được gọi bên trong phần hiểu
Một số cân nhắc
Có một cách tiếp cận chức năng đối với mã trong python có thể mang lại nhiều lợi thế cho cách thức hoạt động của mã và mức độ dễ dàng để làm việc với nó. Có các hàm thuần túy, duy trì một số cảm giác bất biến và sử dụng các hàm bậc cao hơn có thể làm cho mã sạch hơn và tránh các sự cố không mong muốn. Các chức năng và công cụ được trình bày [bản đồ, bộ lọc, thu nhỏ, hiểu] là những cách tuyệt vời để đạt được điều này. Tuy nhiên, có nhiều công cụ khác không được đề cập, rất hữu ích [trang trí, khớp mẫu, v.v.], và hơn thế nữa, những cách tiếp cận này không làm mất hiệu lực các cách phổ biến hơn để lặp hoặc xây dựng mã. Dưới đây là một số điểm cần xem xét