Làm thế nào để ghi lại các thuộc tính của lớp trong Python? [đóng cửa]


115

Tôi đang viết một lớp nhẹ có các thuộc tính nhằm mục đích có thể truy cập công khai và đôi khi chỉ bị ghi đè trong các phần trình bày cụ thể. Không có quy định nào trong ngôn ngữ Python để tạo docstrings cho các thuộc tính lớp hoặc bất kỳ loại thuộc tính nào cho vấn đề đó. Cách dự kiến ​​và được hỗ trợ, nên có, để ghi lại các thuộc tính này là gì? Hiện tại tôi đang làm những việc như sau:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
    """

    flight_speed = 691
    __doc__ += """
        flight_speed (691)
          The maximum speed that such a bird can attain.
    """

    nesting_grounds = "Raymond Luxury-Yacht"
    __doc__ += """
        nesting_grounds ("Raymond Luxury-Yacht")
          The locale where these birds congregate to reproduce.
    """

    def __init__(self, **keyargs):
        """Initialize the Albatross from the keyword arguments."""
        self.__dict__.update(keyargs)

Điều này sẽ dẫn đến việc docstring của lớp chứa phần docstring chuẩn ban đầu, cũng như các dòng được thêm vào cho mỗi thuộc tính thông qua phép gán tăng cường cho __doc__.

Mặc dù phong cách này dường như không bị cấm rõ ràng trong nguyên tắc về kiểu docstring , nhưng nó cũng không được đề cập như một tùy chọn. Ưu điểm ở đây là nó cung cấp một cách để ghi lại các thuộc tính cùng với các định nghĩa của chúng, trong khi vẫn tạo ra một chuỗi docstring của lớp có thể trình bày được và tránh phải viết các chú thích nhắc lại thông tin từ chuỗi docstring. Tôi vẫn hơi khó chịu vì tôi thực sự phải viết các thuộc tính hai lần; Tôi đang xem xét sử dụng các biểu diễn chuỗi của các giá trị trong docstring để ít nhất là tránh trùng lặp các giá trị mặc định.

Đây có phải là sự vi phạm nghiêm trọng các quy ước cộng đồng đặc biệt không? Như thế có ổn không? Có cách nào tốt hơn? Ví dụ, có thể tạo một từ điển chứa các giá trị và docstrings cho các thuộc tính, sau đó thêm nội dung vào lớp __dict__và docstring vào cuối khai báo lớp; điều này sẽ giảm bớt nhu cầu nhập tên thuộc tính và giá trị hai lần. chỉnh sửa : ý tưởng cuối cùng này, tôi nghĩ là không thực sự khả thi, ít nhất không phải là không xây dựng động toàn bộ lớp từ dữ liệu, có vẻ như là một ý tưởng thực sự tồi trừ khi có một số lý do khác để làm điều đó.

Tôi còn khá mới với python và vẫn đang tìm hiểu chi tiết về phong cách mã hóa, vì vậy những lời phê bình không liên quan cũng được hoan nghênh.


Nếu bạn đang tìm cách ghi lại các thuộc tính của mô hình Django, điều này có thể hữu ích: djangosnippets.org/snippets/2533
Michael Scheper

3
Bản sao của Cách lập tài liệu các trường và thuộc tính trong Python? mà có một giải pháp khác nhau.
bufh

1
Tôi không hiểu tại sao điều này lại dựa trên ý kiến. Python trình bày cụ thể các quy ước được chấp nhận trong PEP. Có các công cụ nguồn Python khác nhau giúp trích xuất tài liệu được định dạng đúng. Trên thực tế, Python thực sự có một attribute doc stringđề cập trong PEP 257 không được nhiều người biết đến và có vẻ khó tìm có thể trả lời câu hỏi OP và được hỗ trợ bởi một số công cụ nguồn. Đây không phải là ý kiến. Đó là sự thật, là một phần của ngôn ngữ, và khá chính xác những gì OP muốn.
NeilG

Câu trả lời:


83

Để tránh nhầm lẫn: thuật ngữ thuộc tính có một ý nghĩa cụ thể trong python. Những gì bạn đang nói đến là những gì chúng tôi gọi là thuộc tính lớp . Vì chúng luôn được thực hiện thông qua lớp của chúng, tôi thấy rằng việc ghi lại chúng trong chuỗi doc của lớp là rất hợp lý. Một cái gì đó như thế này:

class Albatross(object):
    """A bird with a flight speed exceeding that of an unladen swallow.

    Attributes:
        flight_speed     The maximum speed that such a bird can attain.
        nesting_grounds  The locale where these birds congregate to reproduce.
    """
    flight_speed = 691
    nesting_grounds = "Throatwarbler Man Grove"

Tôi nghĩ điều đó dễ dàng hơn rất nhiều so với cách tiếp cận trong ví dụ của bạn. Nếu tôi thực sự muốn một bản sao của các giá trị thuộc tính xuất hiện trong chuỗi doc, tôi sẽ đặt chúng bên cạnh hoặc bên dưới mô tả của từng thuộc tính.

Hãy nhớ rằng trong Python, chuỗi doc là thành viên thực tế của các đối tượng mà chúng ghi lại, không chỉ đơn thuần là chú thích mã nguồn. Vì các biến thuộc tính lớp không phải là bản thân các đối tượng mà là các tham chiếu đến các đối tượng, chúng không có cách nào giữ các chuỗi doc của riêng chúng. Tôi đoán bạn có thể đặt trường hợp cho chuỗi doc trên các tham chiếu, có lẽ để mô tả "những gì nên đến đây" thay vì "những gì thực sự ở đây", nhưng tôi thấy nó đủ dễ dàng để làm điều đó trong chuỗi doc của lớp chứa.


Tôi đoán trong hầu hết các trường hợp, điều này là tốt, vì các thuộc tính —cảm ơn sự sửa chữa thuật ngữ — được khai báo đủ ngắn gọn để chúng có thể được nhóm lại ở đầu khai báo lớp mà không làm cho việc lật qua lại để đọc {đọc cả hai tài liệu và giá trị mặc định} hoặc {cập nhật cả hai phiên bản của tài liệu và / hoặc giá trị mặc định}.
trực giác

1
Cũng lưu ý rằng ví dụ của tôi sẽ khiến tài liệu cho các thuộc tính xuất hiện trong docstring của lớp. Tôi thực sự muốn đặt tài liệu trong docstrings của chính các thuộc tính, nhưng điều này không hoạt động đối với hầu hết các loại nội trang.
trực giác

Có, ý tưởng ban đầu của tôi là chỉ khai báo vd flight_speed = 691; flight_speed.__doc__ = "blah blah". Tôi nghĩ đây là những gì bạn đang đề cập trong bản chỉnh sửa của mình . Thật không may, điều này không hoạt động đối với các mô tả của (hầu hết?) Các loại nội trang (như inttrong ví dụ đó). Nó hoạt động cho các phiên bản của các kiểu do người dùng xác định. =========== Thực sự có một PEP (xin lỗi, quên số) đề xuất thêm docstrings cho các thuộc tính lớp / mô-đun, nhưng nó đã bị từ chối vì họ không thể tìm ra cách làm rõ ràng liệu docstrings dành cho các thuộc tính trước hay sau.
trực giác

2
vậy nếu chúng là thuộc tính cá thể thì sao? vẫn còn tài liệu trong lớp docstring hay sao?
n611x007

1
@intuited Có phải đây là PEP? inherit.python.org/dev/peps/pep-0224
taz

30

Bạn trích dẫn PEP257: Quy ước về chuỗi tài liệu, trong phần Chuỗi tài liệu là gì, nó được nêu:

Các ký tự chuỗi xảy ra ở nơi khác trong mã Python cũng có thể hoạt động như tài liệu. Chúng không được trình biên dịch bytecode Python nhận dạng và không thể truy cập được dưới dạng thuộc tính đối tượng thời gian chạy (tức là không được gán cho __doc__), nhưng hai loại docstrings bổ sung có thể được trích xuất bằng các công cụ phần mềm:

Các ký tự chuỗi xảy ra ngay sau một phép gán đơn giản ở cấp cao nhất của một mô-đun, lớp hoặc phương thức __init__ được gọi là "thuộc tính docstrings".

Và điều này được giải thích chi tiết hơn trong PEP 258: Docstrings thuộc tính. Như đã giải thích ở trên ʇsәɹoɈ. thuộc tính không phải là đối tượng có thể sở hữu __doc__ để chúng sẽ không xuất hiện trong help()hoặc pydoc. Các chuỗi doc này chỉ có thể được sử dụng cho tài liệu đã tạo.

Chúng được sử dụng trong Sphinx với thuộc tính tự động chỉ thị

Sphinx có thể sử dụng các nhận xét trên một dòng trước một bài tập hoặc một nhận xét đặc biệt sau một bài tập hoặc một chuỗi tài liệu sau định nghĩa sẽ được tự động ghi lại.


1
plugin jedi-vim cũng nhận dạng docstrings thuộc tính.
Long Vu,

1
Tôi không biết điều này được giới thiệu khi nào, nhưng Sphinx 1.2.2 dường như bao gồm các docstrings thuộc tính trong tài liệu được tạo.
jochen

1
Cảm ơn bạn @jochen, tôi cập nhật câu trả lời của tôi.
marcz

3
Xin lưu ý rằng PEP 258 bị từ chối. Thông báo từ chối nêu rõ: "Mặc dù đây có thể là một tài liệu thiết kế thú vị cho các hình vẽ docutils hiện độc lập, nhưng nó không còn được dự kiến ​​đưa vào thư viện tiêu chuẩn."
Michał Łazowik

13

Bạn có thể lạm dụng thuộc tính để có hiệu lực này. Thuộc tính chứa getter, setter, deleter và docstring . Ngây thơ, điều này sẽ rất dài dòng:

class C:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """Docstring goes here."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Sau đó, bạn sẽ có một chuỗi doc thuộc Cx:

In [24]: print(C.x.__doc__)
Docstring goes here.

Để thực hiện việc này đối với nhiều thuộc tính là rất phức tạp, nhưng bạn có thể hình dung hàm trợ giúp myprop:

def myprop(x, doc):
    def getx(self):
        return getattr(self, '_' + x)

    def setx(self, val):
        setattr(self, '_' + x, val)

    def delx(self):
        delattr(self, '_' + x)

    return property(getx, setx, delx, doc)

class C:
    a = myprop("a", "Hi, I'm A!")
    b = myprop("b", "Hi, I'm B!")

In [44]: c = C()

In [46]: c.b = 42

In [47]: c.b
Out[47]: 42

In [49]: print(C.b.__doc__)
Hi, I'm B!

Sau đó, gọi tương tác Pythons helpsẽ cho:

Help on class C in module __main__:

class C
 |  Data descriptors defined here:
 |  
 |  a
 |      Hi, I'm A!
 |  
 |  b
 |      Hi, I'm B!

mà tôi nghĩ nó phải là những gì bạn đang theo đuổi.

Chỉnh sửa : Bây giờ tôi nhận ra rằng có lẽ chúng ta có thể tránh không cần phải chuyển đối số đầu tiên cho myproptất cả, bởi vì tên nội bộ không quan trọng. Nếu các cuộc gọi tiếp theo của mypropbằng cách nào đó có thể giao tiếp với nhau, nó có thể tự động quyết định một tên thuộc tính nội bộ dài và không chắc. Tôi chắc rằng có nhiều cách để thực hiện điều này, nhưng tôi không chắc liệu chúng có đáng không.

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.