Python'da nesnenin dosya benzeri olup olmadığını kontrol edin


96

Dosya benzeri nesneler , Python'da gerçek bir dosya gibi davranan nesnelerdir, örneğin bir read () ve bir write yöntemi (), ancak farklı bir uygulaması vardır. O ve hayata Ördek Yazma kavram.

Bir dosyanın beklendiği her yerde dosya benzeri bir nesneye izin vermek iyi bir uygulama olarak kabul edilir, böylece örneğin bir StringIO veya Socket nesnesi gerçek bir dosya yerine kullanılabilir. Yani böyle bir kontrol yapmak kötü:

if not isinstance(fp, file):
   raise something

Bir nesnenin (örneğin bir yöntemin parametresi) "dosya benzeri" olup olmadığını kontrol etmenin en iyi yolu nedir?

Yanıtlar:


44

Özel gereksinimleriniz olmadıkça, kodunuzda böyle kontroller bulundurmak genellikle iyi bir uygulama değildir.

Python'da yazım dinamiktir, neden nesneyi bir dosya gibi kullanmak ve ortaya çıkan hatayı ele almak yerine, nesnenin dosya gibi olup olmadığını kontrol etmeye ihtiyaç duyuyorsunuz?

Yapabileceğiniz herhangi bir kontrol, yine de çalışma zamanında gerçekleşecektir, bu nedenle, benzer bir şey yapmak if not hasattr(fp, 'read')ve bazı istisnaları yükseltmek fp.read(), yöntem mevcut değilse, sonuçta ortaya çıkan öznitelik hatasını çağırmaktan ve işlemekten biraz daha fazla yardımcı program sağlar .


whyNe operatörleri gibi ilgili __add__, __lshift__ya da __or__özel sınıflarda? (dosya nesnesi ve API: docs.python.org/glossary.html#term-file-object )
n611x007

@naxa: Peki bu operatörler tam olarak ne olacak?
martineau

34
Çoğunlukla sadece denemek işe yarıyor, ancak Python'da yapılması zorsa yanlış olduğu şeklindeki Pythonic özdeyişine inanmıyorum. Bir nesneden geçirildiğinizi ve türüne bağlı olarak bu nesneyle yapabileceğiniz 10 farklı şey olduğunu hayal edin. Her olasılığı denemeyecek ve sonunda doğru olana kadar hatayı halletmeyeceksiniz. Bu tamamen verimsiz olurdu. Bunun ne tür olduğunu sormanıza gerek yok, ancak bu nesnenin X arabirimini uygulayıp uygulamadığını
sorabilmeniz gerekir

33
Python koleksiyon kitaplığının "arayüz türleri" (örneğin, dizi) olarak adlandırılabilecek şeyleri sağlaması, bunun python'da bile çoğu zaman yararlı olduğu gerçeğine işaret eder. Genel olarak, birisi "nasıl yapılır" diye sorduğunda, "yapma" pek tatmin edici bir cevap değildir.
AdamC

1
AttributeError, nesnenin ihtiyacınız olan arayüzü destekleyip desteklemediğiyle hiçbir ilgisi olmayan her türlü nedenden ötürü ortaya çıkarılabilir. IOBase'den türemeyen filelikler için hasattr gereklidir
Erik Aronesty

79

3.1+ için aşağıdakilerden biri:

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

2.x için, "dosya benzeri nesne" kontrol edilemeyecek kadar belirsiz bir şeydir, ancak uğraştığınız işlev (ler) için belgeler umarız size gerçekte neye ihtiyaçları olduğunu söyleyecektir; değilse, kodu okuyun.


Diğer yanıtların da işaret ettiği gibi, sormanız gereken ilk şey, tam olarak neyi aradığınızdır. EAFP genellikle yeterlidir ve daha deyimseldir.

Sözlüğü "nesne benzeri dosya" sonuçta üç birinin bir örneği var demektir "dosya nesnesi", eş anlamlıdır diyor soyut temel sınıflar tanımlanan modül kendilerini her alt sınıflarıdır . Yani, kontrol etmenin yolu tam olarak yukarıda gösterildiği gibidir.ioIOBase

(Bununla birlikte, kontrol etmek IOBasepek kullanışlı değildir. Gerçek bir dosya benzeri read(size), dosya benzeri olmayan tek bağımsız değişkenli bir işlevi read, ayrıca metin dosyaları ve ham metin dosyaları arasında ayrım yapmanıza gerek kalmadan ayırt etmeniz gereken bir durum hayal edebiliyor musunuz? Yani, gerçekten, neredeyse her zaman kontrol etmek istersiniz, örneğin, "bir metin dosyası nesnesidir", "dosya benzeri bir nesnedir" değil.)


2.x için, iomodül 2.6+ sürümünden beri var olsa da , yerleşik dosya nesneleri iosınıfların örnekleri değildir , stdlib'deki dosya benzeri nesneler ve üçüncü taraf dosya benzeri nesneler de değildir. karşılaşmanız muhtemeldir. "Dosya benzeri nesnenin" ne anlama geldiğine dair resmi bir tanım yoktu; bu sadece "yerleşik bir dosya nesnesi gibi bir şey " ve farklı işlevler "beğenmek" ile farklı şeyler ifade ediyor. Bu tür işlevler ne anlama geldiklerini belgelemelidir; yoksa, koda bakmanız gerekir.

Bununla birlikte, en yaygın anlamlar "vardır read(size)", "vardır read()" veya "dizelerin yinelenebiliridir", ancak bazı eski kitaplıklar readlinebunlardan biri yerine bekleyebilir , bazı kitaplıklar close()onlara verdiğiniz dosyaları sever , bazıları ise filenoo zaman diğer işlevler mevcuttur, vb. Ve benzer şekilde write(buf)(bu yönde çok daha az seçenek olmasına rağmen).


1
Sonunda, birisi bunu gerçek tutuyor.
Anthony Rutledge

20
Tek kullanışlı cevap. Neden StackOverflowers "Yapmaya çalıştığınız şeyi yapmayı bırakın, çünkü daha iyi biliyorum ... ve PEP 8, EAFP ve benzeri şeyler!" gönderiler benim kırılgan akıl sağlığımın ötesinde. ( Belki Cthulhu biliyordur? )
Cecil Curry

1
Çünkü ileriyi düşünmeyen insanlar tarafından yazılmış çok fazla kodla karşılaştık ve onu neredeyse olan ama tam olarak bir dosya olmayan bir şeyi ilettiğinizde kırılıyor çünkü açıkça kontrol ediyorlar. Bütün EAFP, ördek yazma olayı saçma sapan bir saflık testi değil. Bu gerçek bir
egineering

1
Bu daha iyi bir mühendislik olarak görülebilir ve kişisel olarak tercih ederim ama işe yaramayabilir. Dosya benzeri nesnelerin kalıtım yoluyla alması genellikle gerekli değildirIOBase . Örneğin, pytest armatürleri size _pytest.capture.EncodedFilehiçbir şeyden miras almayanları verir .
Tomáš Gavenčiak

46

Başkalarının da söylediği gibi, genellikle bu tür kontrollerden kaçınmalısınız. Bir istisna, nesnenin yasal olarak farklı türler olabileceği ve türe bağlı olarak farklı davranışlar istemenizdir. EAFP yöntemi burada her zaman işe yaramaz çünkü bir nesne birden fazla ördek türüne benzeyebilir!

Örneğin, bir başlatıcı kendi sınıfının bir dosyasını, dizesini veya örneğini alabilir. Daha sonra aşağıdaki gibi bir kodunuz olabilir:

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'yi burada kullanmak her türden ince soruna neden olabilir çünkü her başlatma yolu bir istisna atmadan önce kısmen çalıştırılır. Esasen bu yapı, aşırı yüklenmeyi taklit eder ve bu yüzden çok Pythonic değildir, ancak dikkatli kullanılırsa yararlı olabilir.

Bir yan not olarak, dosya denetimini Python 3'te aynı şekilde yapamazsınız isinstance(f, io.IOBase). Bunun yerine buna benzer bir şeye ihtiyacınız olacak .


28

Buradaki baskın paradigma EAFP'dir: af dilemek izin istemekten daha kolaydır. Devam edin ve dosya arayüzünü kullanın, ardından ortaya çıkan istisnayı ele alın veya bunların arayan kişiye yayılmasına izin verin.


9
+1: xDosya benzeri değilse , x.read()kendi istisnasını ortaya çıkaracaktır. Neden fazladan bir if-ifadesi yazasınız? Sadece nesneyi kullanın. Ya işe yarayacak ya da kırılacak.
S.Lott

3
İstisnayı bile halletme. Birisi beklediğiniz API ile eşleşmeyen bir şey geçirdiyse, bu sizin sorununuz değildir.
habnabit

1
@Aaron Gallagher: Emin değilim. Tutarlı bir durumu korumak benim için zor olsa bile ifadeniz doğru mu?
dmeister

1
Tutarlı bir durumu korumak için "dene / nihayet" (ama hariç!) Veya yeni "with" ifadesini kullanabilirsiniz.
drxzcl

Bu aynı zamanda "Hızlı başarısızlık ve yüksek sesle başarısız olma" paradigmasıyla da tutarlıdır. Titiz olmadıkça, açık hasattr (...) kontrolleri bazen bir fonksiyonun / yöntemin amaçlanan eylemini yerine getirmeden normal şekilde dönmesine neden olabilir.
Ben Burns

11

Bir hatanın normalde çok daha sonraya kadar ortaya çıkmayacağı durumlarda, bir koşulu kontrol ederek bir hatayı yükseltmek genellikle yararlıdır. Bu, özellikle 'kullanıcı alanı' ve 'api' kodu arasındaki sınır için geçerlidir.

Çıkış kapısına bir polis karakoluna metal dedektörü yerleştirmezsiniz, girişe yerleştirirsiniz! Bir koşulu kontrol etmemek, alt sınıfta yükseltilmek yerine 100 satır önce veya bir süper sınıfta yakalanabilecek bir hatanın meydana gelebileceği anlamına geliyorsa, o zaman kontrol etmede yanlış bir şey yok derim.

Birden fazla türü kabul ettiğinizde de doğru türleri kontrol etmek mantıklıdır. Bazı değişkenlerin 'arama' yöntemi olmadığı için bir istisna oluşturmaktansa, "Basestring, OR dosyasının bir alt sınıfına ihtiyacım var" diyen bir istisna oluşturmak daha iyidir ...

Bu, çıldırdığınız ve bunu her yerde yaptığınız anlamına gelmez, çoğunlukla, istisnaların kendiliğinden yükselmesi kavramına katılıyorum, ancak API'nizi büyük ölçüde netleştirebilir veya basit bir koşul yerine getirilmediğinden gereksiz kod yürütmeden kaçınabilirseniz böyle yap!


1
Katılıyorum, ancak bununla her yerde çıldırmama çizgisinde - bu endişelerin çoğu test sırasında ortaya çıkmalı ve "bunu nerede yakalayacağım / kullanıcıya nasıl göstereceğim" sorularının bazıları kullanılabilirlik gereksinimleri tarafından yanıtlanacak.
Ben Burns

7

Yöntemi deneyebilir ve çağırabilir, ardından istisnayı yakalayabilirsiniz:

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

Yalnızca bir okuma ve yazma yöntemi istiyorsanız, bunu yapabilirsiniz:

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

Senin yerinde olsaydım, dene / hariç yöntemini kullanırdım.


Örneklerin sırasını değiştirmenizi öneririm. tryher zaman ilk tercihtir. hasattrÇekler sadece - Bazı gerçekten anlaşılmaz bir sebeple - sadece kullanamazsınız try.
S. Lot

1
Verileri daha sonra işlemek istiyorsanız , tüm kodu bloğa koymamak için fp.read(0)yerine kullanmanızı öneririm . fp.read()tryfp
Meow

3
fp.read()Büyük dosyalarda bellek kullanımını anında artıracağını unutmayın .
Kyrylo Perevozchikov

Bunun pitonik olduğunu anlıyorum, ancak diğer konuların yanı sıra dosyayı iki kez okumamız gerekiyor. Örneğin, Flaskbunu yaptım ve temeldeki FileStoragenesnenin okunduktan sonra işaretçinin sıfırlanması gerektiğini fark ettim .
Adam Hughes

2

Çoğu durumda, bunu halletmenin en iyi yolu yapmamaktır. Bir yöntem dosya benzeri bir nesne alırsa ve iletildiği nesnenin olmadığı ortaya çıkarsa, yöntem nesneyi kullanmaya çalıştığında ortaya çıkan istisna, açıkça oluşturmuş olabileceğiniz herhangi bir istisnadan daha az bilgilendirici değildir.

Yine de, bu tür bir kontrol yapmak isteyebileceğiniz en az bir durum vardır ve bu, nesneyi aktardığınız şey tarafından hemen kullanılmadığında, örneğin bir sınıfın kurucusunda ayarlanmışsa. Bu durumda, EAFP ilkesinin "hızlı başarısız olma" ilkesine dayandığını düşünürdüm. Sınıfımın ihtiyaç duyduğu yöntemleri uyguladığından (ve bunların yöntemler olduğundan) emin olmak için nesneyi kontrol ederdim, örneğin:

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

1
Neden getattr(file, 'read')sadece yerine file.read? Bu aynı şeyi yapıyor.
abarnert

1
Daha da önemlisi, bu kontrol yanlıştır. fileÖrneğin , gerçek bir örnek verildiğinde yükselecektir . (Yerleşik / C-uzantı türlerinin örneklerinin yöntemleri, türdeyken builtin_function_or_methodeski tarz sınıfların yöntemidir instancemethod). Bunun eski tarz bir sınıf olması ==ve ininstanceya da yerine türlerde kullanması issubclassdaha fazla sorun teşkil ediyor , ancak temel fikir işe yaramazsa, bu pek önemli değil.
abarnert

2

openBir dosya adını, dosya tanımlayıcısını veya önceden açılmış dosya benzeri nesneyi kabul edebilecek benzer bir işlev yazarken sorunuzla karşılaştım .

Bir readyöntemi test etmek yerine , diğer yanıtların önerdiği gibi, nesnenin açılıp açılamayacağını kontrol ettim. Mümkünse, bu bir dizge veya tanımlayıcıdır ve sonuçtan elimde geçerli bir dosya benzeri nesne var. A openyükseltirse TypeError, nesne zaten bir dosyadır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.