ทำไมต้องเก็บฟังก์ชั่นภายในพจนานุกรมหลาม?


68

ฉันเป็นผู้เริ่มต้นงูใหญ่และฉันเพิ่งเรียนรู้เทคนิคที่เกี่ยวข้องกับพจนานุกรมและฟังก์ชั่น ไวยากรณ์เป็นเรื่องง่ายและดูเหมือนว่าเป็นเรื่องเล็กน้อย แต่ความรู้สึกของฉันหลามรู้สึกเสียวซ่า มีบางอย่างบอกฉันว่านี่เป็นแนวคิดที่ลึกและไพเราะมากและฉันก็ไม่เข้าใจถึงความสำคัญของมัน ใครสามารถใส่ชื่อให้กับเทคนิคนี้และอธิบายว่ามันมีประโยชน์หรือไม่?


เทคนิคคือเมื่อคุณมีพจนานุกรมไพ ธ อนและฟังก์ชั่นที่คุณตั้งใจจะใช้ คุณแทรกองค์ประกอบพิเศษลงใน dict ที่มีค่าเป็นชื่อของฟังก์ชั่น เมื่อคุณพร้อมที่จะเรียกฟังก์ชั่นที่คุณออกโทรทางอ้อมโดยอ้างถึงองค์ประกอบ Dict ไม่ใช่ฟังก์ชั่นตามชื่อ

ตัวอย่างที่ฉันใช้งานมาจาก Learn Python the Hard Way, 2nd Ed (นี่เป็นรุ่นที่มีให้เมื่อคุณสมัครใช้งานผ่านUdemy.comแต่น่าเสียดายที่เวอร์ชัน HTML ที่ใช้งานได้ฟรีปัจจุบันเป็น Ed 3 และไม่มีตัวอย่างนี้อีกต่อไป)

ในการถอดความ:

# 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

จากนั้นนิพจน์ต่อไปนี้จะเทียบเท่า คุณสามารถเรียกใช้ฟังก์ชันได้โดยตรงหรือโดยอ้างอิงองค์ประกอบ dict ที่มีค่าเป็นฟังก์ชัน

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

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

บางคนสามารถอธิบายคุณลักษณะของภาษานี้ได้และบางทีมันอาจจะเล่นที่ไหนในการเขียนโปรแกรม "ของจริง" แบบฝึกหัดของเล่นนี้เพียงพอที่จะสอนฉันเกี่ยวกับไวยากรณ์ แต่ไม่ได้พาฉันไปที่นั่น


13
เหตุใดโพสต์นี้จึงไม่อยู่ในหัวข้อ มันเป็นคำถามที่ยอดเยี่ยมเกี่ยวกับอัลกอริทึมและโครงสร้างข้อมูล
Martijn Pieters

ฉันเคยเห็นบางสิ่งเช่นนี้ในภาษาอื่นแล้ว คุณสามารถดูได้ว่ามันเป็นคำสั่งสลับ แต่ห่ออย่างดีในวัตถุที่ผ่านได้ด้วยเวลาค้นหา O (1)
KChaloux

1
ฉันมีลางสังหรณ์มีบางสิ่งบางอย่างที่สำคัญ & การอ้างอิงตนเองเกี่ยวกับการรวมฟังก์ชั่นภายใน dict ของตัวเอง ... ดูคำตอบของ @ dietbuddha ... แต่อาจจะไม่?
mdeutschmtl

คำตอบ:


83

การใช้ dict ให้คุณแปลคีย์เป็น callable กุญแจไม่จำเป็นต้องฮาร์ดโค้ดเหมือนเช่นในตัวอย่างของคุณ

โดยปกตินี่เป็นรูปแบบของการจัดส่งผู้โทรเข้าซึ่งคุณใช้ค่าของตัวแปรเพื่อเชื่อมต่อกับฟังก์ชั่น สมมติว่ากระบวนการเครือข่ายส่งรหัสคำสั่งถึงคุณการจับคู่การจัดส่งช่วยให้คุณแปลรหัสคำสั่งเป็นรหัสที่ปฏิบัติการได้ง่าย:

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))

commandโปรดทราบว่าสิ่งที่ฟังก์ชั่นที่เราเรียกว่าตอนนี้ขึ้นอยู่กับสิ่งที่มีค่าเป็นของ กุญแจไม่จำเป็นต้องตรงกับทั้ง; ไม่จำเป็นต้องเป็นสตริงคุณสามารถใช้อะไรก็ได้ที่สามารถใช้เป็นกุญแจและเหมาะกับแอปพลิเคชันเฉพาะของคุณ

การใช้วิธีการแจกจ่ายนั้นปลอดภัยกว่าเทคนิคอื่น ๆ เช่นeval()เนื่องจากมัน จำกัด คำสั่งที่อนุญาตให้ใช้กับสิ่งที่คุณกำหนดไว้ล่วงหน้า ผู้โจมตีจะไม่แอบเข้าไปls)"; DROP TABLE Students; --ฉีดที่โต๊ะส่งตัวอย่างเช่น


5
@Martjin - สิ่งนี้เรียกว่าการใช้ 'รูปแบบคำสั่ง' ไม่ได้หรือไม่? ดูเหมือนว่าเป็นแนวคิดที่ OP พยายามจะเข้าใจใช่ไหม
ปริญญาเอก

3
@PhD: ใช่ตัวอย่างที่ฉันสร้างขึ้นเป็นการนำรูปแบบคำสั่งมาใช้ dictทำหน้าที่เป็นผู้มอบหมายงาน (ผู้จัดการคำสั่งทรง ฯลฯ )
Martijn Pieters

คำอธิบายระดับบนสุดยอดเยี่ยม @Martijn ขอบคุณ ฉันคิดว่าฉันได้รับความคิด "ส่ง"
mdeutschmtl

28

@Martijn Pieters ทำได้ดีมากในการอธิบายเทคนิค แต่ฉันต้องการที่จะอธิบายบางสิ่งบางอย่างจากคำถามของคุณ

สิ่งสำคัญที่ควรทราบคือคุณไม่ได้จัดเก็บ "ชื่อของฟังก์ชัน" ไว้ในพจนานุกรม คุณกำลังจัดเก็บการอ้างอิงถึงฟังก์ชั่นของตัวเอง คุณสามารถเห็นสิ่งนี้ได้โดยใช้printฟังก์ชั่น

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

fเป็นเพียงตัวแปรที่อ้างอิงฟังก์ชันที่คุณกำหนด การใช้พจนานุกรมช่วยให้คุณจัดกลุ่มสิ่งต่าง ๆ ได้ แต่ไม่แตกต่างจากการกำหนดฟังก์ชันให้กับตัวแปรที่แตกต่างกัน

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

ในทำนองเดียวกันคุณสามารถส่งผ่านฟังก์ชั่นเป็นอาร์กิวเมนต์

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

5
การกล่าวถึงฟังก์ชั่นชั้นหนึ่งจะช่วย :-) แน่นอน
Florian Margaine

7

โปรดทราบว่าคลาส Python เป็นเพียงน้ำตาลไวยากรณ์สำหรับพจนานุกรม เมื่อคุณทำ:

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

เมื่อคุณโทร

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

เป็นเช่นเดียวกับ:

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

ซึ่งหลังจากการแก้ไขชื่อแล้วก็เหมือนกับ:

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

เทคนิคหนึ่งที่มีประโยชน์คือการจับคู่ข้อมูลผู้ใช้กับการโทรกลับ ตัวอย่างเช่น:

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

อีกทางเลือกหนึ่งสามารถเขียนในชั้นเรียน:

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

ไวยากรณ์การโทรกลับที่ดีกว่านั้นขึ้นอยู่กับแอปพลิเคชันและรสนิยมของโปรแกรมเมอร์ อดีตเป็นสไตล์การทำงานมากขึ้นหลังเป็นเชิงวัตถุมากขึ้น อดีตอาจรู้สึกเป็นธรรมชาติมากขึ้นถ้าคุณต้องการแก้ไขรายการในพจนานุกรมฟังก์ชั่นแบบไดนามิก (อาจขึ้นอยู่กับการป้อนข้อมูลของผู้ใช้); หลังอาจรู้สึกเป็นธรรมชาติมากขึ้นถ้าคุณมีการตั้งค่าการแมปที่ตั้งไว้ล่วงหน้าที่แตกต่างกันซึ่งสามารถเลือกได้แบบไดนามิก


ฉันคิดว่าการแลกเปลี่ยนนี้คือสิ่งที่ทำให้ฉันคิดว่า "pythonic" ความจริงที่ว่าสิ่งที่คุณเห็นบนพื้นผิวเป็นเพียงวิธีการนำเสนอสิ่งที่ลึกกว่าปกติ บางทีนั่นอาจไม่ใช่สิ่งที่เฉพาะเจาะจงกับไพ ธ อน แต่ถึงแม้ว่าแบบฝึกหัดไพ ธ อน (และโปรแกรมเมอร์ไพ ธ อน?) ดูเหมือนจะพูดถึงคุณสมบัติของภาษาในลักษณะนี้อย่างมากมาย
mdeutschmtl

ความคิดอื่นมีบางสิ่งบางอย่างที่เฉพาะเจาะจงกับงูเหลือมในวิธีที่มันเต็มใจที่จะประเมินสิ่งที่ดูเหมือนว่า "คำศัพท์" สองคำที่อยู่ติดกันซึ่งกันและกันการอ้างอิง dict และรายการอาร์กิวเมนต์? ภาษาอื่นอนุญาตหรือไม่ มันเหมือนกับการเขียนโปรแกรมเทียบเท่าของการก้าวกระโดดในพีชคณิตจาก5 * xถึง5x(ยกโทษการเปรียบเทียบง่าย ๆ )
mdeutschmtl

@mdeutschmtl: มันไม่ได้เป็นเอกลักษณ์ของ Python แม้ว่าภาษาที่ไม่มีฟังก์ชั่นชั้นหนึ่งหรือวัตถุฟังก์ชั่นอาจไม่เคยมีสถานการณ์ใด ๆ ที่การเข้าถึงพจนานุกรมทันทีตามด้วยการเรียกใช้ฟังก์ชันอาจเป็นไปได้
Lie Ryan

2
@mdeutschmtl "ความจริงที่ว่าสิ่งที่คุณเห็นบนพื้นผิวเป็นเพียงวิธีธรรมดาในการนำเสนอบางสิ่งที่ลึกกว่านี้มาก" - นั่นเรียกว่าน้ำตาลทราย
ซินแทคติ

6

มีเทคนิค 2 อย่างที่กระโดดไปในใจของฉันซึ่งคุณอาจจะอ้างถึงไม่ได้ซึ่งก็คือไพทอนในสิ่งที่พวกเขาเป็นมากกว่าภาษาเดียว

1. เทคนิคการซ่อนข้อมูล / การห่อหุ้มและการรวมตัวกัน (โดยปกติแล้วพวกเขาจะจับมือกันดังนั้นฉันจึงจับพวกมันเข้าด้วยกัน)

คุณมีวัตถุที่มีข้อมูลและคุณแนบวิธีการ (พฤติกรรม) ซึ่งมีความเหนียวแน่นกับข้อมูล หากคุณต้องการเปลี่ยนฟังก์ชั่นให้ขยายฟังก์ชั่นหรือทำการเปลี่ยนแปลงอื่น ๆ ที่ผู้โทรไม่จำเป็นต้องเปลี่ยน (โดยไม่จำเป็นต้องส่งข้อมูลเพิ่มเติม)

2. ส่งตาราง

ไม่ใช่เคสแบบคลาสสิกเนื่องจากมีเพียงรายการเดียวที่มีฟังก์ชัน อย่างไรก็ตามตารางการจัดส่งจะใช้ในการจัดระเบียบพฤติกรรมที่แตกต่างกันโดยคีย์เช่นที่พวกเขาสามารถค้นหาและเรียกว่าแบบไดนามิก ฉันไม่แน่ใจว่าคุณคิดถึงสิ่งนี้หรือไม่เพราะคุณไม่ได้อ้างถึงฟังก์ชั่นในลักษณะที่มีการเปลี่ยนแปลง แต่ไม่มีน้อยคุณยังได้รับผลผูกพันปลายที่มีประสิทธิภาพ (การโทร "อ้อม")

ความสมดุล

สิ่งหนึ่งที่ควรทราบคือสิ่งที่คุณทำจะใช้ได้ดีกับเนมสเปซที่รู้จักของคีย์ อย่างไรก็ตามคุณใช้ความเสี่ยงของการชนกันระหว่างข้อมูลและฟังก์ชั่นที่มี namespace ที่ไม่รู้จักของคีย์


การห่อหุ้มเนื่องจากข้อมูลและฟังก์ชันที่เก็บไว้ใน dict (ionary) มีความสัมพันธ์กันและมีการทำงานร่วมกัน ข้อมูลและฟังก์ชั่นนั้นมาจากร่างสองหลักที่แตกต่างกันมากดังนั้นเมื่อเห็นอย่างรวดเร็วในครั้งแรกตัวอย่างก็ดูเหมือนว่าจะรวมกันเป็นก้อน ๆ
ChuckCottrill

0

ฉันโพสต์วิธีแก้ปัญหาที่ฉันคิดว่าค่อนข้างทั่วไปและมีประโยชน์เพราะมันง่ายและปรับให้เข้ากับกรณีเฉพาะ:

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)

นอกจากนี้ยังสามารถกำหนดรายการที่แต่ละองค์ประกอบเป็นวัตถุฟังก์ชั่นและใช้__call__วิธีการในตัว มอบเครดิตให้กับทุกคนสำหรับแรงบันดาลใจและความร่วมมือ

"ศิลปินผู้ยิ่งใหญ่คือซิมป์" Henri Frederic Amiel

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.