2013-06-30 35 views
5

Câu hỏi này dường như xuất hiện thường xuyên trên cả StackOverflow và các nơi khác, nhưng tôi không thể tìm thấy giải pháp hoàn toàn thỏa đáng ở bất cứ đâu.Python 3 - Phương pháp thừa kế docstring mà không phá vỡ trang trí hoặc vi phạm DRY

Dường như có hai loại giải pháp phổ biến. Người đầu tiên (từ ví dụ http://article.gmane.org/gmane.comp.python.general/630549) sử dụng một chức năng trang trí:

class SuperClass: 
    def my_method(self): 
     '''Has a docstring''' 
     pass 

class MyClass(SuperClass): 
    @copy_docstring_from(SuperClass) 
    def my_method(self): 
     pass 

assert SuperClass.my_method.__doc__ == MyClass.my_method._doc__ 

Đây có lẽ là phương pháp đơn giản nhất, nhưng nó đòi hỏi lặp đi lặp lại tên lớp cha mẹ ít nhất một lần, và cũng có thể trở thành rất nhiều phức tạp hơn nếu docstring không thể được tìm thấy trong tổ tiên trực tiếp.

Cách tiếp cận thứ hai sử dụng một metaclass hoặc lớp trang trí (x Inheriting methods' docstrings in Python, Inherit a parent class docstring as __doc__ attribute, http://mail.python.org/pipermail/python-list/2011-June/606043.html) và trông như thế này:

class MyClass1(SuperClass, metaclass=MagicHappeningHere): 
    def method(self): 
     pass 

# or 

@frobnicate_docstrings 
class MyClass2(SuperClass): 
    def method(self): 
     pass 

assert SuperClass.my_method.__doc__ == MyClass1.my_method._doc__ 
assert SuperClass.my_method.__doc__ == MyClass2.my_method._doc__ 

Tuy nhiên, với cách tiếp cận này docstring được chỉ đặt sau khi tạo lớp và do đó không có thể truy cập vào trang trí, vì vậy các công cụ sau sẽ không hoạt động:

def log_docstring(fn): 
    print('docstring for %s is %s' % (fn.__name__, fn.__doc__) 
    return fn 

class MyClass(SuperClass, metaclass=MagicHappeningHere): 
# or 
#@frobnicate_docstrings 
#class MyClass2(SuperClass): 
    @log_docstring 
    def method(self): 
     pass 

Ý tưởng thú vị thứ ba đã được thảo luận trong Inherit docstrings in Python class inheritance. Ở đây, trình trang trí hàm thực sự kết thúc tốt đẹp phương thức và biến nó thành một bộ mô tả phương thức thay vì chỉ cập nhật chuỗi tài liệu của nó. Tuy nhiên, điều này có vẻ như sử dụng sledgehammer để crack một nut bởi vì nó biến phương thức thành một bộ mô tả phương thức (có thể có các hàm ý hiệu năng, mặc dù tôi không kiểm tra), và cũng không làm cho docstring có sẵn cho bất kỳ trang trí nào khác (và trong ví dụ trên sẽ thực sự khiến chúng sụp đổ vì bộ mô tả phương thức không có thuộc tính __name__).

Có giải pháp nào tránh được tất cả các hạn chế ở trên, tức là không yêu cầu tôi lặp lại chính mình và gán ngay cho chuỗi tài liệu bằng trình trang trí không?

Tôi quan tâm đến một giải pháp cho Python 3.

Trả lời

1

tôi nghĩ rằng metaclass' __prepare__ phương pháp có thể được sử dụng cho điều này bằng cách tiêm một trang trí mà biết về hệ thống phân cấp lớp:

def log_docstring(fn): 
    print('docstring for %r is %r' % (fn, fn.__doc__)) 
    return fn 

class InheritableDocstrings(type): 
    def __prepare__(name, bases): 
     classdict = dict() 

     # Construct temporary dummy class to figure out MRO 
     mro = type('K', bases, {}).__mro__[1:] 
     assert mro[-1] == object 
     mro = mro[:-1] 

     def inherit_docstring(fn): 
      if fn.__doc__ is not None: 
       raise RuntimeError('Function already has docstring') 

      # Search for docstring in superclass 
      for cls in mro: 
       super_fn = getattr(cls, fn.__name__, None) 
       if super_fn is None: 
        continue 
       fn.__doc__ = super_fn.__doc__ 
       break 
      else: 
       raise RuntimeError("Can't inherit docstring for %s: method does not " 
            "exist in superclass" % fn.__name__) 

      return fn 

     classdict['inherit_docstring'] = inherit_docstring 
     return classdict 

class Animal(): 
    def move_to(self, dest): 
     '''Move to *dest*''' 
     pass 

class Bird(Animal, metaclass=InheritableDocstrings): 
    @log_docstring 
    @inherit_docstring 
    def move_to(self, dest): 
     self._fly_to(dest) 

assert Animal.move_to.__doc__ == Bird.move_to.__doc__ 

Prints:

docstring for <function Bird.move_to at 0x7f6286b9a200> is 'Move to *dest*' 

Dĩ nhiên, phương pháp này có một số vấn đề khác: - Một số công cụ phân tích (ví dụ pyflakes) sẽ khiếu nại về việc sử dụng (rõ ràng) không xác định inherit_docstring tên - Nó không hoạt động nếu lớp cha đã có một metaclass khác (ví dụ: ABCMeta).

+0

Tôi thực hiện một chút thông minh hơn về điều này và một số ví dụ trên http://code.activestate.com/recipes/578587-inherit-method-docstrings-without-breaking-decorat/ – Nikratio

2

Sử dụng trang trí lớp học thay vì:

@inherit_docstrings 
class MyClass(SuperClass): 
    def method(self): 
     pass 

nơi inherit_docstrings() được định nghĩa là:

from inspect import getmembers, isfunction 

def inherit_docstrings(cls): 
    for name, func in getmembers(cls, isfunction): 
     if func.__doc__: continue 
     for parent in cls.__mro__[1:]: 
      if hasattr(parent, name): 
       func.__doc__ = getattr(parent, name).__doc__ 
    return cls 

Demo:

>>> class SuperClass: 
...  def method(self): 
...   '''Has a docstring''' 
...   pass 
... 
>>> @inherit_docstrings 
... class MyClass(SuperClass): 
...  def method(self): 
...   pass 
... 
>>> MyClass.method.__doc__ 
'Has a docstring' 

Điều này đặt docstring sau khi xác định toàn bộ lớp mà không phải tạo một cá thể trước.

Nếu bạn cần dây rút có sẵn cho trình trang trí phương pháp, bạn không may, hoàn toàn bị mắc kẹt với trang trí của bạn sao chép lớp cha mẹ.

Lý do cho điều này là bạn không thể quan sát những gì mà lớp cha sẽ có khi xác định nội dung lớp. Không gian tên cục bộ trong khi định nghĩa lớp không có quyền truy cập vào các đối số được truyền cho nhà máy lớp.

Bạn thể sử dụng một metaclass để thêm các lớp cơ sở để không gian tên địa phương, sau đó sử dụng một trang trí để kéo những người ra một lần nữa, nhưng theo ý kiến ​​của tôi mà được xấu xí, nhanh chóng: sử dụng

import sys 

class InheritDocstringMeta(type): 
    _key = '__InheritDocstringMeta_bases' 

    def __prepare__(name, bases, **kw): 
     return {InheritDocstringMeta._key: bases} 

    def __call__(self, name, bases, namespace, **kw): 
     namespace.pop(self._key, None) 

def inherit_docstring(func): 
    bases = sys._getframe(1).f_locals.get(InheritDocstringMeta._key,()) 
    for base in bases: 
     for parent in base.mro(): 
      if hasattr(parent, func.__name__): 
       func.__doc__ = getattr(parent, func.__name__).__doc__ 
    return func 

Demo :

>>> class MyClass(SuperClass, metaclass=InheritDocstringMeta): 
...  @inherit_docstring 
...  def method(self): 
...   pass 
... 
>>> MyClass.method.__doc__ 
'Has a docstring' 
+0

Không gian tên cục bộ không có quyền truy cập vào đối số của nhà máy cấp theo mặc định, đúng. Nhưng không thể một metaclass được sử dụng để thay đổi điều đó? – Nikratio

+0

Đối với Python 2, 'chức năng' cần phải được 'ismethod' có vẻ như. – spookylukey

+0

@spookylukey: có, trong Python 2, các thành viên của một lớp là các phương thức không liên kết. –

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