Tại sao lưu trữ một chức năng bên trong một từ điển python?


68

Tôi là một người mới bắt đầu trăn, và tôi vừa học được một kỹ thuật liên quan đến từ điển và chức năng. Cú pháp rất dễ và có vẻ như là một điều tầm thường, nhưng cảm giác trăn của tôi đang nhói lên. Một cái gì đó cho tôi biết đây là một khái niệm sâu sắc và rất sâu sắc và tôi không hoàn toàn nắm bắt được tầm quan trọng của nó. Ai đó có thể đặt tên cho kỹ thuật này và giải thích làm thế nào / tại sao nó hữu ích?


Kỹ thuật này là khi bạn có một từ điển python và một chức năng mà bạn định sử dụng nó. Bạn chèn một phần tử phụ vào dict, có giá trị là tên của hàm. Khi bạn sẵn sàng gọi hàm, bạn sẽ thực hiện cuộc gọi một cách gián tiếp bằng cách tham chiếu đến phần tử dict, chứ không phải hàm theo tên.

Ví dụ tôi đang làm việc là từ Tìm hiểu Python theo cách khó, 2nd Ed. (Đây là phiên bản có sẵn khi bạn đăng ký qua Udemy.com ; đáng buồn là phiên bản HTML miễn phí trực tiếp hiện tại là Ed 3 và không còn bao gồm ví dụ này nữa).

Để diễn dải:

# make a dictionary of US states and major cities
cities = {'San Diego':'CA', 'New York':'NY', 'Detroit':'MI'}

# define a function to use on such a dictionary
def find_city (map, city):
    # does something, returns some value
    if city in map:
        return map[city]
    else:
        return "Not found"

# then add a final dict element that refers to the function
cities['_found'] = find_city

Thì các biểu thức sau là tương đương. Bạn có thể gọi hàm trực tiếp hoặc bằng cách tham chiếu phần tử dict có giá trị là hàm.

>>> find_city (cities, 'New York')
NY

>>> cities['_found'](cities, 'New York')
NY

Ai đó có thể giải thích tính năng ngôn ngữ này là gì không, và có thể nói đến đâu để chơi trong lập trình "thực"? Bài tập đồ chơi này đủ để dạy tôi cú pháp, nhưng không đưa tôi đến đó.


13
Tại sao bài viết này sẽ lạc đề? Đó là một câu hỏi khái niệm cấu trúc dữ liệu và thuật toán tuyệt vời!
Martijn Pieters

Tôi đã thấy (và thực hiện) một số thứ như thế này trong các ngôn ngữ khác. Bạn có thể xem nó như một câu lệnh chuyển đổi, nhưng được gói gọn trong một đối tượng có thể qua được với thời gian tra cứu O (1).
KChaloux

1
Tôi có linh cảm có điều gì đó quan trọng & tự tham khảo về việc bao gồm chức năng bên trong chính sách của mình ... xem câu trả lời của @ dietbuddha ... nhưng có lẽ không?
mdeutschmtl

Câu trả lời:


83

Sử dụng một dict cho phép bạn dịch khóa thành một cuộc gọi. Khóa không cần phải được mã hóa cứng, như trong ví dụ của bạn.

Thông thường, đây là một hình thức gửi người gọi, trong đó bạn sử dụng giá trị của một biến để kết nối với một chức năng. Giả sử một quy trình mạng gửi cho bạn mã lệnh, ánh xạ công văn cho phép bạn dịch mã lệnh dễ dàng thành mã thực thi:

def do_ping(self, arg):
    return 'Pong, {0}!'.format(arg)

def do_ls(self, arg):
    return '\n'.join(os.listdir(arg))

dispatch = {
    'ping': do_ping,
    'ls': do_ls,
}

def process_network_command(command, arg):
    send(dispatch[command](arg))

Lưu ý rằng chức năng nào chúng ta gọi bây giờ phụ thuộc hoàn toàn vào giá trị của nó command. Khóa này cũng không phải khớp; nó thậm chí không phải là một chuỗi, bạn có thể sử dụng bất cứ thứ gì có thể được sử dụng làm khóa và phù hợp với ứng dụng cụ thể của bạn.

Sử dụng một phương thức điều phối an toàn hơn các kỹ thuật khác, chẳng hạn như eval(), vì nó giới hạn các lệnh cho phép đối với những gì bạn đã xác định trước. Chẳng có kẻ tấn công nào sẽ lén ls)"; DROP TABLE Students; --tiêm thuốc qua bảng điều phối, chẳng hạn.


5
@Martjin - Không thể gọi đây là một triển khai 'Mẫu lệnh' trong trường hợp đó sao? Có vẻ như đó là khái niệm mà OP đang cố nắm bắt?
Tiến sĩ

3
@PhD: Vâng, ví dụ tôi xây dựng là triển khai Mẫu lệnh; các dicthoạt động như người điều phối (người quản lý chỉ huy, người xâm lược, v.v.).
Martijn Pieters

Giải thích cấp cao tuyệt vời, @Martijn, cảm ơn. Tôi nghĩ rằng tôi có được ý tưởng "công văn".
mdeutschmtl

28

@Martijn Pieters đã làm rất tốt khi giải thích kỹ thuật này, nhưng tôi muốn làm rõ điều gì đó từ câu hỏi của bạn.

Điều quan trọng cần biết là bạn KHÔNG lưu trữ "tên của hàm" trong từ điển. Bạn đang lưu trữ một tham chiếu đến chính chức năng. Bạn có thể thấy điều này bằng cách sử dụng một printchức năng.

>>> def f():
...   print 1
... 
>>> print f
<function f at 0xb721c1b4>

fchỉ là một biến tham chiếu chức năng bạn xác định. Sử dụng từ điển cho phép bạn nhóm các thứ giống nhau, nhưng nó không khác gì việc gán một hàm cho một biến khác.

>>> a = f
>>> a
<function f at 0xb721c3ac>
>>> a()
1

Tương tự, bạn có thể truyền một hàm làm đối số.

>>> def c(func):
...   func()
... 
>>> c(f)
1

5
Đề cập đến chức năng hạng nhất chắc chắn sẽ giúp ích :-)
Florian Margaine

7

Lưu ý rằng lớp Python thực sự chỉ là một cú pháp đường cho từ điển. Khi bạn làm:

class Foo(object):
    def find_city(self, city):
        ...

khi bạn gọi

f = Foo()
f.find_city('bar')

thực sự giống như:

getattr(f, 'find_city')('bar')

mà, sau khi phân giải tên, cũng giống như:

f.__class__.__dict__['find_city'](f, 'bar')

Một kỹ thuật hữu ích là để ánh xạ đầu vào của người dùng đến các cuộc gọi lại. Ví dụ:

def cb1(...): 
    ...
funcs = {
    'cb1': cb1,
    ...
}
while True:
    input = raw_input()
    funcs[input]()

Điều này thay thế có thể được viết trong lớp:

class Funcs(object):
    def cb1(self, a): 
        ...
funcs = Funcs()
while True:
    input = raw_input()
    getattr(funcs, input)()

Bất kỳ cú pháp gọi lại nào tốt hơn phụ thuộc vào ứng dụng cụ thể và sở thích của lập trình viên. Cái trước là phong cách chức năng hơn, cái sau là hướng đối tượng hơn. Cái trước có thể cảm thấy tự nhiên hơn nếu bạn cần sửa đổi các mục trong từ điển hàm một cách linh hoạt (có lẽ dựa trên đầu vào của người dùng); cái sau có thể cảm thấy tự nhiên hơn nếu bạn có một bộ ánh xạ cài đặt sẵn khác nhau có thể được chọn một cách linh hoạt.


Tôi nghĩ khả năng thay thế này là điều khiến tôi nghĩ là "pythonic", thực tế là những gì bạn nhìn thấy trên bề mặt chỉ là một cách thông thường để trình bày một cái gì đó sâu sắc hơn nhiều. Có lẽ điều đó không đặc biệt với python, mặc dù các bài tập python (và lập trình viên python?) Dường như nói rất nhiều về các tính năng ngôn ngữ theo cách này.
mdeutschmtl

Một suy nghĩ khác, có điều gì đó cụ thể đối với python theo cách nó sẵn sàng đánh giá hai "thuật ngữ" trông như thế nào chỉ ngồi cạnh nhau, tham chiếu chính tả và danh sách đối số? Các ngôn ngữ khác có cho phép điều đó không? Đó là loại tương đương với chương trình của bước nhảy vọt trong đại số từ 5 * xđến 5x(tha thứ cho các loại suy đơn giản).
mdeutschmtl

@mdeutschmtl: nó không thực sự độc đáo với Python, mặc dù các ngôn ngữ thiếu chức năng hoặc đối tượng chức năng hạng nhất có thể không bao giờ có bất kỳ tình huống nào có thể truy cập từ điển ngay sau khi gọi hàm.
Nói dối Ryan

2
@mdeutschmtl "thực tế là những gì bạn nhìn thấy trên bề mặt chỉ là một cách thông thường để trình bày một cái gì đó sâu sắc hơn nhiều." - đó được gọi là đường cú pháp và tồn tại ở khắp mọi nơi
Izkata

6

Có hai kỹ thuật nhảy vọt vào tâm trí của tôi mà bạn có thể đề cập đến, không có kỹ thuật nào trong số đó là Pythonic ở chỗ chúng rộng hơn một ngôn ngữ.

1. Kỹ thuật che giấu / đóng gói thông tin và gắn kết thông tin (chúng thường song hành với nhau nên tôi kết hợp chúng lại với nhau).

Bạn có một đối tượng có dữ liệu và bạn đính kèm một phương thức (hành vi) rất gắn kết với dữ liệu. Nếu bạn cần thay đổi chức năng, mở rộng chức năng hoặc thực hiện bất kỳ thay đổi nào khác, người gọi sẽ không cần thay đổi (giả sử không cần thêm dữ liệu nào nữa).

2. Công văn bảng

Không phải là trường hợp cổ điển, bởi vì chỉ có một mục với một chức năng. Tuy nhiên, các bảng điều phối được sử dụng để tổ chức các hành vi khác nhau bằng một khóa sao cho chúng có thể tra cứu và gọi một cách linh hoạt. Tôi không chắc liệu suy nghĩ của bạn về điều này vì bạn không đề cập đến chức năng một cách năng động, nhưng không có gì ít hơn bạn vẫn đạt được ràng buộc muộn hiệu quả (cuộc gọi "gián tiếp").

Đánh đổi

Một điều cần lưu ý là những gì bạn làm sẽ hoạt động tốt với một không gian tên đã biết của các khóa. Tuy nhiên, bạn có nguy cơ va chạm giữa dữ liệu và các chức năng với một không gian tên không xác định của các khóa.


Đóng gói vì dữ liệu và chức năng được lưu trữ trong dict (ionary) có liên quan và do đó có sự gắn kết. Dữ liệu và chức năng là từ hai người rất khác nhau, vì vậy, thoạt nhìn, ví dụ này dường như đang gộp các thực thể khác nhau lại với nhau.
ChuckCottrill

0

Tôi đăng giải pháp này mà tôi nghĩ là khá chung chung và có thể hữu ích vì nó được giữ đơn giản và dễ thích ứng với các trường hợp cụ thể:

def p(what):
    print 'playing', cmpsr

def l(what):
    print 'listening', cmpsr

actions = {'Play' : p, 'Listen' : l}

act = 'Listen'
cmpsr = 'Vivaldi'

actions[act].__call__(cmpsr)

người ta cũng có thể định nghĩa một danh sách trong đó mỗi phần tử là một đối tượng hàm và sử dụng __call__phương thức dựng sẵn. Tín dụng cho tất cả cho cảm hứng và hợp tác.

"Nghệ sĩ vĩ đại là người mô phỏng", Henri Frederic Amiel

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.