Periksa apakah objek seperti file di Python

94

Objek mirip file adalah objek dalam Python yang berperilaku seperti file nyata, misalnya memiliki metode baca () dan tulis (), tetapi memiliki implementasi yang berbeda. Ini adalah dan realisasi dari konsep Mengetik Bebek .

Ini dianggap sebagai praktik yang baik untuk mengizinkan objek seperti file di mana pun di mana file diharapkan sehingga misalnya objek StringIO atau Socket dapat digunakan sebagai pengganti file nyata. Jadi tidak baik melakukan pemeriksaan seperti ini:

if not isinstance(fp, file):
   raise something

Apa cara terbaik untuk memeriksa apakah suatu objek (misalnya parameter metode) "seperti file"?

dmeister
sumber

Jawaban:

44

Umumnya tidak baik untuk memiliki pemeriksaan seperti ini di kode Anda sama sekali kecuali Anda memiliki persyaratan khusus.

Di Python pengetikannya dinamis, mengapa Anda merasa perlu memeriksa apakah objek tersebut seperti file, daripada hanya menggunakannya seolah-olah itu adalah file dan menangani kesalahan yang dihasilkan?

Setiap pemeriksaan yang dapat Anda lakukan akan tetap terjadi pada waktu proses, jadi melakukan sesuatu seperti if not hasattr(fp, 'read')dan memunculkan beberapa pengecualian akan memberikan utilitas yang lebih sedikit daripada hanya memanggil fp.read()dan menangani kesalahan atribut yang dihasilkan jika metode tersebut tidak ada.

Tendayi Mawushe
sumber
whybagaimana dengan operator yang disukai __add__, __lshift__atau __or__di kelas khusus? (objek file dan API: docs.python.org/glossary.html#term-file-object )
n611x007
@naxa: Lalu bagaimana sebenarnya dengan operator tersebut?
martineau
34
Seringkali hanya mencoba itu berhasil tetapi saya tidak membeli pepatah Pythonic bahwa jika sulit dilakukan dengan Python maka itu salah. Bayangkan Anda melewati suatu objek dan ada 10 hal berbeda yang mungkin Anda lakukan dengan objek itu tergantung pada tipenya. Anda tidak akan mencoba setiap kemungkinan dan menangani kesalahan sampai Anda akhirnya melakukannya dengan benar. Itu sama sekali tidak efisien. Anda tidak perlu bertanya, jenis apa ini, tetapi Anda harus bisa bertanya apakah objek ini mengimplementasikan antarmuka X.
jcoffland
33
Fakta bahwa pustaka koleksi python menyediakan apa yang disebut "tipe antarmuka" (mis., Urutan) menunjukkan fakta bahwa ini sering berguna, bahkan di python. Secara umum, ketika seseorang bertanya "how to foo", "don't foo" bukanlah jawaban yang sangat memuaskan.
AdamC
1
AttributeError dapat dimunculkan untuk segala macam alasan yang tidak ada hubungannya dengan apakah objek mendukung antarmuka yang Anda butuhkan. hasattr diperlukan untuk filelike yang tidak berasal dari IOBase
Erik Aronesty
77

Untuk 3.1+, salah satu dari berikut ini:

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

Untuk 2.x, "file-like object" merupakan hal yang terlalu kabur untuk diperiksa, tetapi dokumentasi untuk fungsi apa pun yang Anda hadapi diharapkan akan memberi tahu Anda apa yang sebenarnya mereka butuhkan; jika tidak, baca kodenya.


Seperti jawaban lain menunjukkan, hal pertama yang harus ditanyakan adalah apa yang sebenarnya Anda periksa. Biasanya, EAFP sudah cukup, dan lebih idiomatis.

Glossary yang mengatakan "file-seperti objek" adalah sinonim untuk "file objek", yang akhirnya berarti itu sebuah contoh dari salah satu dari tiga kelas dasar abstrak didefinisikan dalam satu iomodul , yang dengan sendirinya semua subclass dari IOBase. Jadi cara mengeceknya persis seperti gambar di atas.

(Namun, pemeriksaan IOBasetidak terlalu berguna. Dapatkah Anda membayangkan kasus di mana Anda perlu membedakan file-like yang sebenarnya read(size)dari beberapa fungsi satu-argumen bernama readyang bukan file-like, tanpa juga perlu membedakan antara file teks dan raw file biner? Jadi, sungguh, Anda hampir selalu ingin memeriksa, misalnya, "adalah objek file teks", bukan "adalah objek seperti file".)


Untuk 2.x, sementara iomodul sudah ada sejak 2.6+, objek file bawaan bukanlah instance iokelas, begitu pula objek seperti file di stdlib, dan juga bukan sebagian besar objek seperti file pihak ketiga. Anda akan bertemu. Tidak ada definisi resmi tentang apa arti "objek seperti file"; itu hanya "sesuatu seperti objek file bawaan ", dan fungsi yang berbeda berarti hal yang berbeda dengan "suka". Fungsi tersebut harus mendokumentasikan apa yang mereka maksud; jika tidak, Anda harus melihat kodenya.

Namun, arti yang paling umum adalah "has read(size)", "has read()", atau "is an iterable of strings", tetapi beberapa pustaka lama mungkin mengharapkan readlinealih-alih salah satunya, beberapa pustaka menyukai close()file yang Anda berikan padanya, beberapa akan mengharapkan bahwa jika filenohadir maka fungsionalitas lain tersedia, dll. Dan juga untuk write(buf)(meskipun ada lebih sedikit opsi ke arah itu).

abarnert
sumber
1
Akhirnya, seseorang membuatnya tetap nyata.
Anthony Rutledge
18
Satu-satunya jawaban yang berguna. Mengapa StackOverflowers terus menaikkan suara "Berhenti melakukan apa yang Anda coba lakukan, karena saya tahu lebih baik ... dan PEP 8, EAFP, dan sebagainya!" posting di luar kewarasan saya yang rapuh. ( Mungkin Cthulhu tahu? )
Cecil Curry
1
Karena kita telah menemukan terlalu banyak kode yang ditulis oleh orang-orang yang tidak berpikir ke depan, dan itu rusak ketika Anda menyebarkannya sesuatu yang hampir, tetapi bukan file yang cukup karena mereka memeriksa secara eksplisit. Secara keseluruhan EAFP, hal mengetik bebek bukanlah tes kemurnian omong kosong. Ini adalah keputusan yang benar-benar mencengangkan,
drxzcl
1
Ini mungkin dilihat sebagai teknik yang lebih baik dan saya lebih suka secara pribadi, tetapi mungkin tidak berhasil. Biasanya tidak diperlukan bahwa objek seperti file mewarisi IOBase. Misalnya perlengkapan pytest memberi Anda _pytest.capture.EncodedFileyang tidak mewarisi dari apa pun.
Tomáš Gavenčiak
46

Seperti yang dikatakan orang lain, Anda sebaiknya menghindari pemeriksaan semacam itu. Satu pengecualian adalah ketika objek mungkin merupakan tipe yang berbeda dan Anda menginginkan perilaku yang berbeda tergantung pada tipenya. Metode EAFP tidak selalu berfungsi di sini karena sebuah objek dapat terlihat seperti lebih dari satu jenis bebek!

Misalnya seorang penginisialisasi dapat mengambil file, string atau instance dari kelasnya sendiri. Anda mungkin memiliki kode seperti:

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

Menggunakan EAFP di sini dapat menyebabkan segala macam masalah halus karena setiap jalur inisialisasi dijalankan sebagian sebelum mengeluarkan pengecualian. Pada dasarnya konstruksi ini meniru fungsi overloading sehingga tidak terlalu Pythonic, tetapi dapat berguna jika digunakan dengan hati-hati.

Sebagai catatan tambahan, Anda tidak dapat melakukan pemeriksaan file dengan cara yang sama di Python 3. Anda akan membutuhkan sesuatu seperti itu isinstance(f, io.IOBase).

Scott Griffiths
sumber
28

Paradigma dominan di sini adalah EAFP: lebih mudah meminta maaf daripada izin. Lanjutkan dan gunakan antarmuka file, lalu tangani pengecualian yang dihasilkan, atau biarkan mereka menyebar ke pemanggil.

drxzcl
sumber
9
+1: Jika xtidak seperti file, maka x.read()akan memunculkan pengecualiannya sendiri. Mengapa menulis pernyataan-if tambahan? Gunakan saja objeknya. Ini akan berhasil atau rusak.
S. Lott
3
Bahkan jangan menangani pengecualian. Jika seseorang menyampaikan sesuatu yang tidak sesuai dengan API yang Anda harapkan, itu bukan masalah Anda.
habnabit
1
@ Aaron Gallagher: Saya tidak yakin. Apakah pernyataan Anda benar meskipun sulit bagi saya untuk mempertahankan keadaan yang konsisten?
dmeister
1
Untuk mempertahankan status yang konsisten, Anda dapat menggunakan "coba / akhirnya" (tetapi tanpa kecuali!) Atau pernyataan "dengan" yang baru.
drxzcl
Ini juga konsisten dengan paradigma "Gagal dengan cepat dan gagal dengan keras". Kecuali jika Anda teliti, pemeriksaan hasattr (...) eksplisit terkadang dapat menyebabkan fungsi / metode kembali secara normal tanpa melakukan tindakan yang diinginkan.
Ben Burns
11

Seringkali berguna untuk meningkatkan kesalahan dengan memeriksa suatu kondisi, ketika kesalahan itu biasanya tidak akan dimunculkan sampai nanti. Hal ini terutama berlaku untuk batas antara kode 'user-land' dan 'api'.

Anda tidak akan menempatkan detektor logam di kantor polisi di pintu keluar, Anda akan menempatkannya di pintu masuk! Jika tidak memeriksa kondisi berarti mungkin terjadi kesalahan yang bisa saja tertangkap 100 baris sebelumnya, atau di kelas super alih-alih dimunculkan di subkelas maka saya katakan tidak ada yang salah dengan pemeriksaan.

Memeriksa jenis yang tepat juga masuk akal bila Anda menerima lebih dari satu jenis. Lebih baik memunculkan pengecualian yang mengatakan "Saya memerlukan subkelas basestring, OR file" daripada hanya meningkatkan pengecualian karena beberapa variabel tidak memiliki metode 'seek' ...

Ini tidak berarti Anda menjadi gila dan melakukan ini di mana-mana, sebagian besar saya setuju dengan konsep pengecualian yang meningkat dengan sendirinya, tetapi jika Anda dapat membuat API Anda sangat jelas, atau menghindari eksekusi kode yang tidak perlu karena kondisi sederhana belum terpenuhi lakukanlah!

Ben DeMott
sumber
1
Saya setuju, tetapi tetap tidak menjadi gila dengan hal ini di mana-mana - banyak kekhawatiran ini harus hilang selama pengujian, dan beberapa pertanyaan "di mana untuk menangkap ini / cara menampilkan kepada pengguna" akan dijawab oleh persyaratan kegunaan.
Ben Burns
7

Anda dapat mencoba dan memanggil metode tersebut lalu menangkap pengecualian:

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

Jika Anda hanya menginginkan metode baca dan tulis, Anda dapat melakukan ini:

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

Jika saya jadi Anda, saya akan menggunakan metode coba / kecuali.

Nadia Alramli
sumber
Saya sarankan mengganti urutan contoh. tryselalu menjadi pilihan pertama. The hasattrcek hanya - untuk beberapa alasan yang sangat jelas - Anda tidak dapat hanya menggunakan try.
S. Lott
1
Saya sarankan menggunakan fp.read(0)bukannyafp.read() untuk menghindari meletakkan semua kode di tryblok jika Anda ingin memproses data dari fpselanjutnya.
Meong
3
Perhatikan bahwa fp.read()dengan file besar akan segera meningkatkan penggunaan memori.
Kyrylo Perevozchikov
Saya mendapatkan ini pythonic, tetapi kemudian kami harus membaca file dua kali di antara masalah lainnya. Misalnya, saat Flasksaya melakukan ini dan menyadari bahwa FileStorageobjek yang mendasari memerlukan reset pointer setelah dibaca.
Adam Hughes
2

Dalam banyak situasi, cara terbaik untuk menangani hal ini adalah dengan tidak melakukannya. Jika suatu metode mengambil objek seperti file, dan ternyata objek yang diteruskannya bukan, pengecualian yang dimunculkan saat metode mencoba menggunakan objek tersebut tidak kurang informatifnya daripada pengecualian apa pun yang mungkin Anda kemukakan secara eksplisit.

Setidaknya ada satu kasus di mana Anda mungkin ingin melakukan pemeriksaan semacam ini, dan saat itulah objek tidak segera digunakan oleh apa yang Anda berikan padanya, misalnya jika itu disetel di konstruktor kelas. Dalam hal ini, saya akan berpikir bahwa prinsip EAFP dikalahkan oleh prinsip "gagal cepat." Saya akan memeriksa objek untuk memastikan itu menerapkan metode yang dibutuhkan kelas saya (dan itu adalah metode), misalnya:

class C():
    def __init__(self, file):
        if type(getattr(file, 'read')) != type(self.__init__):
            raise AttributeError
        self.file = file
Robert Rossney
sumber
1
Mengapa, getattr(file, 'read')bukan hanya file.read? Ini melakukan hal yang persis sama.
abarnert
1
Lebih penting lagi, pemeriksaan ini salah. Ini akan meningkat ketika diberikan, katakanlah, filecontoh aktual . (Metode contoh tipe builtin / C-extension adalah tipe builtin_function_or_method, sedangkan class gaya lama adalah instancemethod). Fakta bahwa ini adalah kelas gaya lama, dan yang digunakan ==pada tipe, bukan ininstanceatau issubclass, adalah masalah lebih lanjut, tetapi jika ide dasarnya tidak berhasil, itu hampir tidak menjadi masalah.
abarnert
2

Saya akhirnya menemukan pertanyaan Anda ketika saya menulis openfungsi -like yang dapat menerima nama file, deskriptor file atau objek seperti file yang sudah dibuka sebelumnya.

Alih-alih menguji suatu readmetode, seperti yang disarankan oleh jawaban lain, saya akhirnya memeriksa apakah benda itu dapat dibuka. Jika bisa, itu adalah string atau deskriptor, dan saya memiliki objek seperti file yang valid dari hasil. Jika openmemunculkan a TypeError, maka objek tersebut sudah menjadi file.

Fisikawan Gila
sumber