Có thể có nhiều lớp trong cùng một tệp trong Python không?


18

Tôi mới đến thế giới Python sau nhiều năm Java và PHP. Mặc dù ngôn ngữ khá đơn giản, nhưng tôi đang phải vật lộn với một số vấn đề 'nhỏ' mà tôi không thể giải quyết được - và tôi không thể tìm thấy câu trả lời trong nhiều tài liệu và hướng dẫn mà tôi đã đọc đến nay .

Đối với người thực hành Python có kinh nghiệm, câu hỏi này có vẻ ngớ ngẩn, nhưng tôi thực sự muốn có câu trả lời cho nó để tôi có thể đi xa hơn với ngôn ngữ:

Trong Java và PHP ( mặc dù không bắt buộc ), bạn sẽ phải viết từng classtệp trên tệp riêng của mình, với tên tệp classlà cách thực hành tốt nhất.

Nhưng trong Python, hoặc ít nhất là trong các hướng dẫn Tôi đã kiểm tra, nó là ok có nhiều lớp trong cùng một tập tin.

Liệu quy tắc này có giữ trong sản xuất, mã sẵn sàng triển khai hay nó được thực hiện chỉ vì sự ngắn gọn trong mã chỉ mang tính giáo dục?

Câu trả lời:


13

Có thể có nhiều lớp trong cùng một tệp trong Python không?

Đúng. Cả hai từ góc độ triết học cũng như thực tiễn.

Trong Python, các mô-đun là một không gian tên tồn tại một lần trong bộ nhớ.

Giả sử chúng tôi có cấu trúc thư mục giả định sau, với một lớp được xác định cho mỗi tệp:

                    Defines
 abc/
 |-- callable.py    Callable
 |-- container.py   Container
 |-- hashable.py    Hashable
 |-- iterable.py    Iterable
 |-- iterator.py    Iterator
 |-- sized.py       Sized
 ... 19 more

Tất cả các lớp này đều có sẵn trong collectionsmô-đun và (trên thực tế, có tổng cộng 25) được định nghĩa trong mô-đun thư viện tiêu chuẩn trong_collections_abc.py

Có một vài vấn đề ở đây mà tôi tin rằng làm cho sự _collections_abc.pyvượt trội so với cấu trúc thư mục giả thuyết thay thế.

  • Các tập tin này được sắp xếp theo thứ tự abc. Bạn có thể sắp xếp chúng theo những cách khác, nhưng tôi không biết về một tính năng sắp xếp các tệp theo các phụ thuộc ngữ nghĩa. Nguồn mô-đun _collections_abc được tổ chức theo sự phụ thuộc.
  • Trong các trường hợp không bệnh lý, cả hai mô-đun và định nghĩa lớp là singletons, xảy ra một lần trong bộ nhớ. Sẽ có một ánh xạ mô phỏng của các mô-đun vào các lớp - làm cho các mô-đun trở nên dư thừa.
  • Số lượng tệp ngày càng tăng làm cho việc đọc tình cờ qua các lớp trở nên ít thuận tiện hơn (trừ khi bạn có một IDE làm cho nó đơn giản) - làm cho mọi người không thể truy cập mà không có công cụ.

Bạn có bị ngăn không cho chia các nhóm lớp thành các mô-đun khác nhau khi bạn thấy nó mong muốn từ góc độ tổ chức và không gian tên?

Không.

Từ Zen of Python , phản ánh triết lý và nguyên tắc mà theo đó nó phát triển và phát triển:

Không gian tên là một ý tưởng tuyệt vời - hãy làm nhiều hơn nữa!

Nhưng chúng ta hãy nhớ rằng nó cũng nói:

Bằng phẳng là tốt hơn so với lồng nhau.

Python cực kỳ sạch sẽ và dễ đọc. Nó khuyến khích bạn đọc nó. Đặt mỗi lớp riêng biệt trong một tệp riêng biệt không khuyến khích đọc. Điều này đi ngược lại triết lý cốt lõi của Python. Nhìn vào cấu trúc của Thư viện chuẩn , phần lớn các mô-đun là các mô-đun một tệp, không phải các gói. Tôi sẽ gửi cho bạn rằng mã Python thành ngữ được viết theo cùng kiểu với lib chuẩn CPython.

Đây là mã thực tế từ mô-đun lớp cơ sở trừu tượng . Tôi thích sử dụng nó như một tài liệu tham khảo cho việc biểu thị các loại trừu tượng khác nhau trong ngôn ngữ.

Bạn có nói rằng mỗi lớp này cần một tệp riêng không?

class Hashable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __hash__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Hashable:
            try:
                for B in C.__mro__:
                    if "__hash__" in B.__dict__:
                        if B.__dict__["__hash__"]:
                            return True
                        break
            except AttributeError:
                # Old-style class
                if getattr(C, "__hash__", None):
                    return True
        return NotImplemented


class Iterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterable:
            if _hasattr(C, "__iter__"):
                return True
        return NotImplemented

Iterable.register(str)


class Iterator(Iterable):

    @abstractmethod
    def next(self):
        'Return the next item from the iterator. When exhausted, raise StopIteration'
        raise StopIteration

    def __iter__(self):
        return self

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Iterator:
            if _hasattr(C, "next") and _hasattr(C, "__iter__"):
                return True
        return NotImplemented


class Sized:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            if _hasattr(C, "__len__"):
                return True
        return NotImplemented


class Container:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if _hasattr(C, "__contains__"):
                return True
        return NotImplemented


class Callable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __call__(self, *args, **kwds):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Callable:
            if _hasattr(C, "__call__"):
                return True
        return NotImplemented

Vì vậy, mỗi người nên có tập tin riêng của họ?

Tôi hy vọng là không.

Các tệp này không chỉ là mã - chúng là tài liệu về ngữ nghĩa của Python.

Họ có thể trung bình 10 đến 20 dòng. Tại sao tôi phải vào một tệp hoàn toàn riêng biệt để xem 10 dòng mã khác? Điều đó sẽ rất không thực tế. Hơn nữa, sẽ có các bản nhập nồi hơi gần như giống hệt nhau trên mỗi tệp, thêm nhiều dòng mã dự phòng.

Tôi thấy khá hữu ích khi biết rằng có một mô-đun duy nhất nơi tôi có thể tìm thấy tất cả các Lớp cơ sở trừu tượng này, thay vì phải xem qua danh sách các mô-đun. Xem chúng trong bối cảnh với nhau cho phép tôi hiểu rõ hơn về chúng. Khi tôi thấy rằng một Iterator là một Iterable, tôi có thể nhanh chóng xem lại những gì một Iterable bao gồm bằng cách liếc lên.

Thỉnh thoảng tôi có một vài lớp học rất ngắn. Họ ở lại trong tập tin, ngay cả khi họ cần phát triển lớn hơn theo thời gian. Đôi khi các mô-đun trưởng thành có hơn 1000 dòng mã. Nhưng ctrl-f rất dễ và một số IDE giúp dễ dàng xem các phác thảo của tệp - vì vậy cho dù tệp lớn đến đâu, bạn có thể nhanh chóng đi đến bất kỳ đối tượng hoặc phương thức nào bạn đang tìm kiếm.

Phần kết luận

Hướng của tôi, trong ngữ cảnh của Python, là thích giữ các định nghĩa lớp tương tự và có liên quan về mặt ngữ nghĩa trong cùng một tệp. Nếu tập tin phát triển quá lớn đến mức trở nên khó sử dụng, thì hãy xem xét tổ chức lại.


1
Chà, trong khi tôi hiểu, nhờ vào mã bạn đã gửi mà có nhiều lớp trong cùng một tệp, tôi không thể tìm thấy lập luận rất thuyết phục. Ví dụ, PHP rất phổ biến khi có toàn bộ tệp chỉ có mã tương tự như sau:class SomeException extends \Exception {}
Olivier Malki

3
Các cộng đồng khác nhau có các tiêu chuẩn mã hóa khác nhau. Người Java nhìn vào python và nói "tại sao nó cho phép nhiều lớp trên mỗi tệp!?". Người Python nhìn vào Java và nói "tại sao nó yêu cầu mỗi lớp có tệp riêng!?". Tốt nhất là theo phong cách của cộng đồng bạn đang làm việc.
Gort the Robot

Tôi cũng bối rối ở đây. Rõ ràng tôi đã hiểu nhầm một số điều về Python với câu trả lời của tôi. Nhưng liệu người ta có áp dụng "căn hộ tốt hơn lồng nhau" để thêm nhiều phương thức nhất có thể vào một lớp không? Nói chung, tôi nghĩ rằng các nguyên tắc gắn kết và SRP vẫn áp dụng cho các mô đun ưu tiên mô đun cung cấp các lớp liên quan chặt chẽ với nhau về chức năng (mặc dù có lẽ rất nhiều hơn một lớp vì mô đun mô hình khái niệm gói thô hơn một lớp đơn ), đặc biệt vì bất kỳ biến phạm vi mô-đun nào (mặc dù hy vọng tránh được nói chung) sẽ tăng phạm vi.

1
Zen of Python là một danh sách các nguyên tắc gây căng thẳng với nhau. Người ta có thể trả lời đồng ý với quan điểm của bạn với "Thưa thì tốt hơn là dày đặc". - ngay lập tức theo sau, "Căn hộ tốt hơn so với lồng nhau." Các dòng riêng lẻ từ Zen của Python có thể dễ dàng bị sử dụng sai và bị cực đoan, nhưng nói chung, nó có thể hỗ trợ cho nghệ thuật mã hóa và tìm ra điểm chung nơi những người hợp lý có thể không đồng ý. Tôi không nghĩ mọi người sẽ coi các ví dụ mã của tôi dày đặc, nhưng những gì bạn mô tả nghe có vẻ rất dày đặc đối với tôi.
Aaron Hall

Cảm ơn bạn Jeffrey Albertson / Guy Comic Comic. :) Hầu hết người dùng Python không nên sử dụng các phương thức đặc biệt (gạch dưới hai lần), nhưng họ cho phép một nhà thiết kế / kiến ​​trúc sư cốt lõi tham gia vào lập trình meta để sử dụng tùy chỉnh các toán tử, bộ so sánh, ký hiệu đăng ký, bối cảnh và các thứ khác tính năng ngôn ngữ. Chừng nào họ không vi phạm nguyên tắc ít gây bất ngờ nhất, tôi nghĩ rằng tác hại của tỷ lệ giá trị là vô cùng lớn.
Aaron Hall

4

Khi cấu trúc ứng dụng của bạn trong Python, bạn cần suy nghĩ về các gói và mô-đun.

Các mô-đun là về các tập tin bạn đang nói về. Sẽ rất tốt nếu có một loạt các lớp trong cùng một mô-đun. Mục đích là tất cả các lớp trong cùng một mô-đun sẽ phục vụ cùng một mục đích / logic. Nếu mô-đun đi quá lâu thì hãy nghĩ đến việc chia nhỏ nó bằng cách thiết kế lại logic của bạn.

Đừng quên thỉnh thoảng đọc về Đề xuất cải tiến Python .


2

Câu trả lời thực sự cho vấn đề này là chung chung và không phụ thuộc vào ngôn ngữ được sử dụng: Những gì nên có trong một tệp không chủ yếu phụ thuộc vào số lượng lớp mà nó định nghĩa. Nó phụ thuộc vào sự kết nối hợp lý, và vào sự phức tạp. Giai đoạn = Stage.

Vì vậy, nếu bạn có một vài lớp rất nhỏ có tính liên kết cao, chúng nên được gói vào cùng một tệp. Bạn nên tách ra một lớp nếu nó không được kết nối chặt chẽ với một lớp khác hoặc nếu nó quá phức tạp để được bao gồm với một lớp khác.

Điều đó nói rằng, quy tắc một lớp cho mỗi tệp thường là một heuristic tốt. Tuy nhiên, có những trường hợp ngoại lệ quan trọng: Một lớp trình trợ giúp nhỏ thực sự chỉ là chi tiết triển khai của lớp người dùng duy nhất nói chung nên được gói vào tệp của lớp người dùng đó. Tương tự như vậy, nếu bạn có ba lớp vector2, vector3vector4, có rất ít lý do để thực hiện chúng trong các tệp riêng biệt.


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.