ฟังก์ชัน Python จะจัดการกับชนิดของพารามิเตอร์ที่คุณส่งผ่านได้อย่างไร


305

ถ้าฉันเข้าใจผิดการสร้างฟังก์ชั่นใน Python จะทำงานเช่นนี้

def my_func(param1, param2):
    # stuff

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


15
ฉันคิดว่าคำตอบที่ได้รับการยอมรับในคำถามนี้ควรได้รับการปรับปรุงให้สอดคล้องกับความสามารถในปัจจุบันที่ Python นำเสนอ ฉันคิดว่าคำตอบนี้ทำงานได้ดี
code_dredd

คำตอบ:


173

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

นี้มีอะไรจะทำอย่างไรกับชื่อ ชื่อในหลามไม่ได้ "มีประเภท": ถ้าและเมื่อชื่อที่กำหนดชื่อหมายถึงวัตถุและวัตถุจะมีประเภท ( แต่ที่ไม่ได้มีผลบังคับใช้จริงประเภทในที่ชื่อก ชื่อคือชื่อ)

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


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

19
@liang นั่นเป็นความเห็นดังนั้นคุณจะไม่ถูกหรือผิด มันเป็นความคิดของฉันและฉันก็ลองหลายภาษา ความจริงที่ว่าฉันไม่สามารถใช้ IDE ของฉันเพื่อค้นหาประเภท (และสมาชิกดังนั้น) ของพารามิเตอร์ที่เป็นข้อเสียเปรียบที่สำคัญของหลาม หากข้อเสียเปรียบนี้สำคัญกว่าข้อได้เปรียบของการพิมพ์เป็ดนั้นขึ้นอยู่กับคนที่คุณถาม
Maarten Bodewes

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

4
@ qPCR4vir วัตถุใด ๆ ที่สามารถส่งผ่านเป็นอาร์กิวเมนต์ ข้อผิดพลาด (ข้อยกเว้นโปรแกรมจะไม่ "ตาย" หากมีการเข้ารหัสเพื่อให้ดูtry/ except) จะเกิดขึ้นเมื่อใดและหากมีการพยายามดำเนินการที่วัตถุไม่สนับสนุน ใน Python 3.5 ตอนนี้คุณสามารถเลือก "ระบุประเภท" ของการขัดแย้งได้ แต่ไม่มีข้อผิดพลาดเกิดขึ้นต่อไปหากข้อมูลจำเพาะนั้นละเมิด สัญกรณ์การพิมพ์นั้นมีไว้เพื่อช่วยเครื่องมือแยกต่างหากที่ดำเนินการวิเคราะห์ ฯลฯ เท่านั้น แต่ไม่ได้เปลี่ยนลักษณะการทำงานของ Python เอง
Alex Martelli

2
@AlexMartelli ขอบคุณ! สำหรับฉันนี่คือคำตอบที่ถูกต้อง: "ข้อผิดพลาด (ข้อยกเว้นโปรแกรมจะไม่" ตาย "ถ้ามันเป็นรหัสที่จะจับมันดูลอง / ยกเว้น) .. "
qPCR4vir

753

คำตอบอื่น ๆ ทำได้ดีในการอธิบายการพิมพ์เป็ดและคำตอบง่ายๆโดย tzot :

Python ไม่มีตัวแปรเช่นภาษาอื่น ๆ ที่ตัวแปรมีชนิดและค่า มันมีชื่อที่ชี้ไปยังวัตถุซึ่งทราบประเภทของวัตถุเหล่านั้น

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

def pick(l: list, index: int) -> int:
    return l[index]

เราจะเห็นว่าpickใช้พารามิเตอร์ 2 รายการlindexและเป็นจำนวนเต็ม มันควรจะกลับจำนวนเต็ม

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

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

โปรดทราบว่าตอนนี้เราใส่สตริงเป็นประเภทของ lซึ่งได้รับอนุญาตทาง syntactically แต่มันไม่ดีสำหรับการแยกวิเคราะห์ทางโปรแกรม (ซึ่งเราจะกลับมาในภายหลัง)

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

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

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

PEP 3107ไม่เพียง แต่การปรับปรุงการอ่านรหัส แต่ยังมีกรณีการใช้งานที่เหมาะสมหลายอย่างที่คุณสามารถอ่านข้อมูลเกี่ยวกับที่นี่


คำอธิบายประกอบแบบได้รับความสนใจมากขึ้นใน Python 3.5 ด้วยการเปิดตัวPEP 484ซึ่งแนะนำโมดูลมาตรฐานสำหรับคำใบ้ประเภท

คำใบ้ประเภทเหล่านี้มาจากตัวตรวจสอบชนิดmypy ( GitHub ) ซึ่งตอนนี้เป็นไปตามPEP 484

ด้วยโมดูลการพิมพ์ที่มาพร้อมคอลเลกชันคำแนะนำประเภทที่ครอบคลุมรวมถึง:

  • List, Tuple, Set, Map- สำหรับlist, tuple, setและmapตามลำดับ
  • Iterable - มีประโยชน์สำหรับเครื่องกำเนิดไฟฟ้า
  • Any - เมื่อมันสามารถเป็นอะไรก็ได้
  • Union- Anyเมื่อมันจะเป็นอะไรที่อยู่ในชุดที่ระบุประเภทเมื่อเทียบกับ
  • Optional- เมื่อมันอาจจะไม่มี Union[T, None]ชวเลข
  • TypeVar - ใช้กับยาชื่อสามัญ
  • Callable - ใช้สำหรับฟังก์ชั่นเป็นหลัก แต่สามารถใช้สำหรับ callables อื่น

นี่คือคำแนะนำประเภทที่พบบ่อยที่สุด รายชื่อที่สมบูรณ์สามารถพบได้ในเอกสารสำหรับโมดูลการพิมพ์

นี่คือตัวอย่างเก่าโดยใช้วิธีการเพิ่มความคิดเห็นที่แนะนำในโมดูลการพิมพ์:

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

คุณลักษณะที่มีประสิทธิภาพอย่างหนึ่งคือคุณสมบัติCallableที่ช่วยให้คุณพิมพ์วิธีการใส่คำอธิบายประกอบที่รับฟังก์ชั่นเป็นอาร์กิวเมนต์ ตัวอย่างเช่น:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

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


ก่อนหน้านี้เมื่อมีรหัส Python ที่มีเอกสารหนึ่งตัวอย่างเช่นSphinxฟังก์ชันการทำงานบางอย่างข้างต้นสามารถรับได้โดยการเขียนเอกสารที่จัดรูปแบบดังนี้

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

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

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


ตัวอย่างของรหัสหลามซึ่งหนักใช้ประเภทเค้าสามารถพบได้ที่นี่


2
@rickfoosusa: ฉันสงสัยว่าคุณไม่ได้ใช้งาน Python 3 ซึ่งมีการเพิ่มฟีเจอร์นี้
erb

26
เดี๋ยวก่อน หากการกำหนดพารามิเตอร์และชนิดส่งคืนไม่เพิ่ม a TypeErrorจุดของการใช้pick(l: list, index: int) -> intเช่นการกำหนดหนึ่งบรรทัดคืออะไร? หรือฉันเข้าใจผิดฉันไม่รู้
Erdin Eray

24
@Eray Erdin: นั่นเป็นความเข้าใจผิดที่พบบ่อยและไม่ได้เป็นคำถามที่ไม่ดีเลย สามารถใช้เพื่อวัตถุประสงค์ด้านเอกสารช่วยให้ IDE ทำการเติมข้อความอัตโนมัติได้ดีขึ้นและค้นหาข้อผิดพลาดก่อนรันไทม์โดยใช้การวิเคราะห์แบบคงที่ (เช่น mypy ที่ฉันกล่าวถึงในคำตอบ) มีความหวังว่ารันไทม์สามารถใช้ประโยชน์จากข้อมูลและเร่งความเร็วโปรแกรมจริง ๆ แต่น่าจะใช้เวลานานกว่าจะนำมาใช้ คุณอาจสร้างมัณฑนากรที่โยน TypeErrors ให้คุณได้ (ข้อมูลจะถูกเก็บไว้ใน__annotations__คุณสมบัติของวัตถุฟังก์ชั่น)
erb

2
@ErdinEray ฉันควรเพิ่มว่าการขว้าง TypeErrors เป็นความคิดที่ไม่ดี (การดีบั๊กไม่เคยสนุกไม่ว่าจะมีข้อยกเว้นที่ตั้งใจไว้มากแค่ไหนก็ตาม) แต่ไม่ต้องกลัวข้อได้เปรียบของฟีเจอร์ใหม่ที่อธิบายไว้ในคำตอบของฉันจะช่วยให้วิธีที่ดีกว่า: ไม่ต้องพึ่งพาการตรวจสอบที่รันไทม์ทำทุกอย่างก่อนรันไทม์ด้วย mypy หรือใช้โปรแกรมแก้ไขซึ่งทำการวิเคราะห์แบบคงที่สำหรับคุณเช่น PyCharm .
14164 erb

2
@Tony: เมื่อคุณส่งคืนวัตถุสองชิ้นขึ้นไปคุณจะส่งคืน tuple ดังนั้นคุณควรใช้คำอธิบายประกอบประเภท Tuple เช่นdef f(a) -> Tuple[int, int]:
erb

14

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

ดังนั้นฟังก์ชั่นง่าย ๆ นี้:

def no_op(param1, param2):
    pass

... จะไม่ล้มเหลวไม่ว่าสอง args จะผ่านอะไรก็ตาม

อย่างไรก็ตามฟังก์ชั่นนี้:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... จะล้มเหลวขณะรันไทม์ถ้าparam1และparam2ทั้งคู่ไม่มีชื่อเรียกquackได้


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

11

หลายภาษามีตัวแปรซึ่งเป็นประเภทเฉพาะและมีค่า Python ไม่มีตัวแปร มันมีวัตถุและคุณใช้ชื่อเพื่ออ้างถึงวัตถุเหล่านี้

ในภาษาอื่น ๆ เมื่อคุณพูดว่า:

a = 1

ดังนั้นตัวแปร (โดยทั่วไปจำนวนเต็ม) จะเปลี่ยนเนื้อหาเป็นค่า 1

ใน Python

a = 1

หมายถึง“ ใช้ชื่อaเพื่ออ้างถึงวัตถุ1 ” คุณสามารถทำสิ่งต่อไปนี้ในเซสชัน Python แบบโต้ตอบ:

>>> type(1)
<type 'int'>

ฟังก์ชั่นtypeเรียกว่ามีวัตถุ1; เนื่องจากวัตถุทุกชนิดรู้ชนิดของมันจึงเป็นเรื่องง่ายสำหรับtypeการค้นหาประเภทที่กล่าวและส่งคืน

เช่นเดียวกันทุกครั้งที่คุณกำหนดฟังก์ชั่น

def funcname(param1, param2):

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


8

Python ไม่ได้พิมพ์อย่างมากในแง่ของการตรวจสอบประเภทแบบคงที่หรือเวลารวบรวม

รหัสหลามส่วนใหญ่อยู่ภายใต้สิ่งที่เรียกว่า"การพิมพ์ดีดเป็ด" - ตัวอย่างเช่นคุณมองหาวิธีการreadบนวัตถุ - คุณไม่สนใจว่าวัตถุนั้นเป็นไฟล์บนดิสก์หรือซ็อกเก็ตคุณแค่ต้องการอ่าน N ไบต์จากมัน


21
Python ถูกพิมพ์อย่างยิ่ง มันยังพิมพ์แบบไดนามิก
Daniel Newby

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

6

ขณะที่อเล็กซ์เทลอธิบาย ,

วิธีการแก้ปัญหาแบบปกติ Pythonic ที่ต้องการเกือบจะเป็น "การพิมพ์เป็ด" อย่างคงที่: ลองใช้การโต้แย้งราวกับว่าเป็นประเภทที่ต้องการให้ทำในการลอง / ยกเว้นข้อความที่จับข้อยกเว้นทั้งหมดที่อาจเกิดขึ้นหากการโต้แย้งไม่ได้เกิดขึ้นจริง ประเภทนั้น (หรือประเภทอื่น ๆ อย่างเป็ด - ล้อเลียนมัน ;-) และในข้อยกเว้นลองอย่างอื่น (ใช้อาร์กิวเมนต์ "ราวกับว่า" มันเป็นประเภทอื่น ๆ )

อ่านส่วนที่เหลือของโพสต์ของเขาสำหรับข้อมูลที่เป็นประโยชน์


5

Python ไม่สนใจสิ่งที่คุณส่งผ่านไปยังฟังก์ชั่น เมื่อคุณเรียกmy_func(a,b)ใช้ตัวแปร param1 และ param2 จะเก็บค่าของ a และ b Python ไม่ทราบว่าคุณกำลังเรียกใช้ฟังก์ชันที่มีประเภทที่เหมาะสมและคาดว่าโปรแกรมเมอร์จะดูแลมัน หากฟังก์ชั่นของคุณจะถูกเรียกใช้พร้อมกับพารามิเตอร์ประเภทต่างๆคุณสามารถใส่โค้ดในการเข้าถึงด้วยลอง / ยกเว้นบล็อคและประเมินพารามิเตอร์ในแบบที่คุณต้องการ


11
Python ไม่มีตัวแปรเช่นภาษาอื่น ๆ ที่ตัวแปรมีชนิดและค่า มันมีชื่อที่ชี้ไปยังวัตถุซึ่งทราบประเภทของวัตถุเหล่านั้น
tzot

2

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

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


2

มีข้อยกเว้นอันฉาวโฉ่อย่างหนึ่งจากมูลค่าการพิมพ์เป็ดที่กล่าวถึงในหน้านี้

เมื่อstrฟังก์ชั่นการเรียก__str__วิธีการเรียนมันอย่างละเอียดсhecksประเภท:

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type tuple)

ราวกับว่ากุยโดแนะนำเราว่าโปรแกรมใดควรทำการยกระดับหากพบประเภทที่ไม่คาดคิด


1

ใน Python ทุกอย่างมีประเภท ฟังก์ชั่น Python จะทำทุกอย่างที่ถูกขอให้ทำถ้าประเภทของอาร์กิวเมนต์สนับสนุน

ตัวอย่าง: fooจะเพิ่มทุกอย่างที่สามารถแก้ไขได้__add__) โดยไม่ต้องกังวลเกี่ยวกับประเภทของมัน ดังนั้นเพื่อหลีกเลี่ยงความล้มเหลวคุณควรจัดเตรียมเฉพาะสิ่งที่สนับสนุนการเพิ่มเท่านั้น

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail

1

ฉันไม่เห็นสิ่งนี้พูดถึงในคำตอบอื่น ๆ ดังนั้นฉันจะเพิ่มลงในหม้อ

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

หนึ่งในเครื่องมือหลักในการทำเช่นนี้คือฟังก์ชั่น isinstance ()

ตัวอย่างเช่นถ้าฉันเขียนวิธีที่คาดว่าจะได้รับข้อมูลข้อความไบนารีแบบดิบแทนที่จะเป็นสตริงที่เข้ารหัสแบบ utf-8 ปกติฉันสามารถตรวจสอบชนิดของพารามิเตอร์ระหว่างทางและปรับให้เข้ากับสิ่งที่ฉันค้นหาหรือเพิ่ม ข้อยกเว้นที่จะปฏิเสธ

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

Python ยังมีเครื่องมือทุกประเภทที่จะขุดเข้าไปในวัตถุ หากคุณกล้าคุณสามารถใช้ importlib เพื่อสร้างออบเจ็กต์ของคุณเองได้ตามใจชอบ ฉันทำสิ่งนี้เพื่อสร้างวัตถุใหม่จากข้อมูล JSON สิ่งนี้จะเป็นฝันร้ายในภาษาแบบคงที่เช่น C ++


1

ในการใช้โมดูลการพิมพ์ (ใหม่ใน Python 3.5) ได้อย่างมีประสิทธิภาพรวมทั้งหมด ( *)

from typing import *

และคุณจะพร้อมใช้งาน:

List, Tuple, Set, Map - for list, tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

แต่ยังคงคุณสามารถใช้ชื่อประเภทเช่นint, list, dict...


1

ฉันใช้ตัวคลุมหากใครต้องการระบุประเภทตัวแปร

import functools

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable `' + str(v_name) + '` should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

ใช้เป็น:

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable `name` should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable `return` should be type (<class 'str'>) but instead is type (<class 'float'>)

แก้ไข

รหัสข้างต้นไม่สามารถใช้งานได้หากไม่มีการประกาศประเภทของอาร์กิวเมนต์ (หรือผลตอบแทน) การแก้ไขต่อไปนี้สามารถช่วยได้ แต่ทำงานได้กับ kwargs เท่านั้นและไม่ได้ตรวจสอบ args

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue

            v_type = func.__annotations__[name]

            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable `' + str(v_name) + '` should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

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