2013-08-30 44 views
13

Câu hỏi này được xây dựng trên this one asked earlier và cũng this one, nhưng đề cập đến một điểm tinh tế hơn, cụ thể, những gì được tính là "lớp nội bộ"?dấu gạch dưới dẫn đầu trong tên lớp Python

Đây là tình huống của tôi. Tôi đang xây dựng một thư viện Python để thao tác các tệp MS Office, trong đó nhiều lớp không được xây dựng bên ngoài, nhưng nhiều phương thức của chúng là các phần quan trọng của API. Ví dụ:

class _Slide(object): 
    def add_shape(self): 
     ... 

_Slide không phải là để được xây dựng bên ngoài, như một người dùng cuối của thư viện bạn sẽ có được một slide mới bằng cách gọi Presentation.add_slide(). Tuy nhiên, một khi bạn có một slide bạn chắc chắn muốn có thể gọi add_shape(). Vì vậy, phương thức này là một phần của API nhưng hàm tạo không phải là. Tình huống này phát sinh hàng chục lần trong thư viện bởi vì chỉ có lớp Presentation có một hàm tạo bên ngoài/API.

PEP8 không rõ ràng về điểm này, tham chiếu đến "các lớp nội bộ" mà không cần tính toán những gì được tính là lớp nội bộ. Trong trường hợp này tôi giả sử người ta có thể nói rằng lớp học là "một phần nội bộ".

Vấn đề là tôi chỉ có hai ký hiệu để phân biệt ít nhất ba tình huống khác nhau:

  1. Các nhà xây dựng và "công cộng/API" phương thức của lớp đều có sẵn để sử dụng.
  2. Hàm tạo không được sử dụng, nhưng phương thức "công khai/API" của lớp là.
  3. Lớp học thực sự là nội bộ, không có API công khai và tôi có quyền thay đổi hoặc xóa lớp đó trong bản phát hành trong tương lai.

Tôi lưu ý rằng từ góc độ giao tiếp, có xung đột giữa biểu hiện rõ ràng của API bên ngoài (đối tượng người dùng cuối thư viện) và API nội bộ (đối tượng nhà phát triển). Đối với người dùng cuối, gọi số _Slide() là rủi ro do chính mình gây ra. Tuy nhiên, đây là thành viên hài lòng của API nội bộ và khác với _random_helper_method() chỉ nên được gọi từ trong số _Slide().

Bạn có quan điểm về câu hỏi này có thể giúp tôi không? Quy ước có chỉ ra rằng tôi sử dụng đạn "đơn hàng đầu" dưới tên lớp để đấu tranh cho sự rõ ràng trong API người dùng cuối hay tôi có thể cảm thấy tốt khi đặt nó để giao tiếp với bản thân và các nhà phát triển khác. riêng tư và không được sử dụng, ví dụ, bởi các đối tượng bên ngoài mô-đun mà nó tồn tại bởi vì nó là chi tiết triển khai có thể thay đổi?


UPDATE: Sau một vài năm phản ánh thêm, tôi đã giải quyết thành công ước của việc sử dụng một dấu gạch dưới hàng đầu khi đặt tên lớp mà không được dự định để được truy cập như là một lớp bên ngoài mô-đun của họ (tập tin). Truy cập như vậy thường để khởi tạo một đối tượng của lớp đó hoặc để truy cập một phương thức lớp học, v.v.

Điều này cung cấp cho người dùng mô-đun (thường là bản thân bạn :) chỉ báo cơ bản: "Nếu tên lớp có dấu gạch dưới hàng đầu xuất hiện trong câu lệnh nhập, bạn đang làm gì đó sai. " (Các mô-đun thử nghiệm đơn vị là ngoại lệ đối với quy tắc này, các lần nhập như vậy thường có thể xuất hiện trong các bài kiểm tra đơn vị cho lớp "nội bộ").

Lưu ý rằng đây là quyền truy cập vào lớp.Truy cập vào một đối tượng của loại đó (loại) bên ngoài mô-đun, có thể được cung cấp bởi một nhà máy hoặc bất cứ điều gì, là hoàn toàn tốt và có thể mong đợi. Tôi nghĩ rằng sự thất bại này để phân biệt các lớp từ các đối tượng được tạo ra từ chúng là những gì đã dẫn đến sự nhầm lẫn ban đầu của tôi.

Quy ước này cũng có lợi ích phụ không bao gồm các lớp này khi đưa ra tuyên bố from module import *. Mặc dù tôi không bao giờ sử dụng chúng trong mã của riêng tôi và khuyên bạn nên tránh chúng, nó là một hành vi thích hợp bởi vì những định danh lớp không có ý định là một phần của giao diện mô-đun.

Đây là "thực hành tốt nhất" cá nhân của tôi sau nhiều năm dùng thử và dĩ nhiên không phải là cách "đúng". Số dặm của bạn có thể thay đổi.

+0

Tôi mất một chút để quyết định rằng điều này không được tính là chủ yếu dựa trên ý kiến. Các từ ngữ có thể được cải thiện, mặc dù. – user2357112

+0

Sau khi gọi 'Presentation.add_slide()', trang trình bày được tạo ra như thế nào? Nếu nó thông qua một cá thể '_Slide()' mà người dùng có, '_Slide' là một lớp công khai hiệu quả. – martineau

+0

@martineau: Tôi nghĩ rằng đây là câu trả lời khá nhiều. Kết luận tôi đã đưa ra sau khi bạn đề cập đến điều này là một lớp là nội bộ nếu không có phần nào của nó (hàm tạo, phương thức) được sử dụng * bên ngoài mô đun của nó *. Và đó là dấu gạch dưới hàng đầu trong tên lớp sẽ được sử dụng cho các lớp học đáp ứng mô tả này. Nếu bạn sẽ đưa vào một câu trả lời tôi sẽ chấp nhận nó. – scanny

Trả lời

3

Tôi không thể nói từ mô tả chỉ trong câu hỏi của bạn, nhưng từ thông tin bổ sung bạn đã cung cấp trong nhận xét, tôi nghĩ rằng lớp học Slide của bạn thực sự là công khai. Điều này là đúng mặc dù thực tế là trường hợp sẽ chỉ được tạo gián tiếp bằng cách gọi phương thức add_slide() của Presentation vì người gọi sau đó sẽ được miễn phí (và có nhiều khả năng bắt buộc) để gọi các phương thức của cá thể để thao tác nó sau đó. Theo tôi một lớp học thực sự riêng tư sẽ chỉ được truy cập bởi các phương pháp của lớp mà "sở hữu" nó.

Để mọi thứ theo cách khác, cả hai đều phá vỡ đóng gói và tăng sự ghép nối giữa các thành phần trong thiết kế của bạn, cả hai đều không mong muốn và nên tránh càng nhiều càng tốt cho tính linh hoạt và khả năng sử dụng lại.

0

__all__ = ['Presentation'] trong mô-đun ẩn tất cả các lớp khỏi các hàm như help() ngoại trừ lớp Bản trình bày. Nhưng các nhà phát triển vẫn có quyền truy cập để gọi các nhà thầu và phương pháp từ bên ngoài.

4

Tôi nghĩ câu trả lời của martineau là câu trả lời hay nhất, chắc chắn là đơn giản nhất và không nghi ngờ gì nhiều nhất về mặt pythonic.

Đó là, tuy nhiên, không có nghĩa là tùy chọn duy nhất.

Kỹ thuật được sử dụng thường xuyên là xác định các phương thức công khai như là một phần của loại giao diện; ví dụ zope.interface.Interface được sử dụng rộng rãi trong khuôn khổ xoắn. Con trăn hiện đại có lẽ sẽ sử dụng abc.ABCMeta cho cùng một hiệu ứng. Về cơ bản, phương thức công khai Presentation.add_slide được ghi lại là một số ví dụ về AbstractSlide, có nhiều phương thức công khai hơn; nhưng vì không thể xây dựng một thể hiện trực tiếp của AbstractSlide, nên không có cách nào để tạo một thể hiện.

Kỹ thuật đó có thể đặc biệt tiện dụng, vì các thể hiện giả của kiểu trừu tượng có thể được tạo có thể khẳng định rằng chỉ có các phương thức công khai được gọi; khá hữu ích cho thử nghiệm đơn vị (đặc biệt là đối với người dùng thư viện của bạn, để họ có thể đảm bảo rằng họ chỉ sử dụng giao diện công cộng).

Một tùy chọn khác; vì cách công khai duy nhất để tạo các phiên bản của lớp slide là thông qua Presentation.add_slide; bạn có thể làm cho rằng theo nghĩa đen là hàm tạo. Như vậy có lẽ sẽ yêu cầu một số shenanegans metaclass, một cái gì đó như thế này nên làm.

from functools import partial 

class InstanceConstructor(type): 
    def __get__(cls, instance, owner): 
     if instance is not None: 
      return partial(cls, instance) 
     return cls 

Và chỉ cần định nghĩa add_slide hành vi trong __init__ của lớp:

>>> class Presentation(object): 
...  def __init__(self): 
...   self.slides = [] 
... 
...  class add_slide(object): 
...   __metaclass__ = InstanceConstructor 
... 
...   def __init__(slide, presentation, color): 
...    slide.color = color 
...    presentation.slides.append(slide) 
... 
>>> p = Presentation() 
>>> p.add_slide('red') 
<instance_ctor.add_slide object at ...> 
>>> p.slides 
[<instance_ctor.add_slide object at ...>] 
>>> p.slides[0].color 
'red' 
+0

Tôi nghĩ đây là câu trả lời hay nhất. Câu hỏi duy nhất của tôi là tôi nghĩ sử dụng 'self' cho bản trình bày trong' Presentation.add_slide .__ init__' là khó hiểu. Tôi muốn đề nghị cho phép 'tự' là slide (như trong một lớp bình thường, không lồng nhau), hoặc bỏ qua quy ước' self' hoàn toàn và đi tới 'def __init __ (slide, presentation, color)'. – Blckknght

+1

Cảm ơn sự hỗ trợ của bạn, nhưng đề nghị thứ hai của tôi thực sự là một trò giải trí. Tôi sẽ không khuyên bạn nên nó. Hoặc là làm cho lớp công khai như trong câu trả lời được chấp nhận, hoặc sử dụng một lớp trừu tượng cho giao diện công cộng. – SingleNegationElimination

1

một lớp "nội bộ" là gì là một vấn đề của hội nghị, đó là lý do tại sao PEP không xác định thuật ngữ, nhưng nếu bạn đang trưng bày những điều này cho người dùng của mình (cung cấp các phương thức trả về lớp này, hoặc dựa vào việc lớp này được truyền xung quanh) thì nó không thực sự là "nội bộ".

Bạn có ba tùy chọn, thực sự. Đầu tiên có thể là tài liệu và làm rõ ý định của bạn về cách lớp này được xây dựng, nói cách khác cho phép người dùng của bạn xây dựng và sử dụng các phiên bản của lớp và ghi lại cách thực hiện điều này. Vì bạn muốn lớp cấp cao của bạn theo dõi các đối tượng Slide này, hãy làm cho giao diện người xây dựng bắt buộc và thiết lập để người dùng của mô-đun của bạn có thể tạo ra những thứ này giống như bạn.

Một cách khác là bọc các khía cạnh của lớp này mà bạn muốn trưng ra trong lớp tạo và quản lý các trường hợp này, nói cách khác làm cho lớp này trở thành lớp nội bộ thực sự. Nếu bạn chỉ cần phơi bày một phương pháp (ví dụ: add_shape()), điều này có thể rất hợp lý. Nếu bạn muốn một phần lớn giao diện cho lớp này được tiếp xúc, mặc dù, bắt đầu nhìn vụng về.

Cuối cùng, bạn chỉ có thể ghi rõ rằng người dùng không nên tự tạo các phiên bản của lớp này, nhưng có quyền truy cập vào các phần của giao diện. Điều này là khó hiểu nhưng có thể là một lựa chọn.

Tôi thích sự lựa chọn đầu tiên, đưa ra lớp cho người dùng mô-đun của bạn và sử dụng hàm tạo và thực thi hủy của nó để đảm bảo rằng nó luôn được kết nối đúng cách với các lớp khác cần biết về nó. Sau đó, người dùng có thể làm những việc như lớp con thứ này và các phương thức bọc mà họ muốn gọi, và bạn chắc chắn rằng các đối tượng của bạn vẫn được kết nối.

Các vấn đề liên quan