ตรวจสอบว่าวัตถุเหมือนไฟล์ใน Python หรือไม่


98

อ็อบเจ็กต์คล้ายไฟล์คืออ็อบเจ็กต์ใน Python ที่ทำงานเหมือนไฟล์จริงเช่นมี read () และ write method () แต่มีการนำไปใช้งานที่แตกต่างจากfileไฟล์. เป็นการนำแนวคิดDuck Typingมาใช้

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

if not isinstance(fp, file):
   raise something

วิธีใดที่ดีที่สุดในการตรวจสอบว่าออบเจ็กต์ (เช่นพารามิเตอร์ของเมธอด) เป็น "ไฟล์เหมือน" หรือไม่

คำตอบ:


41

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

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

การตรวจสอบใด ๆ ที่คุณสามารถทำได้จะเกิดขึ้นที่รันไทม์ดังนั้นการทำบางสิ่งบางอย่างเช่นif not hasattr(fp, 'read')และการเพิ่มข้อยกเว้นบางอย่างจะให้ประโยชน์มากกว่าการเรียกfp.read()และจัดการข้อผิดพลาดแอตทริบิวต์ที่เป็นผลลัพธ์หากไม่มีวิธี


whyสิ่งที่เกี่ยวกับผู้ประกอบการต้องการ__add__, __lshift__หรือ__or__ในชั้นเรียนที่กำหนดเอง? (ไฟล์ออบเจ็กต์และ API: docs.python.org/glossary.html#term-file-object )
n611x007

@naxa: แล้วโอเปอเรเตอร์เหล่านั้นล่ะ?
martineau

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

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

2
AttributeError สามารถยกขึ้นได้ด้วยเหตุผลหลายประการที่ไม่เกี่ยวข้องกับว่าวัตถุนั้นรองรับอินเทอร์เฟซที่คุณต้องการหรือไม่ จำเป็นต้องมี hasattr สำหรับ filelikes ที่ไม่ได้มาจาก IOBase
Erik Aronesty

80

สำหรับ 3.1+ ข้อใดข้อหนึ่งต่อไปนี้:

isinstance(something, io.TextIOBase)
isinstance(something, io.BufferedIOBase)
isinstance(something, io.RawIOBase)
isinstance(something, io.IOBase)

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


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

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

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


สำหรับ 2.x ในขณะที่ioโมดูลมีมาตั้งแต่ 2.6+ อ็อบเจ็กต์ไฟล์ในตัวไม่ใช่อินสแตนซ์ของioคลาสไม่มีอ็อบเจ็กต์ที่คล้ายไฟล์ใด ๆ ใน stdlib และทั้งสองก็ไม่ใช่อ็อบเจ็กต์ไฟล์ของบุคคลที่สามส่วนใหญ่ มีแนวโน้มที่จะพบ ไม่มีคำจำกัดความอย่างเป็นทางการว่า "file-like object" หมายถึงอะไร มันเป็นแค่ "สิ่งที่เหมือนออบเจ็กต์ไฟล์ในตัว " และฟังก์ชันที่แตกต่างกันหมายถึงสิ่งที่แตกต่างกันโดย "like" ฟังก์ชันดังกล่าวควรบันทึกความหมาย หากไม่เป็นเช่นนั้นคุณต้องดูรหัส

อย่างไรก็ตามความหมายที่พบบ่อยที่สุดคือ "has read(size)" "has read()" หรือ "is an iterable of strings" แต่ไลบรารีเก่าบางแห่งอาจคาดหวังreadlineแทนที่จะเป็นหนึ่งในนั้นไลบรารีบางแห่งชอบclose()ไฟล์ที่คุณให้บางไลบรารีอาจคาดหวังว่าหากfilenoมีอยู่แล้วฟังก์ชันอื่น ๆ จะพร้อมใช้งาน ฯลฯ และในทำนองเดียวกันสำหรับwrite(buf)(แม้ว่าจะมีตัวเลือกน้อยกว่ามากในทิศทางนั้น)


2
ในที่สุดก็มีใครบางคนให้มันเป็นจริง
Anthony Rutledge

22
คำตอบเดียวที่มีประโยชน์ เหตุใด StackOverflowers จึงยังคงเพิ่มคะแนน "หยุดทำในสิ่งที่คุณพยายามทำเพราะฉันรู้ดีกว่า ... และ PEP 8, EAFP และอื่น ๆ !" โพสต์อยู่นอกเหนือความมีสติที่เปราะบางของฉัน ( คธูลูอาจจะรู้? )
Cecil Curry

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

1
สิ่งนี้อาจถูกมองว่าเป็นวิศวกรรมที่ดีกว่าและฉันต้องการเป็นการส่วนตัว แต่อาจไม่ได้ผล มันเป็นเรื่องปกติไม่IOBaseจำเป็นว่าไฟล์เหมือนวัตถุที่สืบทอดมาจาก ตัวอย่างเช่นการติดตั้ง pytest ให้คุณ_pytest.capture.EncodedFileซึ่งไม่ได้รับมรดกจากสิ่งใด
TomášGavenčiak

47

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

ตัวอย่างเช่นผู้เริ่มต้นสามารถใช้ไฟล์สตริงหรืออินสแตนซ์ของคลาสของตนเอง จากนั้นคุณอาจมีรหัสเช่น:

class A(object):
    def __init__(self, f):
        if isinstance(f, A):
            # Just make a copy.
        elif isinstance(f, file):
            # initialise from the file
        else:
            # treat f as a string

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

ตามหมายเหตุด้านข้างคุณไม่สามารถทำการตรวจสอบไฟล์ในลักษณะเดียวกันใน Python 3 ได้คุณจะต้องมีบางอย่างเช่นนี้isinstance(f, io.IOBase)แทน


28

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


9
+1: หากxไม่เหมือนไฟล์x.read()จะเพิ่มข้อยกเว้นของตัวเอง ทำไมต้องเขียน if-statement พิเศษ? เพียงแค่ใช้วัตถุ มันจะทำงานหรือพัง
ล็อต

3
อย่าแม้แต่จัดการกับข้อยกเว้น หากมีคนส่งผ่านสิ่งที่ไม่ตรงกับ API ที่คุณคาดหวังก็ไม่ใช่ปัญหาของคุณ
habnabit

1
@Aaron Gallagher: ฉันไม่แน่ใจ คำพูดของคุณเป็นจริงหรือไม่แม้ว่าฉันจะรักษาสถานะที่สอดคล้องกันได้ยากก็ตาม
dmeister

1
เพื่อรักษาสถานะที่สอดคล้องกันคุณสามารถใช้ "try / ในที่สุด" (แต่ไม่ยกเว้น!) หรือคำสั่ง "with" ใหม่
drxzcl

นอกจากนี้ยังสอดคล้องกับกระบวนทัศน์ "ล้มเหลวเร็วและล้มเหลวดัง" หากคุณไม่พิถีพิถันการตรวจสอบ hasattr (... ) อย่างชัดเจนในบางครั้งอาจทำให้ฟังก์ชัน / วิธีการกลับมาเป็นปกติโดยไม่ต้องดำเนินการตามที่ตั้งใจไว้
Ben Burns

11

มักจะมีประโยชน์ในการเพิ่มข้อผิดพลาดโดยการตรวจสอบเงื่อนไขเมื่อปกติแล้วข้อผิดพลาดนั้นจะไม่ถูกเพิ่มขึ้นจนกว่าจะถึงเวลาต่อมา โดยเฉพาะอย่างยิ่งสำหรับขอบเขตระหว่างรหัส 'user-land' และ 'api'

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

การตรวจสอบประเภทที่เหมาะสมยังเหมาะสมเมื่อคุณยอมรับมากกว่าหนึ่งประเภท จะเป็นการดีกว่าที่จะยกข้อยกเว้นที่ระบุว่า "ฉันต้องการคลาสย่อยของ basestring หรือ file" มากกว่าแค่เพิ่มข้อยกเว้นเพราะตัวแปรบางตัวไม่มีเมธอด 'ค้นหา' ...

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


1
ฉันเห็นด้วย แต่ตลอดแนวที่จะไม่คลั่งไคล้สิ่งนี้ในทุกๆที่ - ความกังวลเหล่านี้จำนวนมากควรจะหายไปในระหว่างการทดสอบและคำถาม "ที่ที่จะจับสิ่งนี้ / วิธีแสดงต่อผู้ใช้" จะได้รับคำตอบจากข้อกำหนดด้านความสามารถในการใช้งาน
Ben Burns

7

คุณสามารถลองเรียกใช้เมธอดจากนั้นจับข้อยกเว้น:

try:
    fp.read()
except AttributeError:
    raise something

หากคุณต้องการเพียงวิธีการอ่านและเขียนคุณสามารถทำได้:

if not (hasattr(fp, 'read') and hasattr(fp, 'write')):
   raise something

ถ้าฉันเป็นคุณฉันจะใช้วิธี try / except


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

1
ฉันขอแนะนำให้ใช้fp.read(0)แทนfp.read()เพื่อหลีกเลี่ยงการใส่รหัสทั้งหมดในtryบล็อกหากคุณต้องการประมวลผลข้อมูลในfpภายหลัง
Meow

3
โปรดทราบว่าfp.read()ไฟล์ขนาดใหญ่จะเพิ่มการใช้งานหน่วยความจำทันที
Kyrylo Perevozchikov

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

3

ฉันพบคำถามของคุณเมื่อฉันเขียนไฟล์ openฟังก์ชัน -like ที่สามารถยอมรับชื่อไฟล์ตัวอธิบายไฟล์หรืออ็อบเจ็กต์ไฟล์ที่เปิดไว้ล่วงหน้า

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


2

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

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

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file

1
ทำไมgetattr(file, 'read')แทนที่จะเป็นเพียงfile.read? สิ่งนี้ไม่เหมือนกันทุกประการ
ยกเลิก

1
ที่สำคัญเช็คนี้ผิด มันจะเพิ่มขึ้นเมื่อได้รับfileเช่นอินสแตนซ์จริง (วิธีการของอินสแตนซ์ประเภทบิวอิน / ส่วนขยาย C เป็นประเภทbuiltin_function_or_methodในขณะที่คลาสแบบเก่าเป็นinstancemethod) ความจริงที่ว่านี่เป็นคลาสแบบเก่าและใช้==กับประเภทแทนininstanceหรือissubclassเป็นปัญหาเพิ่มเติม แต่ถ้าแนวคิดพื้นฐานไม่ได้ผลก็แทบจะไม่สำคัญ
ยกเลิก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.