User Tools

Site Tools


python:goodies

19. ของดีๆ

เป้าหมายอย่างหนึ่งของผมสำหรับหนังสือเล่มนี้คือการสอนไพธอน ให้น้อยที่สุด เมื่อมีสองวิธีในการทำบางสิ่ง ผมเลือกวิธีหนึ่งและหลีกเลี่ยงการพูดถึงอีกวิธีหนึ่ง หรือบางครั้งผมก็เอาอันที่สองไปเป็นแบบฝึกหัด

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

19.1 นิพจน์เงื่อนไข

เราเห็นคำสั่งแบบมีเงื่อนไขในหัวข้อ 5.4 คำสั่งแบบมีเงื่อนไขมักใช้เพื่อเลือกค่าใดค่าหนึ่งจากสองค่า ตัวอย่างเช่น

if x > 0:
    y = math.log(x)
else:
    y = float('nan')

คำสั่งนี้ตรวจสอบว่า x เป็นบวกหรือไม่ ถ้าใช่ มันจะคำนวณ math.log ถ้าไม่เช่นนั้น math.log จะได้ ValueError เพื่อหลีกเลี่ยงไม่ให้โปรแกรมหยุดทำงาน เราจึงสร้าง “NaN” ซึ่งเป็นค่าทศนิยมพิเศษที่แทนค่า “ไม่ใช่ตัวเลข (Not a Number)”

เราสามารถเขียนคำสั่งนี้ให้กระชับยิ่งขึ้นโดยใช้นิพจน์เงื่อนไข

y = math.log(x) if x > 0 else float('nan')

คุณเกือบจะอ่านบรรทัดนี้ได้เหมือนภาษาอังกฤษ: “y gets log-x if x is more than 0; otherwise it gets NaN” นั่นคือ y ได้รับ log-x ถ้า x มากกว่า 0; มิฉะนั้นจะได้รับ NaN

บางครั้งฟังก์ชันแบบเรียกซ้ำสามารถเขียนใหม่ได้โดยใช้นิพจน์เงื่อนไข ตัวอย่างนี้คือเวอร์ชันแบบเรียกซ้ำของ แฟกทอเรียล (factorial)

def factorial(n):
    if n == 0:
        return 1
    else:
        return n * factorial(n-1)

เราสามารถเขียนใหม่ได้ดังนี้

def factorial(n):
    return 1 if n == 0 else n * factorial(n-1)

การใช้นิพจน์เงื่อนไขอีกวิธีหนึ่งคือการจัดการอาร์กิวเมนต์ที่เป็นทางเลือก ตัวอย่างนี้คือเมธอด init จาก GoodKangaroo (ดูแบบฝึกหัดที่ แบบฝึกหัด 17.2)

    def __init__(self, name, contents=None):
        self.name = name
        if contents == None:
            contents = []
        self.pouch_contents = contents

เราสามารถเขียนใหม่ได้ดังนี้

    def __init__(self, name, contents=None):
        self.name = name
        self.pouch_contents = [] if contents == None else contents 

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

19.2 การสรุปความลิสต์

ในหัวข้อ 10.7 เราเห็นการแปลงและรูปแบบการกรอง ตัวอย่างเช่น ฟังก์ชันนี้รับลิสต์ของสตริง แปลงแต่ละอีลีเมนต์ให้เป็นตัวพิมพ์ใหญ่ ด้วยเมธอดของสตริงและส่งคืนลิสต์ใหม่ของสตริง:

def capitalize_all(t):
    res = []
    for s in t:
        res.append(s.capitalize())
    return res

เราสามารถเขียนให้กระชับยิ่งขึ้นโดยใช้การสรุปความลิสต์

def capitalize_all(t):
    return [s.capitalize() for s in t]

ตัวดำเนินการวงเล็บบ่งบอกว่าเรากำลังสร้างลิสต์ใหม่ นิพจน์ภายในวงเล็บจะระบุองค์ประกอบของลิสต์ และส่วนคำสั่ง for ระบุลำดับที่เรากำลังข้ามผ่าน

ไวยากรณ์ของการสรุปความลิสต์ นั้นดูอึดอัดเล็กน้อย เนื่องจากตัวแปรของลูป s ในตัวอย่างนี้ ปรากฏในนิพจน์ก่อนที่เราจะเจอส่วนของนิยาม

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

def only_upper(t):
    res = []
    for s in t:
        if s.isupper():
            res.append(s)
    return res

เราสามารถเขียนใหม่ได้โดยใช้การสรุปความลิสต์

def only_upper(t):
    return [s for s in t if s.isupper()]

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

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

19.3 นิพจน์ตัวสร้าง

นิพจน์ตัวสร้าง คล้ายกับการสรุปความลิสต์ แต่มีวงเล็บแทนการใช้วงเล็บเหลี่ยม

>>> g = (x**2 for x in range(5))
>>> g
<generator object <genexpr> at 0x7f4c45a786c0>

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

>>> next(g)
0
>>> next(g)
1

เมื่อคุณไปถึงจุดสิ้นสุดของลำดับ next จะทำให้เกิดเอ็กเซ็ปชัน StopIteration คุณยังสามารถใช้ลูป for เพื่อย้ำผ่านค่า

>>> for val in g:
...     print(val)
4
9
16

ออบเจ็กต์ตัวสร้างจะติดตามตำแหน่งที่มันอยู่ในลำดับ ดังนั้นลูป for จะเลือกตำแหน่งที่ next ค้างไว้ต่อไป เมื่อตัวสร้างหมด มันจะเกิด StopException ต่อไป

>>> next(g)
StopIteration

นิพจน์ตัวสร้างมักใช้กับฟังก์ชันเช่น sum, max และ min

>>> sum(x**2 for x in range(5))
30

19.4 ฟังก์ชัน any และฟังก์ชัน all

ไพธอนมีฟังก์ชันในตัว any ที่รับลำดับของค่าบูลีนและส่งกลับ True หากค่าใดค่าหนึ่งเป็น True มันทำงานกับลิสต์

>>> any([False, False, True])
True

แต่มักใช้กับนิพจน์ตัวสร้าง

>>> any(letter == 't' for letter in 'monty')
True

ตัวอย่างนั้นมีประโยชน์ไม่มากนักเพราะมันทำสิ่งเดียวกับตัวดำเนินการ in แต่เราสามารถใช้ฟังก์ชัน any เพื่อเขียนใหม่บางฟังก์ชันการค้นหาที่เราเขียนไว้ในหัวข้อ 9.3 ตัวอย่างเช่น เราสามารถเขียน avoids ได้ดังนี้

def avoids(word, forbidden):
    return not any(letter in forbidden for letter in word)

ฟังก์ชันนี้เกือบจะอ่านเหมือนภาษาอังกฤษ “word avoids forbidden if there are not any forbidden letters in word.” (“คำที่หลีกเลี่ยงสิ่งต้องห้ามหากไม่มีตัวอักษรต้องห้ามในคำ”)

การใช้ any กับนิพจน์ตัวสร้างจะมีประสิทธิภาพ เนื่องจากจะหยุดทันทีหากพบค่า True ดังนั้นจึงไม่ต้องประเมินลำดับทั้งหมด

ไพธอนมีฟังก์ชันในตัว all ที่คืนค่า True หากทุกองค์ประกอบของลำดับเป็น True เพื่อเป็นการฝึกหัด ให้ใช้ all เพื่อเขียนใหม่ uses_all จากส่วนที่ 9.3

19.5 เซต

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

def subtract(d1, d2):
    res = dict()
    for key in d1:
        if key not in d2:
            res[key] = None
    return res

ในดิกชันนารีทั้งหมดเหล่านี้ ค่าต่างๆ คือ None เนื่องจากเราไม่เคยใช้ค่าเหล่านี้ ส่งผลให้เราเสียพื้นที่จัดเก็บบางส่วน

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

ตัวอย่างเช่น การลบเซตสามารถใช้ได้เป็นเมธอดที่เรียกว่า difference หรือเป็นตัวดำเนินการ - เราก็เขียน subtract ใหม่ได้ดังนี้

def subtract(d1, d2):
    return set(d1) - set(d2)

ผลลัพธ์ที่ได้เป็นเซตแทนที่จะเป็นดิกชันนารี แต่สำหรับการดำเนินการเช่นการวนซ้ำ ลักษณะการทำงานจะเหมือนกัน

แบบฝึกหัดบางส่วนในหนังสือเล่มนี้สามารถทำได้อย่างกระชับและมีประสิทธิภาพด้วยเซต ตัวอย่างเช่น วิธีแก้ปัญหาสำหรับ has_duplicates จากแบบฝึกหัด แบบฝึกหัด 10.7 ที่ใช้ดิกชันนารี:

def has_duplicates(t):
    d = {}
    for x in t:
        if x in d:
            return True
        d[x] = True
    return False

เมื่อองค์ประกอบปรากฏขึ้นเป็นครั้งแรก องค์ประกอบนั้นจะถูกเพิ่มลงในดิกชันนารี หากองค์ประกอบเดิมปรากฏขึ้นอีกครั้ง ฟังก์ชันจะคืนค่า True

เมื่อใช้เซตเราสามารถเขียนฟังก์ชันเดียวกันได้ดังนี้

def has_duplicates(t):
    return len(set(t)) < len(t)

องค์ประกอบสามารถปรากฏในเซตได้เพียงครั้งเดียว ดังนั้นหากองค์ประกอบใน t ปรากฏมากกว่าหนึ่งครั้ง เซตจะเล็กกว่า t ถ้าไม่มีซ้ำ เซตจะมีขนาดเท่ากับ t

เรายังสามารถใช้เซตเพื่อทำแบบฝึกหัดในบทที่ 9 ได้อีกด้วย ตัวอย่างเช่น เวอร์ชันของ uses_only ที่มีลูป

def uses_only(word, available):
    for letter in word: 
        if letter not in available:
            return False
    return True

uses_only ตรวจสอบว่ามีตัวอักษรทั้งหมดใน word อยู่ใน available หรือไม่ เราสามารถเขียนใหม่ได้ดังนี้

def uses_only(word, available):
    return set(word) <= set(available)

ตัวดำเนินการ <= จะตรวจสอบว่าเซตใดเซตหนึ่งเป็นเซตย่อยหรือไม่ ซึ่งรวมถึงความเป็นไปได้ที่เซตดังกล่าวจะเท่ากัน ซึ่งจะเป็นจริงหากตัวอักษรทั้งหมดใน word ปรากฏใน available

เพื่อเป็นการฝึกหัด ให้เขียน avoids ใหม่โดยใช้เซต

19.6 เคาน์เตอร์ (Counter)

เคาน์เตอร์เป็นเหมือนเซต ยกเว้นว่าหากองค์ประกอบปรากฏขึ้นมากกว่าหนึ่งครั้ง เคาน์เตอร์จะติดตามจำนวนครั้งที่ปรากฏขึ้น หากคุณคุ้นเคยกับแนวคิดทางคณิตศาสตร์ของ มัลติเซ็ต (multiset) เคาน์เตอร์จะเป็นวิธีที่เป็นธรรมชาติในการแสดงมัลติเซ็ต

เคาน์เตอร์ถูกกำหนดในโมดูลมาตรฐานที่เรียกว่า collections ดังนั้นคุณต้องนำเข้าก่อน คุณสามารถเริ่มต้นเคาน์เตอร์ด้วยสตริง ลิสต์ หรืออะไรก็ได้ที่สนับสนุนการวนซ้ำ

>>> from collections import Counter
>>> count = Counter('parrot')
>>> count
Counter({'r': 2, 't': 1, 'o': 1, 'p': 1, 'a': 1})

เคาน์เตอร์ทำตัวเหมือนดิกชันนารีในหลายๆ ด้าน โดยจะจับคู่จากแต่ละคีย์กับจำนวนครั้งที่ปรากฏ เช่นเดียวกับในดิกชันนารี คีย์ต่างๆ จะต้องสามารถแฮชได้

ต่างจากดิกชันนารีตรงที่ เคาน์เตอร์จะไม่สร้างเอ็กเซ็ปชั่นหากคุณเข้าถึงองค์ประกอบที่ไม่ปรากฏ แต่จะคืนค่า 0

>>> count['d']
0

เราสามารถใช้เคาน์เตอร์ เพื่อเขียน is_anagram จากแบบฝึกหัด แบบฝึกหัด 10.6:

def is_anagram(word1, word2):
    return Counter(word1) == Counter(word2)

หากคำสองคำเป็นอนาแกรม คำเหล่านั้นมีตัวอักษรเดียวกันโดยมีจำนวนเท่ากัน ดังนั้นเคาน์เตอร์จึงเท่ากัน

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

>>> count = Counter('parrot')
>>> for val, freq in count.most_common(3):
...     print(val, freq)
r 2
p 1
a 1

19.7 ดิกต์ค่าเริ่มต้น

โมดูล collections ยังมี defaultdict ซึ่งเหมือนกับดิกชันนารี ยกเว้นว่าหากคุณเข้าถึงคีย์ที่ไม่มีอยู่ ก็จะสามารถสร้างค่าใหม่ได้ทันที

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

>>> from collections import defaultdict
>>> d = defaultdict(list)

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

>>> t = d['new key']
>>> t
[]

ลิสต์ใหม่ที่เราเรียกว่า t ถูกเพิ่มลงในดิกชันนารีด้วย ดังนั้นหากเราแก้ไข t การเปลี่ยนแปลงจะปรากฏใน d

>>> t.append('new value')
>>> d
defaultdict(<class 'list'>, {'new key': ['new value']})

หากคุณกำลังสร้างดิกชันนารีของลิสต์ คุณมักจะสามารถเขียนโค้ดที่ง่ายกว่าโดยใช้ defaultdict ในวิธีแก้ปัญหาของผมสำหรับแบบฝึกหัด แบบฝึกหัด 12.2 ซึ่งคุณสามารถหาได้จาก http://thinkpython2.com/code/anagram_sets.py ผมสร้างดิกชันนารีที่จับคู่จากสตริงตัวอักษรที่เรียงลำดับไปยังลิสต์คำที่สามารถสะกดด้วยตัวอักษรเหล่านั้นได้ ตัวอย่างเช่น 'opst' จะจับคู่กับลิสต์ ['opts', 'post', 'pots', 'spot', 'stop', 'tops']

นี่เป็นโปรแกรมดั้งเดิม

def all_anagrams(filename):
    d = {}
    for line in open(filename):
        word = line.strip().lower()
        t = signature(word)
        if t not in d:
            d[t] = [word]
        else:
            d[t].append(word)
    return d

โปรแกรมนี้ทำให้ง่ายขึ้นได้โดยใช้ setdefault ซึ่งคุณอาจใช้ในแบบฝึกหัดที่ แบบฝึกหัด 12.2:

def all_anagrams(filename):
    d = {}
    for line in open(filename):
        word = line.strip().lower()
        t = signature(word)
        d.setdefault(t, []).append(word)
    return d

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

เราสามารถหลีกเลี่ยงปัญหานี้และทำให้โปรแกรมง่ายขึ้นโดยใช้ defaultdict

def all_anagrams(filename):
    d = defaultdict(list)
    for line in open(filename):
        word = line.strip().lower()
        t = signature(word)
        d[t].append(word)
    return d

วิธีแก้ปัญหาของผมสำหรับแบบฝึกหัด 18.3 ซึ่งคุณสามารถดาวน์โหลดได้จาก http://thinkpython2.com/code/PokerHandSoln.py ใช้ setdefault ในฟังก์ชัน has_straightflush การแก้ปัญหาในโปรแกรมนี้มีข้อเสียเปรียบในการสร้างออบเจ็กต์ Hand ทุกครั้งที่วนซ้ำไม่ว่าจะจำเป็นหรือไม่ก็ตาม เพื่อเป็นการฝึกหัด ให้เขียนใหม่โดยใช้ defaultdict

19.8 เนมทูเพิล

ออบเจ็กต์อย่างง่ายจำนวนมากนั้นเป็นชุดของค่าที่เกี่ยวข้องกัน ตัวอย่างเช่น ออบเจ็กต์ Point ที่กำหนดไว้ในบทที่ 15 ประกอบด้วยตัวเลขสองตัวคือ x และ y เมื่อคุณกำหนดคลาสเช่นนี้ คุณมักจะเริ่มต้นด้วยเมธอด init และเมธอด str

class Point:
 
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
 
    def __str__(self):
        return '(%g, %g)' % (self.x, self.y)

นี่เป็นคำสั่งจำนวนมากในการถ่ายทอดข้อมูลจำนวนเล็กน้อย ไพธอนให้วิธีที่กระชับยิ่งขึ้นในการพูดสิ่งเดียวกัน:

from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])

อาร์กิวเมนต์แรกคือชื่อของคลาสที่คุณต้องการสร้าง อาร์กิวเมนต์ที่สองคือรายการของแอตทริบิวต์ที่ออบเจ็กต์ Point ควรมีในรูปสตริง ค่าส่งคืนจากเนมทูเพิลเป็นคลาสออบเจ๊คต์

>>> Point
<class '__main__.Point'>

Point จะจัดเตรียมเมธอดเช่น __init__ และ __str__ โดยอัตโนมัติ คุณจึงไม่ต้องเขียน

ในการสร้างออบเจ็กต์ Point คุณใช้คลาส Point เป็นฟังก์ชัน

>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)

เมธอด init กำหนดอาร์กิวเมนต์ให้กับแอตทริบิวต์โดยใช้ชื่อที่คุณระบุ เมธอด str พิมพ์คำบรรยายของออบเจ๊คต์ Point และแอตทริบิวต์

คุณสามารถเข้าถึงองค์ประกอบของเนมทูเพิลตามชื่อ

>>> p.x, p.y
(1, 2)

แต่คุณยังสามารถถือว่าเนมทูเพิลเป็นทูเพิลได้

>>> p[0], p[1]
(1, 2)
 
>>> x, y = p
>>> x, y
(1, 2)

เนมทูเพิลเป็นวิธีที่รวดเร็วในการกำหนดคลาสอย่างง่าย ข้อเสียคือคลาสธรรมดาไม่ได้เรียบง่ายเสมอไป คุณอาจตัดสินใจในภายหลังว่าคุณต้องการเพิ่มเมธอดให้กับ เนมทูเพิล ในกรณีนั้นคุณสามารถกำหนดคลาสใหม่ที่สืบทอดมาจากเนมทูเพิล

class Pointier(Point):
    # add more methods here

หรือคุณอาจเปลี่ยนไปใช้การประกาศคลาสแบบธรรมดาก็ได้

19.9 การรวบรวมพารามิเตอร์ด้วยคีย์เวิร์ด args

ในหัวข้อ 12.4 เราได้เห็นวิธีเขียนฟังก์ชันที่รวบรวมอาร์กิวเมนต์เป็นทูเพิล

def printall(*args):
    print(args)

คุณสามารถเรียกใช้ฟังก์ชันนี้ด้วยอาร์กิวเมนต์ตำแหน่งจำนวนเท่าใดก็ได้ (นั่นคือ อาร์กิวเมนต์ที่ไม่มีคีย์เวิร์ด)

>>> printall(1, 2.0, '3')
(1, 2.0, '3')

แต่ตัวดำเนินการ ไม่ได้รวบรวมอาร์กิวเมนต์คีย์เวิร์ด

>>> printall(1, 2.0, third='3')
TypeError: printall() got an unexpected keyword argument 'third'

ในการรวบรวมอาร์กิวเมนต์คีย์เวิร์ด คุณสามารถใช้ตัวดำเนินการ *

def printall(*args, **kwargs):
    print(args, kwargs)

คุณสามารถรวบรวมพารามิเตอร์คีย์เวิร์ดอะไรก็ได้ที่คุณต้องการ แต่ kwargs เป็นตัวเลือกทั่วไป ผลลัพธ์คือดิกชันนารีที่จับคู่คีย์เวิร์ดกับค่า

>>> printall(1, 2.0, third='3')
(1, 2.0) {'third': '3'}

หากคุณมีดิกชันนารีของคีย์เวิร์ดและค่า คุณสามารถใช้ตัวดำเนินการกระจาย ** เพื่อเรียกใช้ฟังก์ชัน

>>> d = dict(x=1, y=2)
>>> Point(**d)
Point(x=1, y=2)

หากไม่มีตัวดำเนินการกระจาย ฟังก์ชันจะถือว่า d เป็นอาร์กิวเมนต์ตำแหน่งเดียว ดังนั้นจะกำหนด d ให้กับ x และ จะแสดงข้อผิดพลาดเพราะไม่มีอะไรจะกำหนดให้กับ y

>>> d = dict(x=1, y=2)
>>> Point(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() missing 1 required positional argument: 'y'

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

19.10 อภิธานศัพท์

  • นิพจน์เงื่อนไข (conditional expression): นิพจน์ที่มีค่าหนึ่งในสองค่า ขึ้นอยู่กับเงื่อนไข
  • การสรุปความลิสต์ (list comprehension): นิพจน์ที่มี for วนซ้ำในวงเล็บเหลี่ยมที่ให้ลิสต์ใหม่
  • นิพจน์ตัวสร้าง (generator expression): นิพจน์ที่มี for วนซ้ำในวงเล็บที่ให้ผลลัพธ์เป็นออบเจ๊คต์ตัวสร้าง
  • มัลติเซต (multiset): เอกลักษณ์ทางคณิตศาสตร์ที่แสดงถึงการจับคู่ระหว่างอีลีเมนต์ของเซตและจำนวนครั้งที่ปรากฏขึ้น
  • แฟคทอรี่ (factory): ฟังก์ชันซึ่งมักจะถูกส่งผ่านเป็นพารามิเตอร์ เพื่อใช้สร้างออบเจ๊คต์

19.11 แบบฝึกหัด

แบบฝึกหัด 1
ต่อไปนี้เป็นฟังก์ชันคำนวณสัมประสิทธิ์ทวินามแบบเรียกซ้ำ

def binomial_coeff(n, k):
    """Compute the binomial coefficient "n choose k".
 
    n: number of trials
    k: number of successes
 
    returns: int
    """
    if k == 0:
        return 1
    if n == 0:
        return 0
 
    res = binomial_coeff(n-1, k) + binomial_coeff(n-1, k-1)
    return res

เขียนเนื้อความของฟังก์ชันใหม่โดยใช้นิพจน์เงื่อนไขที่ซ้อนกัน

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

https://greenteapress.com/thinkpython2/html/thinkpython2020.html

python/goodies.txt · Last modified: 2021/08/30 09:55 (external edit)

Page Tools