การตรวจสอบโค้ดที่สะอาด: ดูที่ Python, Parameterized

เผยแพร่แล้ว: 2022-03-11

ในโพสต์นี้ ฉันจะพูดถึงสิ่งที่ฉันคิดว่าเป็นเทคนิคหรือรูปแบบที่สำคัญที่สุดในการสร้างโค้ด Pythonic ที่สะอาดหมดจด กล่าวคือ การกำหนดพารามิเตอร์ โพสต์นี้เหมาะสำหรับคุณหาก:

  • คุณค่อนข้างใหม่กับรูปแบบการออกแบบทั้งหมดและอาจสับสนเล็กน้อยกับชื่อรูปแบบและไดอะแกรมคลาสยาว ข่าวดีก็คือมีรูปแบบการออกแบบเพียงรูปแบบเดียวเท่านั้นที่คุณต้องรู้สำหรับ Python ยิ่งไปกว่านั้น คุณอาจจะรู้อยู่แล้ว แต่อาจไม่ใช่ทุกวิธีที่สามารถนำมาใช้ได้
  • คุณมาที่ Python จากภาษา OOP อื่น เช่น Java หรือ C# และต้องการทราบวิธีการแปลความรู้ของคุณเกี่ยวกับรูปแบบการออกแบบจากภาษานั้นไปยัง Python ใน Python และภาษาที่พิมพ์แบบไดนามิกอื่นๆ รูปแบบต่างๆ ที่พบบ่อยในภาษา OOP ที่พิมพ์แบบสแตติกนั้น “มองไม่เห็นหรือง่ายกว่า” ตามที่ผู้เขียน Peter Norvig กล่าวไว้

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

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

กรณีที่ง่ายที่สุดของ Python Parameterized

สำหรับตัวอย่างส่วนใหญ่ เราจะใช้โมดูลเต่าไลบรารีมาตรฐานการสอนสำหรับการทำกราฟิก

นี่คือรหัสบางส่วนที่จะวาดสี่เหลี่ยมขนาด 100x100 โดยใช้ turtle :

 from turtle import Turtle turtle = Turtle() for i in range(0, 4): turtle.forward(100) turtle.left(90)

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

 def draw_square(size): for i in range(0, 4): turtle.forward(size) turtle.left(90) draw_square(100)

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

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

 from turtle import Turtle def draw_square(turtle, size): for i in range(0, 4): turtle.forward(size) turtle.left(90) turtle = Turtle() draw_square(turtle, 100)

มีชื่อแฟนซี - การพึ่งพาอาศัยกัน หมายความว่าหากฟังก์ชันต้องการวัตถุบางอย่างเพื่อทำงาน เช่น draw_square ต้องการ Turtle ผู้เรียกมีหน้าที่ส่งผ่านวัตถุนั้นเป็นพารามิเตอร์ ไม่ จริงๆ หากคุณเคยสงสัยเกี่ยวกับการฉีดพึ่งพา Python นี่แหละ

จนถึงตอนนี้ เราได้จัดการกับการใช้งานพื้นฐานสองแบบ ข้อสังเกตที่สำคัญสำหรับส่วนที่เหลือของบทความนี้คือ ใน Python มีสิ่งต่างๆ มากมายที่สามารถกลายเป็นพารามิเตอร์ได้ มากกว่าในภาษาอื่นบางภาษา และทำให้เป็นเทคนิคที่ทรงพลังมาก

อะไรก็ได้ที่เป็นวัตถุ

ใน Python คุณสามารถใช้เทคนิคนี้เพื่อสร้างพารามิเตอร์อะไรก็ได้ที่เป็นอ็อบเจกต์ และใน Python สิ่งที่คุณเจอส่วนใหญ่เป็นอ็อบเจกต์ ซึ่งรวมถึง:

  • อินสแตนซ์ของประเภทในตัว เช่น สตริง "I'm a string" และจำนวนเต็ม 42 หรือพจนานุกรม
  • อินสแตนซ์ของประเภทและคลาสอื่นๆ เช่น datetime.datetime object
  • หน้าที่และวิธีการ
  • ชนิดในตัวและคลาสที่กำหนดเอง

สองข้อสุดท้ายเป็นสิ่งที่น่าประหลาดใจที่สุด โดยเฉพาะอย่างยิ่งถ้าคุณมาจากภาษาอื่น และพวกเขาต้องการการอภิปรายเพิ่มเติม

ทำหน้าที่เป็นพารามิเตอร์

คำสั่งฟังก์ชันใน Python ทำสองสิ่ง:

  1. มันสร้างวัตถุฟังก์ชั่น
  2. มันสร้างชื่อในขอบเขตท้องถิ่นที่ชี้ไปที่วัตถุนั้น

เราสามารถเล่นกับวัตถุเหล่านี้ใน REPL:

 > >> def foo(): ... return "Hello from foo" > >> > >> foo() 'Hello from foo' > >> print(foo) <function foo at 0x7fc233d706a8> > >> type(foo) <class 'function'> > >> foo.name 'foo'

และเช่นเดียวกับอ็อบเจกต์อื่นๆ เราสามารถกำหนดฟังก์ชันให้กับตัวแปรอื่นๆ ได้:

 > >> bar = foo > >> bar() 'Hello from foo'

โปรดทราบว่า bar เป็นชื่ออื่นสำหรับวัตถุเดียวกัน ดังนั้นจึงมีคุณสมบัติ __name__ ภายในเหมือนเมื่อก่อน:

 > >> bar.name 'foo' > >> bar <function foo at 0x7fc233d706a8>

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

สมมติว่าเราขยายฟังก์ชันการวาดสี่เหลี่ยมด้านบน และบางครั้งเมื่อเราวาดสี่เหลี่ยม เราต้องการที่จะหยุดที่แต่ละมุม—การเรียก time.sleep()

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

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

 def do_nothing(turtle, size): pass def draw_square(turtle, size, at_corner=do_nothing): for i in range(0, 4): turtle.forward(size) at_corner(turtle, size) turtle.left(90) def pause(turtle, size): time.sleep(5) turtle = Turtle() draw_square(turtle, 100, at_corner=pause)

หรือเราอาจทำบางอย่างที่เจ๋งกว่านี้หน่อย เช่น วาดสี่เหลี่ยมเล็กๆ ซ้ำๆ ในแต่ละมุม:

 def smaller_square(turtle, size): if size < 10: return draw_square(turtle, size / 2, at_corner=smaller_square) draw_square(turtle, 128, at_corner=smaller_square) 

ภาพประกอบของสี่เหลี่ยมเล็ก ๆ ที่วาดซ้ำ ๆ ตามที่แสดงในโค้ดพารามิเตอร์ python ด้านบน

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

ในภาษาอื่น…

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

ทางออกหนึ่งคือเปลี่ยน draw_square เป็นคลาส SquareDrawer :

 class SquareDrawer: def __init__(self, size): self.size = size def draw(self, t): for i in range(0, 4): t.forward(self.size) self.at_corner(t, size) t.left(90) def at_corner(self, t, size): pass

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

แม้ว่าบางครั้งอาจมีประโยชน์ใน Python แต่การดึงรหัสตัวแปรออกมาในฟังก์ชันที่ส่งผ่านอย่างง่าย ๆ เนื่องจากพารามิเตอร์มักจะง่ายกว่ามาก

วิธีที่สองที่เราอาจแก้ปัญหานี้ในภาษาที่ไม่มีฟังก์ชันชั้นหนึ่งคือการรวมฟังก์ชันของเราเป็นเมธอดภายในคลาส เช่นนี้

 class DoNothing: def run(self, turtle, size): pass def draw_square(turtle, size, at_corner=DoNothing()): for i in range(0, 4): turtle.forward(size) at_corner.run(turtle, size) t.left(90) class Pauser: def run(self, turtle, size): time.sleep(5) draw_square(turtle, 100, at_corner=Pauser())

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

Callables อื่น ๆ

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

สมมติว่าเรามีรายการ foo :

 foo = [1, 2, 3]

ตอนนี้ foo มีเมธอดมากมายแนบอยู่ เช่น .append() และ .count() “วิธีการผูกมัด” เหล่านี้สามารถส่งผ่านและใช้เหมือนกับฟังก์ชัน:

 > >> appendtofoo = foo.append > >> appendtofoo(4) > >> foo [1, 2, 3, 4]

นอกจากเมธอดของอินสแตนซ์เหล่านี้ ยังมีอ็อบเจ็กต์ที่เรียกได้ประเภทอื่นๆ— staticmethods และ classmethods , อินสแตนซ์ของคลาสที่ใช้ __call__ และคลาส/ประเภทเอง

คลาสเป็นพารามิเตอร์

ใน Python คลาสคือ "เฟิร์สคลาส"—เป็นอ็อบเจกต์รันไทม์เหมือนกับ dicts, strings ฯลฯ ซึ่งอาจดูแปลกกว่าฟังก์ชันที่เป็นอ็อบเจกต์ แต่โชคดีที่มันง่ายกว่าที่จะแสดงให้เห็นความจริงข้อนี้มากกว่าสำหรับฟังก์ชัน

คำสั่งคลาสที่คุณคุ้นเคยเป็นวิธีที่ดีในการสร้างคลาส แต่ไม่ใช่วิธีเดียว—เรายังสามารถใช้ประเภทอาร์กิวเมนต์สามเวอร์ชันได้ สองข้อความต่อไปนี้ทำสิ่งเดียวกันทุกประการ:

 class Foo: pass Foo = type('Foo', (), {})

ในเวอร์ชันที่สอง ให้สังเกตสองสิ่งที่เราเพิ่งทำ (ซึ่งสะดวกกว่าโดยใช้คำสั่งของชั้นเรียน):

  1. ทางด้านขวามือของเครื่องหมายเท่ากับ เราได้สร้างคลาสใหม่ โดยใช้ชื่อภายในของ Foo นี่คือชื่อที่คุณจะได้กลับมาถ้าคุณทำ Foo.__name__
  2. ด้วยการมอบหมายงาน เราจึงสร้างชื่อในขอบเขตปัจจุบัน Foo ซึ่งหมายถึงวัตถุของชั้นเรียนที่เราเพิ่งสร้างขึ้น

เราทำข้อสังเกตแบบเดียวกันสำหรับสิ่งที่คำสั่งฟังก์ชันทำ

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

เราสามารถแบ่งออกเป็นการใช้งานได้หลายอย่าง:

ชั้นเรียนเป็นโรงงาน

คลาสเป็นวัตถุที่เรียกได้ซึ่งสร้างอินสแตนซ์ของตัวเอง:

 > >> class Foo: ... pass > >> Foo() <__main__.Foo at 0x7f73e0c96780>

และในฐานะที่เป็นวัตถุ สามารถกำหนดให้กับตัวแปรอื่นๆ ได้:

 > >> myclass = Foo > >> myclass() <__main__.Foo at 0x7f73e0ca93c8>

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

 def draw_square(x, y, size): turtle = Turtle() turtle.penup() # Don't draw while moving to the start position turtle.goto(x, y) turtle.pendown() for i in range(0, 4): turtle.forward(size) turtle.left(90)

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

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

 def draw_square(x, y, size, make_turtle=Turtle): turtle = make_turtle() turtle.penup() turtle.goto(x, y) turtle.pendown() for i in range(0, 4): turtle.forward(size) turtle.left(90)

ในการใช้สิ่งนี้ เราสามารถเขียนฟังก์ชัน make_turtle ที่สร้างเต่าและแก้ไขมัน สมมติว่าเราต้องการซ่อนเต่าเมื่อวาดสี่เหลี่ยม:

 def make_hidden_turtle(): turtle = Turtle() turtle.hideturtle() return turtle draw_square(5, 10, 20, make_turtle=make_hidden_turtle)

หรือเราสามารถ subclass Turtle เพื่อให้พฤติกรรมนั้นสร้างขึ้นและส่งผ่าน subclass เป็นพารามิเตอร์:

 class HiddenTurtle(Turtle): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.hideturtle() draw_square(5, 10, 20, make_turtle=HiddenTurtle)

ในภาษาอื่น…

ภาษา OOP อื่นๆ อีกหลายภาษา เช่น Java และ C# ไม่มีคลาสระดับเฟิร์สคลาส หากต้องการยกตัวอย่างคลาส คุณต้องใช้คีย์เวิร์ด new ตามด้วยชื่อคลาสจริง

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

คลาสเป็นคลาสพื้นฐาน

สมมติว่าเราสร้างคลาสย่อยเพื่อเพิ่มคุณสมบัติเดียวกันในคลาสต่างๆ ตัวอย่างเช่น เราต้องการคลาสย่อย Turtle ที่จะเขียนลงในบันทึกเมื่อถูกสร้างขึ้น:

 import logging logger = logging.getLogger() class LoggingTurtle(Turtle): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) logger.debug("Turtle got created")

แต่แล้ว เราก็พบว่าตัวเองทำสิ่งเดียวกันกับชั้นเรียนอื่น:

 class LoggingHippo(Hippo): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) logger.debug("Hippo got created")

สิ่งเดียวที่แตกต่างกันระหว่างสองสิ่งนี้คือ:

  1. คลาสพื้นฐาน
  2. ชื่อของคลาสย่อย—แต่เราไม่ได้สนใจเรื่องนั้นจริงๆ และสามารถสร้างได้โดยอัตโนมัติจากแอตทริบิวต์คลาสฐาน __name__
  3. ชื่อที่ใช้ในการโทร debug —แต่อีกครั้ง เราสามารถสร้างสิ่งนี้ได้จากชื่อคลาสพื้นฐาน

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

 def make_logging_class(cls): class LoggingThing(cls): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) logger.debug("{0} got created".format(cls.__name__)) LoggingThing.__name__ = "Logging{0}".format(cls.__name__) return LoggingThing LoggingTurtle = make_logging_class(Turtle) LoggingHippo = make_logging_class(Hippo)

เรามีการสาธิตคลาสเฟิร์สคลาส:

  • เราส่งคลาสไปยังฟังก์ชัน โดยให้พารามิเตอร์เป็นชื่อธรรมดา cls เพื่อหลีกเลี่ยงความขัดแย้งกับ class ของคีย์เวิร์ด (คุณจะเห็น class_ และ klass ใช้เพื่อจุดประสงค์นี้ด้วย)
  • ภายในฟังก์ชัน เราสร้างคลาส โปรดทราบว่าทุกครั้งที่เรียกใช้ฟังก์ชันนี้จะสร้างคลาส ใหม่
  • เราส่งคืนคลาสนั้นเป็นค่าส่งคืนของฟังก์ชัน

นอกจากนี้เรายังตั้งค่า LoggingThing.__name__ ซึ่งเป็นทางเลือกทั้งหมด แต่สามารถช่วยแก้ไขจุดบกพร่องได้

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

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

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

คลาสเป็นข้อยกเว้น

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

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

 import time def retry_with_backoff(action, exceptions_to_catch, max_attempts=10, attempts_so_far=0): try: return action() except exceptions_to_catch: attempts_so_far += 1 if attempts_so_far >= max_attempts: raise else: time.sleep(attempts_so_far ** 2) return retry_with_backoff(action, exceptions_to_catch, attempts_so_far=attempts_so_far, max_attempts=max_attempts)

เราได้ดึงทั้งการกระทำที่ต้องทำและข้อยกเว้นที่จับมาเป็นพารามิเตอร์ พารามิเตอร์ exceptions_to_catch สามารถเป็นคลาสเดียว เช่น httplib.client.HTTPConnectionError IOError ทูเพิลของคลาสดังกล่าว (เราต้องการหลีกเลี่ยงส่วนคำสั่ง "bare ยกเว้น" หรือแม้แต่ except Exception เนื่องจากเป็นที่รู้กันว่าซ่อนข้อผิดพลาดในการเขียนโปรแกรมอื่น ๆ ไว้)

คำเตือนและข้อสรุป

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

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

บางครั้งโค้ดที่ "ซ้ำซ้อน" เล็กน้อยก็ดีกว่าปัญหาเหล่านี้มาก ดังนั้นให้ใช้เทคนิคนี้อย่างระมัดระวัง

ในโพสต์นี้ เราได้กล่าวถึงรูปแบบการออกแบบที่เรียกว่า การเติมพึ่งพา , กลยุทธ์ , วิธีเทมเพลต , โรงงานนามธรรม , วิธีโรงงาน , และ มัณฑนากร ใน Python สิ่งเหล่านี้หลายๆ อย่างกลายเป็นแอปพลิเคชั่นง่ายๆ ของการกำหนดพารามิเตอร์หรือไม่จำเป็นอย่างแน่นอนเนื่องจากข้อเท็จจริงที่ว่าพารามิเตอร์ใน Python สามารถเรียกอ็อบเจกต์หรือคลาสได้ หวังว่านี่จะช่วยแบ่งเบาภาระแนวคิดของ "สิ่งที่คุณควรรู้ในฐานะนักพัฒนา Python ตัวจริง" และช่วยให้คุณสามารถเขียนโค้ด Pythonic ที่กระชับได้!

อ่านเพิ่มเติม:

  • รูปแบบการออกแบบหลาม: สำหรับโค้ดที่เก๋ไก๋และทันสมัย
  • รูปแบบหลาม: สำหรับรูปแบบการออกแบบหลาม
  • การบันทึก Python: บทช่วยสอนเชิงลึก