Kiểm tra xem đối tượng có giống tệp trong Python không


93

Các đối tượng giống tệp là các đối tượng trong Python hoạt động giống như một tệp thực, ví dụ: có phương thức read () và write (), nhưng có cách triển khai khác. Đó là và hiện thực hóa khái niệm Duck Typing .

Việc cho phép một đối tượng giống tệp ở mọi nơi mà tệp được mong đợi được coi là thực tiễn tốt để ví dụ như đối tượng StringIO hoặc Socket có thể được sử dụng thay thế cho một tệp thực. Vì vậy, thật tệ khi thực hiện kiểm tra như thế này:

if not isinstance(fp, file):
   raise something

Cách tốt nhất để kiểm tra xem một đối tượng (ví dụ: một tham số của một phương thức) là "giống tệp" là gì?

Câu trả lời:


45

Thông thường, việc kiểm tra như thế này trong mã của bạn là không tốt chút nào trừ khi bạn có yêu cầu đặc biệt.

Trong Python, thao tác nhập là động, tại sao bạn cần kiểm tra xem đối tượng có phải là tệp giống như tệp hay không, thay vì chỉ sử dụng nó như thể nó là tệp và xử lý lỗi phát sinh?

Bất kỳ kiểm tra nào bạn có thể thực hiện đều sẽ xảy ra trong thời gian chạy vì vậy thực hiện một số việc như if not hasattr(fp, 'read')và nêu ra một số ngoại lệ cung cấp ít tiện ích hơn là chỉ gọi fp.read()và xử lý lỗi thuộc tính kết quả nếu phương thức không tồn tại.


whynhững gì về các toán tử như __add__, __lshift__hoặc __or__trong các lớp tùy chỉnh? (object file và API: docs.python.org/glossary.html#term-file-object )
n611x007

@naxa: Vậy chính xác thì những toán tử đó là gì?
martineau

32
Thường chỉ cần thử nó sẽ hoạt động nhưng tôi không mua câu châm ngôn Pythonic rằng nếu nó khó thực hiện bằng Python thì nó sai. Hãy tưởng tượng bạn được chuyển một đối tượng và có 10 điều khác nhau bạn có thể làm với đối tượng đó tùy thuộc vào loại của nó. Bạn sẽ không thử từng khả năng và xử lý lỗi cho đến khi cuối cùng bạn làm đúng. Điều đó sẽ hoàn toàn không hiệu quả. Bạn không nhất thiết cần phải hỏi, loại là thế này, nhưng bạn cần để có thể hỏi không đối tượng này thực hiện giao diện X.
jcoffland

31
Thực tế là thư viện bộ sưu tập python cung cấp những gì có thể được gọi là "loại giao diện" (ví dụ: chuỗi) nói lên thực tế rằng điều này thường hữu ích, ngay cả trong python. Nói chung, khi ai đó hỏi "làm thế nào để foo", "không foo" không phải là một câu trả lời thỏa đáng.
AdamC

1
AttributeError có thể tăng lên vì nhiều lý do không liên quan đến việc đối tượng có hỗ trợ giao diện bạn cần hay không. hasattr là cần thiết cho các filelikes không bắt nguồn từ IOBase
Erik Aronesty

74

Đối với 3.1+, một trong những điều sau:

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

Đối với 2.x, "đối tượng giống tệp" là một thứ quá mơ hồ để kiểm tra, nhưng tài liệu cho bất kỳ (các) chức năng nào bạn đang xử lý sẽ hy vọng cho bạn biết chúng thực sự cần gì; nếu không, hãy đọc mã.


Như các câu trả lời khác đã chỉ ra, điều đầu tiên cần hỏi là bạn đang kiểm tra chính xác điều gì. Thông thường, EAFP là đủ và mang tính thành ngữ hơn.

Các thuật ngữ nói "tập tin giống như đối tượng" là một từ đồng nghĩa với "đối tượng tập tin", mà cuối cùng có nghĩa là nó là một thể hiện của một trong ba lớp cơ sở trừu tượng quy định tại các iomô-đun , đó là bản thân tất cả các lớp con của IOBase. Vì vậy, cách kiểm tra chính xác như hình trên.

(Tuy nhiên, việc kiểm tra IOBasekhông hữu ích lắm. Bạn có thể tưởng tượng một trường hợp mà bạn cần phân biệt một tệp thực tế giống read(size)với một hàm một đối số readnào đó có tên không giống tệp, mà không cần phân biệt giữa tệp văn bản và tệp thô tệp nhị phân? Vì vậy, thực sự, bạn hầu như luôn muốn kiểm tra, ví dụ: "có phải là một đối tượng tệp văn bản", không phải "là một đối tượng giống tệp".)


Đối với 2.x, mặc dù iomô-đun đã tồn tại từ 2.6+, nhưng các đối tượng tệp tích hợp không phải là phiên bản của iocác lớp, không phải bất kỳ đối tượng giống tệp nào trong stdlib và hầu hết các đối tượng giống tệp của bên thứ ba cũng vậy. có khả năng gặp phải. Không có định nghĩa chính thức về "đối tượng giống tệp" nghĩa là gì; nó chỉ là "một cái gì đó giống như một đối tượng tệp nội trang " và các chức năng khác nhau có nghĩa là những thứ khác nhau bởi "như". Các chức năng như vậy nên ghi lại ý nghĩa của chúng; nếu không, bạn phải xem mã.

Tuy nhiên, các ý nghĩa phổ biến nhất là "có read(size)", "có read()" hoặc "là một chuỗi có thể lặp lại", nhưng một số thư viện cũ có thể mong đợi readlinethay vì một trong những ý nghĩa đó, một số thư viện thích close()các tệp bạn cung cấp, một số sẽ mong đợi rằng nếu filenohiện tại thì chức năng khác có sẵn, v.v. Và tương tự cho write(buf)(mặc dù có ít tùy chọn hơn theo hướng đó).


1
Cuối cùng, ai đó đang giữ cho nó có thật.
Anthony Rutledge

16
Câu trả lời hữu ích duy nhất. Tại sao StackOverflowers tiếp tục ủng hộ "Hãy ngừng làm những gì bạn đang cố gắng làm, bởi vì tôi biết rõ hơn ... và PEP 8, EAFP, và những thứ khác!" bài viết nằm ngoài sự tỉnh táo mong manh của tôi. ( Có lẽ Cthulhu biết? )
Cecil Curry

1
Bởi vì chúng tôi đã gặp phải quá nhiều mã được viết bởi những người không suy nghĩ trước và nó bị hỏng khi bạn chuyển nó qua một cái gì đó gần như là, nhưng không hoàn toàn là một tệp vì họ kiểm tra rõ ràng. Toàn bộ EAFP, thứ gõ vịt không phải là một bài kiểm tra độ tinh khiết nhảm nhí. Đây là một quyết định egineering thực tế,
drxzcl

1
Đây có thể được coi là kỹ thuật tốt hơn và cá nhân tôi thích nó hơn, nhưng có thể không hoạt động. Nói chung không bắt buộc phải kế thừa các đối tượng giống tệp IOBase. Ví dụ, đồ đạc pytest cung cấp cho bạn _pytest.capture.EncodedFilethứ không kế thừa từ bất cứ thứ gì.
Tomáš Gavenčiak

46

Như những người khác đã nói, bạn thường nên tránh kiểm tra như vậy. Một ngoại lệ là khi đối tượng hợp pháp có thể là các loại khác nhau và bạn muốn có các hành vi khác nhau tùy thuộc vào loại. Phương thức EAFP không phải lúc nào cũng hoạt động ở đây vì một đối tượng có thể trông giống như nhiều loại vịt!

Ví dụ, một trình khởi tạo có thể lấy một tệp, chuỗi hoặc thể hiện của lớp riêng của nó. Sau đó, bạn có thể có mã như:

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

Sử dụng EAFP ở đây có thể gây ra tất cả các loại vấn đề phức tạp vì mỗi đường dẫn khởi tạo được chạy một phần trước khi đưa ra một ngoại lệ. Về cơ bản cấu trúc này bắt chước chức năng quá tải và do đó không phải là Pythonic cho lắm, nhưng nó có thể hữu ích nếu được sử dụng cẩn thận.

Một lưu ý nhỏ là bạn không thể kiểm tra tệp theo cách tương tự trong Python 3. isinstance(f, io.IOBase)Thay vào đó, bạn sẽ cần một cái gì đó tương tự .


28

Mô hình nổi trội ở đây là EAFP: dễ xin tha hơn xin phép. Hãy tiếp tục và sử dụng giao diện tệp, sau đó xử lý ngoại lệ kết quả hoặc để chúng phổ biến tới người gọi.


9
+1: Nếu xkhông giống tệp, thì x.read()sẽ nâng ngoại lệ của chính nó. Tại sao phải viết thêm một câu lệnh if? Chỉ sử dụng đối tượng. Nó sẽ hoạt động hoặc phá vỡ.
S.Lott

3
Thậm chí không xử lý ngoại lệ. Nếu ai đó đã chuyển một thứ gì đó không khớp với API bạn mong đợi, đó không phải là vấn đề của bạn.
habnabit

1
@Aaron Gallagher: Tôi không chắc. Câu nói của bạn có đúng ngay cả khi tôi khó giữ được trạng thái nhất quán?
dmeister

1
Để duy trì trạng thái nhất quán, bạn có thể sử dụng "try / last" (nhưng không ngoại trừ!) Hoặc câu lệnh mới "with".
drxzcl

Điều này cũng phù hợp với mô hình "Thất bại nhanh và thất bại lớn". Trừ khi bạn tỉ mỉ, việc kiểm tra hàm hasattr (...) rõ ràng đôi khi có thể khiến một hàm / phương thức trở lại bình thường mà không thực hiện hành động dự kiến ​​của nó.
Ben Burns

11

Việc nâng lỗi bằng cách kiểm tra một điều kiện thường rất hữu ích, khi lỗi đó thông thường sẽ không được phát sinh cho đến rất lâu sau này. Điều này đặc biệt đúng với ranh giới giữa mã 'đất người dùng' và mã 'api'.

Bạn sẽ không đặt máy dò kim loại ở đồn cảnh sát ở cửa thoát hiểm, bạn sẽ đặt nó ở lối vào! Nếu không kiểm tra một điều kiện có nghĩa là một lỗi có thể xảy ra có thể đã được bắt trước 100 dòng hoặc trong một lớp siêu cấp thay vì được nâng lên trong lớp con thì tôi nói rằng không có gì sai khi kiểm tra.

Kiểm tra các loại thích hợp cũng có ý nghĩa khi bạn chấp nhận nhiều hơn một loại. Tốt hơn nên nêu ra một ngoại lệ có nội dung "Tôi yêu cầu một lớp con của tệp basestring, OR" hơn là chỉ tăng một ngoại lệ vì một số biến không có phương thức 'seek' ...

Điều này không có nghĩa là bạn trở nên điên rồ và làm điều này ở khắp mọi nơi, phần lớn tôi đồng ý với khái niệm ngoại lệ tự nâng cao, nhưng nếu bạn có thể làm rõ ràng đáng kể API của mình hoặc tránh thực thi mã không cần thiết vì một điều kiện đơn giản chưa được đáp ứng làm như vậy!


1
Tôi đồng ý, nhưng không nên phát điên lên với điều này ở mọi nơi - rất nhiều mối quan tâm này sẽ được giải quyết trong quá trình thử nghiệm và một số câu hỏi "bắt cái này ở đâu / cách hiển thị cho người dùng" sẽ được giải đáp theo yêu cầu về khả năng sử dụng.
Ben Burns

7

Bạn có thể thử và gọi phương thức sau đó bắt ngoại lệ:

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

Nếu bạn chỉ muốn một phương pháp đọc và ghi, bạn có thể làm như sau:

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

Nếu tôi là bạn, tôi sẽ sử dụng phương pháp thử / ngoại trừ.


Tôi khuyên bạn nên chuyển thứ tự của các ví dụ. tryluôn là sự lựa chọn hàng đầu. Các hasattrséc chỉ - vì một số lý do thực sự khó hiểu - bạn không thể đơn giản sử dụng try.
S.Lott

1
Tôi khuyên bạn nên sử dụng fp.read(0)thay vì fp.read()để tránh đặt tất cả mã vào trykhối nếu bạn muốn xử lý dữ liệu fpsau đó.
Meo

3
Lưu ý rằng fp.read()với các tập tin lớn sẽ ngay lập tức tăng mức sử dụng bộ nhớ.
Kyrylo Perevozchikov

Tôi hiểu đây là pythonic, nhưng sau đó chúng tôi phải đọc tệp hai lần trong số các vấn đề khác. Ví dụ, trong Flasktôi đã làm điều này và nhận ra FileStorageđối tượng cơ bản cần thiết lập lại con trỏ của nó sau khi được đọc.
Adam Hughes

2

Trong hầu hết các trường hợp, cách tốt nhất để xử lý điều này là không. Nếu một phương thức nhận một đối tượng giống tệp và hóa ra không phải đối tượng mà nó được truyền vào, thì ngoại lệ được đưa ra khi phương thức cố gắng sử dụng đối tượng không ít thông tin hơn bất kỳ ngoại lệ nào mà bạn có thể đã nêu ra một cách rõ ràng.

Tuy nhiên, có ít nhất một trường hợp mà bạn có thể muốn thực hiện loại kiểm tra này và đó là khi đối tượng không được sử dụng ngay lập tức bởi những gì bạn đã chuyển nó đến, ví dụ: nếu nó đang được đặt trong hàm tạo của một lớp. Trong trường hợp đó, tôi sẽ nghĩ rằng nguyên tắc của EAFP bị lấn át bởi nguyên tắc "thất bại nhanh". Tôi sẽ kiểm tra đối tượng để đảm bảo rằng nó đã triển khai các phương thức mà lớp của tôi cần (và chúng là các phương thức), ví dụ:

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

1
Tại sao getattr(file, 'read')thay vì chỉ file.read? Điều này làm điều tương tự.
abarnert

1
Quan trọng hơn, việc kiểm tra này là sai. Nó sẽ tăng lên khi đưa ra một fileví dụ thực tế . (Các phương thức của các phiên bản của kiểu nội trang / mở rộng C là kiểu builtin_function_or_method, trong khi của các kiểu cũ là như vậy instancemethod). Thực tế rằng đây là một lớp kiểu cũ và nó sử dụng ==trên các kiểu thay vì ininstancehoặc issubclass, là những vấn đề xa hơn, nhưng nếu ý tưởng cơ bản không hoạt động, điều đó hầu như không quan trọng.
abarnert

2

Tôi đã gặp phải câu hỏi của bạn khi tôi đang viết một openhàm -like có thể chấp nhận tên tệp, trình mô tả tệp hoặc đối tượng giống tệp được mở trước.

Thay vì thử nghiệm một readphương pháp, như các câu trả lời khác đề xuất, tôi đã kết thúc việc kiểm tra xem đối tượng có thể mở được hay không. Nếu có thể, đó là một chuỗi hoặc bộ mô tả và tôi có trong tay một đối tượng giống tệp hợp lệ từ kết quả. Nếu opentăng a TypeError, thì đối tượng đã là một tệp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.