ฉันเจอคำว่าการพิมพ์เป็ดในขณะที่อ่านหัวข้อสุ่มบนซอฟต์แวร์ออนไลน์และไม่เข้าใจอย่างสมบูรณ์
"การพิมพ์เป็ด" คืออะไร?
ฉันเจอคำว่าการพิมพ์เป็ดในขณะที่อ่านหัวข้อสุ่มบนซอฟต์แวร์ออนไลน์และไม่เข้าใจอย่างสมบูรณ์
"การพิมพ์เป็ด" คืออะไร?
คำตอบ:
มันเป็นคำที่ใช้ในภาษาแบบไดนามิกที่ไม่ได้มีการพิมพ์ที่แข็งแกร่ง
แนวคิดคือคุณไม่จำเป็นต้องพิมพ์เพื่อเรียกใช้เมธอดที่มีอยู่บนวัตถุ - หากมีการกำหนดเมธอดไว้คุณสามารถเรียกใช้ได้
ชื่อนี้มาจากวลี "ถ้ามันดูเหมือนเป็ดและต้มตุ๋นเหมือนเป็ดมันก็เป็นเป็ด"
Wikipediaมีข้อมูลมากมาย
การพิมพ์เป็ด หมายความว่าการดำเนินการไม่ได้ระบุข้อกำหนดอย่างเป็นทางการที่ตัวถูกดำเนินการต้องปฏิบัติ แต่เพียงลองใช้สิ่งที่ได้รับ
ไม่เหมือนกับสิ่งที่คนอื่นพูดนี่ไม่จำเป็นต้องเกี่ยวข้องกับภาษาแบบไดนามิกหรือปัญหาการสืบทอด
ตัวอย่างงาน:เรียกวิธีการบางอย่างQuack
บนวัตถุ
โดยไม่ต้องใช้เป็ดพิมพ์, ฟังก์ชั่นการทำงานนี้มีการระบุล่วงหน้าว่าอาร์กิวเมนต์มีการสนับสนุนวิธีการบางอย่างf
Quack
วิธีทั่วไปคือการใช้อินเทอร์เฟซ
interface IQuack {
void Quack();
}
void f(IQuack x) {
x.Quack();
}
การโทรf(42)
ล้มเหลว แต่f(donald)
ทำงานได้นานตราบเท่าที่donald
เป็นอินสแตนซ์ของIQuack
-subtype
อีกวิธีคือการพิมพ์โครงสร้าง - แต่อีกครั้งวิธีการที่Quack()
ระบุไว้อย่างเป็นทางการทุกสิ่งที่ไม่สามารถพิสูจน์ได้ว่ามันquack
จะทำให้เกิดความล้มเหลวในการรวบรวมล่วงหน้า
def f(x : { def Quack() : Unit }) = x.Quack()
เราสามารถเขียนได้
f :: Quackable a => a -> IO ()
f = quack
ใน Haskell โดยที่Quackable
typeclass ช่วยให้แน่ใจว่ามีวิธีการของเรา
อย่างที่ฉันได้กล่าวไปแล้วว่าระบบการพิมพ์เป็ดไม่ได้ระบุข้อกำหนด แต่เพียงลองใช้งานถ้ามีอะไรเกิดขึ้น
ดังนั้นระบบพิมพ์แบบไดนามิกที่ Python ใช้การพิมพ์แบบเป็ดเสมอ:
def f(x):
x.Quack()
หากf
ได้รับการx
สนับสนุนQuack()
ทุกอย่างก็โอเคถ้าไม่มันจะพังเมื่อรันไทม์
แต่การพิมพ์เป็ดไม่ได้หมายถึงการพิมพ์แบบไดนามิกเลยจริงๆแล้วมีวิธีการพิมพ์เป็ดที่ได้รับความนิยมอย่างมาก
template <typename T>
void f(T x) { x.Quack(); }
ฟังก์ชั่นไม่ได้บอกในทางที่มันต้องการบางอย่างx
ที่สามารถทำได้Quack
ดังนั้นแทนที่จะพยายามรวบรวมเวลาและถ้าทุกอย่างใช้งานได้มันก็โอเค
def f(x)
def f(IQuack x)
การอภิปรายความหมายของคำถามนั้นค่อนข้างเหมาะสม (และเป็นเรื่องทางวิชาการ) แต่เป็นแนวคิดทั่วไป:
พิมพ์ดีดเป็ด
(“ ถ้ามันเดินเหมือนเป็ดและ quacks เหมือนเป็ดก็เป็นเป็ด”) - ใช่! แต่นั่นหมายความว่ายังไง ?? นี่คือตัวอย่างที่ดีที่สุดโดย:
ตัวอย่างการทำงานของ Duck Typing:
ลองนึกภาพฉันมีไม้เท้าวิเศษ มันมีพลังพิเศษ ถ้าฉันโบกมือพูดว่า"ขับ!" สำหรับรถยนต์แล้วมันก็ขับ!
มันทำงานกับสิ่งอื่น ๆ ? ไม่แน่ใจ: ดังนั้นฉันลองบนรถบรรทุก ว้าว - มันก็เหมือนกัน! จากนั้นฉันก็ลองใช้บนเครื่องบินรถไฟและ 1 วูดส์ (เป็นไม้กอล์ฟชนิดหนึ่งที่ผู้คนใช้ในการ 'ขับ' ลูกกอล์ฟ) พวกเขาทั้งหมดขับรถ!
แต่มันจะใช้งานได้ไหมว่าเป็นถ้วยน้ำชา? ข้อผิดพลาด: KAAAA-BOOOOOOM! ที่ไม่ได้ผลดีมาก ====> ถ้วยชาขับไม่ได้ !! duh !?
นี่เป็นแนวคิดของการพิมพ์เป็ด มันเป็นลองก่อนที่คุณซื้อ ระบบ ถ้ามันใช้ได้ดีทั้งหมด แต่ถ้ามันล้มเหลวเหมือนระเบิดมือยังคงอยู่ในมือของคุณมันจะระเบิดขึ้นที่หน้าของคุณ
ในคำอื่น ๆ ที่เรามีความสนใจในสิ่งที่วัตถุที่สามารถทำได้มากกว่ากับสิ่งที่เป็นวัตถุ
ตัวอย่าง: ภาษาที่พิมพ์แบบคงที่
หากเราเกี่ยวข้องกับสิ่งที่เป็นวัตถุจริง ๆแล้วเวทมนต์ของเราจะทำงานเฉพาะในประเภทที่ได้รับอนุญาตในรถยนต์คันนี้ แต่จะล้มเหลวในวัตถุอื่น ๆ ที่สามารถขับได้ : รถบรรทุก, รถจักรยานยนต์, รถตุ๊กตุ๊ก ฯลฯ มันไม่ทำงานบนรถบรรทุกเพราะไม้เท้าของเราคาดหวังว่ามันจะใช้ได้กับรถยนต์เท่านั้น
กล่าวอีกนัยหนึ่งในสถานการณ์นี้ไม้กายสิทธิ์มองอย่างใกล้ชิดในสิ่งที่วัตถุคือ (เป็นรถหรือไม่) มากกว่าสิ่งที่วัตถุสามารถทำได้ (เช่นรถยนต์รถบรรทุก ฯลฯ สามารถขับได้)
วิธีเดียวที่คุณจะได้รถบรรทุกเพื่อขับคือถ้าคุณสามารถใช้ไม้เรียวเพื่อคาดหวังว่าทั้งรถบรรทุกและรถยนต์ (อาจใช้ "การใช้อินเทอร์เฟซทั่วไป") หากคุณไม่ทราบความหมายนั้นก็ให้เพิกเฉยสักครู่
สรุป: กุญแจนำออก
สิ่งที่สำคัญในการพิมพ์เป็ดเป็นสิ่งที่วัตถุจริงสามารถทำมากกว่าสิ่งที่วัตถุที่เป็น
พิจารณาว่าคุณกำลังออกแบบฟังก์ชั่นที่เรียบง่ายซึ่งได้รับวัตถุประเภทBird
และเรียกwalk()
วิธีการของมัน มีสองวิธีที่คุณสามารถนึกถึง:
Bird
มิฉะนั้นรหัสของพวกเขาจะไม่คอมไพล์ หากใครต้องการที่จะใช้ฟังก์ชั่นของฉันเขาจะต้องทราบว่าฉันเท่านั้นที่ยอมรับBird
sobjects
และฉันก็เรียกwalk()
วิธีการของวัตถุ ดังนั้นถ้าobject
สามารถwalk()
ถูกต้องหากไม่สามารถทำงานของฉันจะล้มเหลว ดังนั้นที่นี่มันไม่สำคัญว่าวัตถุจะเป็นBird
หรืออย่างอื่นมันเป็นสิ่งสำคัญที่มันสามารถwalk()
(นี่คือการพิมพ์เป็ด )จะต้องพิจารณาว่าการพิมพ์เป็ดอาจมีประโยชน์ในบางกรณีเช่น Python ใช้การพิมพ์เป็ดมาก
Wikipedia มีคำอธิบายโดยละเอียดอย่างเป็นธรรม:
http://en.wikipedia.org/wiki/Duck_typing
การพิมพ์เป็ดเป็นรูปแบบของการพิมพ์แบบไดนามิกที่ชุดวิธีการและคุณสมบัติปัจจุบันของวัตถุเป็นตัวกำหนดความหมายที่ถูกต้องมากกว่าการสืบทอดจากคลาสเฉพาะหรือการใช้อินเทอร์เฟซเฉพาะ
หมายเหตุสำคัญมีความเป็นไปได้ที่การพิมพ์เป็ดนักพัฒนาจะเกี่ยวข้องกับชิ้นส่วนของวัตถุที่ถูกบริโภคมากกว่าประเภทที่แท้จริงที่แท้จริง
ฉันเห็นคำตอบมากมายที่ซ้ำสำนวนเก่า:
ถ้ามันดูเหมือนเป็ดและต้มตุ๋นเหมือนเป็ดมันก็เป็นเป็ด
จากนั้นดำดิ่งสู่คำอธิบายว่าคุณสามารถทำอะไรกับการพิมพ์เป็ดหรือตัวอย่างที่ดูเหมือนจะทำให้สับสนกับแนวคิดต่อไป
ฉันไม่พบความช่วยเหลือมากนัก
นี่เป็นความพยายามที่ดีที่สุดในการตอบคำถามภาษาอังกฤษแบบธรรมดาเกี่ยวกับการพิมพ์เป็ดที่ฉันได้พบ:
การพิมพ์ดีดเป็ดหมายถึงวัตถุที่ถูกกำหนดโดยสิ่งที่มันสามารถทำได้ไม่ได้โดยสิ่งที่มันเป็น
ซึ่งหมายความว่าเราไม่เกี่ยวข้องกับคลาส / ชนิดของวัตถุและเกี่ยวข้องกับวิธีการใดที่สามารถเรียกใช้บนมันและการดำเนินการใดที่สามารถทำได้บนมัน เราไม่ได้ดูแลเกี่ยวกับเรื่องของชนิดเราดูแลเกี่ยวกับสิ่งที่สามารถทำได้
พิมพ์เป็ด:
หากมันพูดและเดินเหมือนเป็ดเป็ดก็จะเป็นเช่นนั้น
นี้มักจะเรียกว่าการลักพาตัว ( abductiveเหตุผลหรือเรียกอีกอย่างว่าretroductionความหมายที่ชัดเจนผมคิดว่า):
จากC (สรุป, สิ่งที่เราเห็น ) และR (กฎ, สิ่งที่เรารู้ ), เรายอมรับ / ตัดสินใจ / ถือว่าP (สถานที่, คุณสมบัติ ) ในคำอื่น ๆ ความจริงที่กำหนด
... พื้นฐานการวินิจฉัยทางการแพทย์
กับเป็ด: C = เดินพูดคุย , R = เหมือนเป็ด , P = มันเป็นเป็ด
กลับไปที่การเขียนโปรแกรม:
วัตถุoมีเมธอด / คุณสมบัติmp1และอินเตอร์เฟส / ชนิดT ต้องการ / กำหนดmp1
วัตถุoมีเมธอด / คุณสมบัติmp2 และอินเตอร์เฟส / ประเภทTต้องการ / กำหนดmp2
...
ดังนั้นมากกว่าแค่ยอมรับmp1 ... บนวัตถุใด ๆ ตราบเท่าที่มันตรงกับคำจำกัดความของmp1 ... , คอมไพเลอร์ / รันไทม์ก็ควรจะโอเคกับการยืนยันoเป็นประเภทT
และเป็นกรณีตัวอย่างข้างต้น? การพิมพ์ Duck นั้นไม่มีการพิมพ์เลยใช่ไหม? หรือเราควรเรียกมันว่าการพิมพ์โดยนัย?
การดูภาษานั้นอาจช่วยได้ มันมักจะช่วยฉัน (ฉันไม่ใช่เจ้าของภาษา)
ในduck typing
:
1) คำtyping
ไม่ได้หมายถึงการพิมพ์บนแป้นพิมพ์ (เช่นเดียวกับที่เป็นภาพติดตาในใจของฉัน) มันหมายถึงการพิจารณา " สิ่งที่ประเภทของสิ่งที่เป็นสิ่งที่? "
2) คำนี้duck
แสดงให้เห็นว่าการกำหนดนั้นทำได้อย่างไร; มันเป็นการตัดสินใจแบบ 'หลวม' เช่นเดียวกับใน: " ถ้ามันเดินเหมือนเป็ด ... มันก็เป็นเป็ด " มัน 'หลวม' เพราะสิ่งนี้อาจเป็นเป็ดหรืออาจจะไม่ แต่จริง ๆ แล้วมันเป็นเป็ดไม่สำคัญ สิ่งสำคัญคือฉันสามารถทำอะไรกับมันได้ฉันสามารถทำอะไรกับเป็ดได้และคาดว่าพฤติกรรมที่แสดงออกโดยเป็ด ฉันสามารถให้อาหารมันกับเศษขนมปังและสิ่งที่อาจเข้าหาฉันหรือพุ่งเข้าใส่ฉันหรือถอยออกไป ... แต่มันจะไม่กลืนกินฉันเหมือนอย่างหงุดหงิด
ฉันรู้ว่าฉันไม่ได้ให้คำตอบทั่วไป ในทับทิมเราจะไม่ประกาศประเภทของตัวแปรหรือวิธีการ - ทุกอย่างเป็นเพียงวัตถุบางชนิด ดังนั้นกฎคือ "คลาสที่ไม่ใช่ประเภท"
ใน Ruby คลาสไม่เคย (OK, แทบไม่เคย) ประเภท แต่จะกำหนดชนิดของวัตถุให้มากขึ้นโดยสิ่งที่วัตถุนั้นสามารถทำได้ ในทับทิมเราเรียกเป็ดตัวนี้ว่า หากวัตถุเดินเหมือนเป็ดและพูดเหมือนเป็นเป็ดล่ามก็ยินดีที่จะปฏิบัติกับมันราวกับว่าเป็นเป็ด
ตัวอย่างเช่นคุณอาจกำลังเขียนชุดคำสั่งเพื่อเพิ่มข้อมูลเพลงลงในสตริง หากคุณมาจากพื้นหลัง C # หรือ Java คุณอาจถูกล่อลวงให้เขียนสิ่งนี้:
def append_song(result, song)
# test we're given the right parameters
unless result.kind_of?(String)
fail TypeError.new("String expected") end
unless song.kind_of?(Song)
fail TypeError.new("Song expected")
end
result << song.title << " (" << song.artist << ")" end
result = ""
append_song(result, song) # => "I Got Rhythm (Gene Kelly)"
ยอมรับการพิมพ์เป็ดของ Ruby และคุณจะเขียนอะไรที่ง่ายกว่านี้:
def append_song(result, song)
result << song.title << " (" << song.artist << ")"
end
result = ""
append_song(result, song) # => "I Got Rhythm (Gene Kelly)"
คุณไม่จำเป็นต้องตรวจสอบประเภทของข้อโต้แย้ง หากพวกเขาสนับสนุน << (ในกรณีของผล) หรือชื่อและศิลปิน (ในกรณีของเพลง) ทุกอย่างจะทำงาน หากไม่เป็นเช่นนั้นวิธีการของคุณจะมีข้อยกเว้นอยู่ดี (เช่นเดียวกับที่ทำถ้าคุณตรวจสอบประเภท) แต่ถ้าไม่มีการตรวจสอบวิธีการของคุณก็จะยืดหยุ่นได้มากกว่าเดิม คุณสามารถส่งผ่านอาร์เรย์, สตริง, ไฟล์, หรือวัตถุอื่น ๆ ที่ต่อท้ายโดยใช้ <<, และมันก็ใช้ได้
พิมพ์ดีดเป็ดไม่ได้พิมพ์คำใบ้!
โดยทั่วไปเพื่อใช้ "การพิมพ์เป็ด" คุณจะไม่ได้กำหนดเป้าหมายประเภทเฉพาะ แต่จะเป็นประเภทย่อยที่กว้างกว่า (ไม่พูดถึงการสืบทอดเมื่อฉันหมายถึงชนิดย่อยฉันหมายถึง "สิ่ง" ที่เหมาะสมภายในโปรไฟล์เดียวกัน) โดยใช้อินเทอร์เฟซทั่วไป .
คุณสามารถจินตนาการระบบที่จัดเก็บข้อมูล ในการเขียน / อ่านข้อมูลคุณจำเป็นต้องมีพื้นที่เก็บข้อมูลและข้อมูลบางประเภท
ประเภทของการจัดเก็บอาจ: ไฟล์ฐานข้อมูลเซสชั่น ฯลฯ
อินเทอร์เฟซจะแจ้งให้คุณทราบตัวเลือกที่มีอยู่ (วิธีการ) โดยไม่คำนึงถึงประเภทการจัดเก็บหมายความว่า ณ จุดนี้ไม่มีการใช้งาน กล่าวอีกนัยหนึ่งอินเทอร์เฟซไม่รู้อะไรเกี่ยวกับวิธีการเก็บข้อมูล
ทุกระบบจัดเก็บข้อมูลจะต้องรู้ว่ามีอินเทอร์เฟซอยู่หรือไม่โดยใช้วิธีการเดียวกัน
interface StorageInterface
{
public function write(string $key, array $value): bool;
public function read(string $key): array;
}
class File implements StorageInterface
{
public function read(string $key): array {
//reading from a file
}
public function write(string $key, array $value): bool {
//writing in a file implementation
}
}
class Session implements StorageInterface
{
public function read(string $key): array {
//reading from a session
}
public function write(string $key, array $value): bool {
//writing in a session implementation
}
}
class Storage implements StorageInterface
{
private $_storage = null;
function __construct(StorageInterface $storage) {
$this->_storage = $storage;
}
public function read(string $key): array {
return $this->_storage->read($key);
}
public function write(string $key, array $value): bool {
return ($this->_storage->write($key, $value)) ? true : false;
}
}
ดังนั้นทุกครั้งที่คุณจำเป็นต้องเขียน / อ่านข้อมูล:
$file = new Storage(new File());
$file->write('filename', ['information'] );
echo $file->read('filename');
$session = new Storage(new Session());
$session->write('filename', ['information'] );
echo $session->read('filename');
ในตัวอย่างนี้คุณจะใช้ Duck Typing ใน Constructor Storage:
function __construct(StorageInterface $storage) ...
หวังว่ามันจะช่วย;)
Tree Traversal ด้วยเทคนิคการพิมพ์เป็ด
def traverse(t):
try:
t.label()
except AttributeError:
print(t, end=" ")
else:
# Now we know that t.node is defined
print('(', t.label(), end=" ")
for child in t:
traverse(child)
print(')', end=" ")
ฉันคิดว่ามันสับสนที่จะผสมการพิมพ์แบบไดนามิกการพิมพ์แบบคงที่และการพิมพ์แบบเป็ด การพิมพ์เป็ดเป็นแนวคิดที่เป็นอิสระและแม้กระทั่งภาษาที่พิมพ์แบบคงที่เช่น Go อาจมีระบบตรวจสอบประเภทที่ใช้การพิมพ์เป็ด หากระบบประเภทจะตรวจสอบวิธีการของวัตถุ (ประกาศ) แต่ไม่ใช่ประเภทนั้นอาจเรียกได้ว่าเป็นภาษาการพิมพ์เป็ด
ฉันพยายามที่จะเข้าใจประโยคที่โด่งดังในแบบของฉัน: "Python ปริมาณไม่สนใจวัตถุเป็นเป็ดจริงหรือไม่ทั้งหมดที่มันสนใจก็คือไม่ว่าจะเป็นวัตถุแรก 'ต้มตุ๋น' ที่สอง 'เหมือนเป็ด'
มีเว็บไซต์ที่ดี http://www.voidspace.org.uk/python/articles/duck_typing.shtml#id14
ผู้เขียนชี้ให้เห็นว่าการพิมพ์เป็ดช่วยให้คุณสร้างคลาสของคุณเองที่มีโครงสร้างข้อมูลภายในของตนเอง - แต่เข้าถึงได้โดยใช้ไวยากรณ์ Python ปกติ