การเพิ่ม docstrings ใน namedtuples?


87

เป็นไปได้ไหมที่จะเพิ่มสตริงเอกสารเข้ากับ namestuple อย่างง่ายดาย?

ฉันเหนื่อย

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
"""
A point in 2D space
"""

# Yet another test

"""
A(nother) point in 2D space
"""
Point2 = namedtuple("Point2", ["x", "y"])

print Point.__doc__ # -> "Point(x, y)"
print Point2.__doc__ # -> "Point2(x, y)"

แต่นั่นไม่ได้ตัดมัน เป็นไปได้ไหมที่จะทำด้วยวิธีอื่น?

คำตอบ:


53

namedtupleคุณสามารถบรรลุนี้โดยการสร้างที่เรียบง่ายคลาสเสื้อคลุมที่ว่างเปล่ารอบค่ากลับมาจาก เนื้อหาของไฟล์ที่ฉันสร้าง ( nt.py):

from collections import namedtuple

Point_ = namedtuple("Point", ["x", "y"])

class Point(Point_):
    """ A point in 2d space """
    pass

จากนั้นใน Python REPL:

>>> print nt.Point.__doc__
 A point in 2d space 

หรือคุณสามารถทำได้:

>>> help(nt.Point)  # which outputs...
ความช่วยเหลือเกี่ยวกับคลาส Point ในโมดูล nt:

คลาสพอยต์ (Point)
 | จุดในพื้นที่ 2d
 |  
 | ลำดับวิธีแก้ปัญหา:
 | จุด
 | จุด
 | __builtin __. tuple
 | __builtin __ วัตถุ
 ...

หากคุณไม่ชอบทำด้วยมือทุกครั้งการเขียนฟังก์ชันโรงงานเพื่อทำสิ่งนี้เป็นเรื่องเล็กน้อย:

def NamedTupleWithDocstring(docstring, *ntargs):
    nt = namedtuple(*ntargs)
    class NT(nt):
        __doc__ = docstring
    return NT

Point3D = NamedTupleWithDocstring("A point in 3d space", "Point3d", ["x", "y", "z"])

p3 = Point3D(1,2,3)

print p3.__doc__

ซึ่งผลลัพธ์:

A point in 3d space

2
จะไม่แปลงคลาสย่อยnamedtupleเป็น "วัตถุ" ที่สมบูรณ์หรือไม่? ดังนั้นจึงสูญเสียประสิทธิภาพบางส่วนที่ได้รับจากสิ่งที่มีชื่อ?
exhuma

5
หากคุณเพิ่มลง__slots__ = ()ในคลาสย่อยที่ได้รับคุณจะสามารถรักษาความจำและประสิทธิภาพของการใช้งานได้namedtuple
ali_m

มันยังคงเพิ่มอีกระดับให้กับ MRO ซึ่งไม่เป็นธรรมสำหรับ docstring อย่างไรก็ตามเราสามารถกำหนด__doc__และมี docstring แบบกำหนดเองที่บันทึกไว้ในวัตถุต้นฉบับได้
Bachsau

71

ใน Python 3 ไม่จำเป็นต้องใช้ wrapper เนื่องจาก__doc__แอตทริบิวต์ของประเภทสามารถเขียนได้

from collections import namedtuple

Point = namedtuple('Point', 'x y')
Point.__doc__ = '''\
A 2-dimensional coordinate

x - the abscissa
y - the ordinate'''

สิ่งนี้สอดคล้องอย่างใกล้ชิดกับนิยามคลาสมาตรฐานโดยที่ docstring ตามหลังส่วนหัว

class Point():
    '''A 2-dimensional coordinate

    x - the abscissa
    y - the ordinate'''
    <class code>

สิ่งนี้ใช้ไม่ได้ใน Python 2

AttributeError: attribute '__doc__' of 'type' objects is not writable.


64

เจอคำถามเก่า ๆ นี้ผ่าน Google ในขณะที่สงสัยในสิ่งเดียวกัน

แค่อยากจะชี้ให้เห็นว่าคุณสามารถทำให้มันเป็นระเบียบมากขึ้นโดยการเรียก namedtuple () จากการประกาศคลาส:

from collections import namedtuple

class Point(namedtuple('Point', 'x y')):
    """Here is the docstring."""

8
สิ่งสำคัญที่คุณต้องรวม__slots__ = ()ไว้ในชั้นเรียน มิฉะนั้นคุณจะสร้างแอทเทอร์__dict__ของคุณซึ่งจะสูญเสียลักษณะน้ำหนักเบาของ namedtuple
BoltzmannBrain

34

เป็นไปได้ไหมที่จะเพิ่มสตริงเอกสารเข้ากับ namestuple อย่างง่ายดาย?

ได้หลายวิธี

การพิมพ์คลาสย่อย NamedTuple - Python 3.6+

ใน Python 3.6 เราสามารถใช้classคำจำกัดความtyping.NamedTupleได้โดยตรงด้วย docstring (และคำอธิบายประกอบ!):

from typing import NamedTuple

class Card(NamedTuple):
    """This is a card type."""
    suit: str
    rank: str

เมื่อเทียบกับ Python 2 การประกาศว่าง__slots__ไม่จำเป็น ใน Python 3.8 ไม่จำเป็นสำหรับคลาสย่อย

โปรดทราบว่าการประกาศ__slots__ต้องไม่ว่างเปล่า!

ใน Python 3 คุณยังสามารถแก้ไขเอกสารบน namestuple ได้อย่างง่ายดาย:

NT = collections.namedtuple('NT', 'foo bar')

NT.__doc__ = """:param str foo: foo name
:param list bar: List of bars to bar"""

ซึ่งช่วยให้เราสามารถดูเจตนาของพวกเขาเมื่อเราขอความช่วยเหลือจากพวกเขา:

Help on class NT in module __main__:

class NT(builtins.tuple)
 |  :param str foo: foo name
 |  :param list bar: List of bars to bar
...

สิ่งนี้ตรงไปตรงมามากเมื่อเทียบกับความยากลำบากที่เราได้ทำสิ่งเดียวกันใน Python 2

Python 2

ใน Python 2 คุณจะต้อง

  • คลาสย่อยที่มีชื่อทูเพิลและ
  • ประกาศ __slots__ == ()

ประกาศ__slots__จะเป็นส่วนสำคัญที่คำตอบอื่น ๆ ที่นี่พลาด

หากคุณไม่ประกาศ__slots__- คุณสามารถเพิ่มแอตทริบิวต์ ad-hoc ที่เปลี่ยนแปลงได้ให้กับอินสแตนซ์โดยแนะนำจุดบกพร่อง

class Foo(namedtuple('Foo', 'bar')):
    """no __slots__ = ()!!!"""

และตอนนี้:

>>> f = Foo('bar')
>>> f.bar
'bar'
>>> f.baz = 'what?'
>>> f.__dict__
{'baz': 'what?'}

แต่ละอินสแตนซ์จะสร้างแยกกัน__dict__เมื่อ__dict__มีการเข้าถึง (การขาด__slots__จะไม่ขัดขวางฟังก์ชันการทำงาน แต่ความเบาของคุณลักษณะทูเพิลไม่เปลี่ยนรูปและประกาศเป็นคุณสมบัติที่สำคัญทั้งหมดของเนมทูเกิล)

คุณจะต้องมี__repr__ถ้าคุณต้องการให้สิ่งที่สะท้อนในบรรทัดคำสั่งให้วัตถุที่เทียบเท่ากับคุณ:

NTBase = collections.namedtuple('NTBase', 'foo bar')

class NT(NTBase):
    """
    Individual foo bar, a namedtuple

    :param str foo: foo name
    :param list bar: List of bars to bar
    """
    __slots__ = ()

สิ่ง__repr__นี้เป็นสิ่งจำเป็นหากคุณสร้างฐานชื่อทูเปิลด้วยชื่ออื่น (เหมือนที่เราทำข้างต้นด้วยอาร์กิวเมนต์สตริงชื่อ'NTBase'):

    def __repr__(self):
        return 'NT(foo={0}, bar={1})'.format(
                repr(self.foo), repr(self.bar))

ในการทดสอบการตอบกลับให้สร้างอินสแตนซ์จากนั้นทดสอบความเท่าเทียมกันของการส่งผ่านไปยัง eval(repr(instance))

nt = NT('foo', 'bar')
assert eval(repr(nt)) == nt

ตัวอย่างจากเอกสาร

เอกสารยังยกตัวอย่างเช่นเรื่อง__slots__- ฉันเพิ่ม docstring ของตัวเองไป:

class Point(namedtuple('Point', 'x y')):
    """Docstring added here, not in original"""
    __slots__ = ()
    @property
    def hypot(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
        return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

...

คลาสย่อยที่แสดงด้านบนตั้งค่า__slots__เป็นทูเพิลว่าง สิ่งนี้ช่วยให้ความต้องการหน่วยความจำต่ำโดยการป้องกันการสร้างพจนานุกรมอินสแตนซ์

สิ่งนี้แสดงให้เห็นถึงการใช้งานในสถานที่ (เช่นคำตอบอื่นที่แนะนำ) แต่โปรดทราบว่าการใช้งานในสถานที่อาจทำให้เกิดความสับสนเมื่อคุณดูลำดับการแก้ปัญหาวิธีการหากคุณกำลังแก้ไขข้อบกพร่องซึ่งเป็นเหตุผลที่ฉันแนะนำให้ใช้Baseเป็นส่วนต่อท้าย สำหรับฐานที่มีชื่อว่าสองตัวอย่าง:

>>> Point.mro()
[<class '__main__.Point'>, <class '__main__.Point'>, <type 'tuple'>, <type 'object'>]
                # ^^^^^---------------------^^^^^-- same names!        

ในการป้องกันการสร้าง__dict__คลาสย่อยเมื่อใช้คลาสคุณต้องประกาศในคลาสย่อยด้วย ดูเพิ่มเติมคำตอบนี้สำหรับคำเตือนเพิ่มเติมในการใช้__slots__


3
แม้ว่าจะไม่ได้เป็นที่กระชับและชัดเจนเป็นคำตอบอื่น ๆ __slots__นี้ควรจะเป็นคำตอบที่ได้รับการยอมรับเพราะมันไฮไลท์สำคัญของ หากไม่มีสิ่งนี้คุณจะสูญเสียค่าน้ำหนักเบาของ namestuple
BoltzmannBrain

7

ตั้งแต่ Python 3.5 namedtupleสามารถอัปเดตdocstrings สำหรับวัตถุได้

จากwhatsnew :

Point = namedtuple('Point', ['x', 'y'])
Point.__doc__ += ': Cartesian coodinate'
Point.x.__doc__ = 'abscissa'
Point.y.__doc__ = 'ordinate'


3

ไม่จำเป็นต้องใช้คลาส Wrapper ตามที่คำตอบที่ยอมรับแนะนำ เพียงแค่เพิ่ม docstring:

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
Point.__doc__="A point in 2D space"

ผลลัพธ์ใน: (ตัวอย่างการใช้ipython3):

In [1]: Point?
Type:       type
String Form:<class '__main__.Point'>
Docstring:  A point in 2D space

In [2]: 

โวล่า!


1
หมายเหตุ: นี่จะใช้ได้เฉพาะสำหรับหลาม 3. ในหลาม AttributeError: attribute '__doc__' of 'type' objects is not writable2:
Taylor Edmiston

1

คุณสามารถสร้างฟังก์ชัน namedtuple factoryเวอร์ชันของคุณเองโดย Raymond Hettinger และเพิ่มdocstringอาร์กิวเมนต์ที่เป็นทางเลือก อย่างไรก็ตามมันจะง่ายกว่าและดีกว่า - เพียงแค่กำหนดฟังก์ชันโรงงานของคุณเองโดยใช้เทคนิคพื้นฐานเดียวกับในสูตรอาหาร ไม่ว่าจะด้วยวิธีใดคุณจะได้สิ่งที่นำกลับมาใช้ใหม่ได้

from collections import namedtuple

def my_namedtuple(typename, field_names, verbose=False,
                 rename=False, docstring=''):
    '''Returns a new subclass of namedtuple with the supplied
       docstring appended to the default one.

    >>> Point = my_namedtuple('Point', 'x, y', docstring='A point in 2D space')
    >>> print Point.__doc__
    Point(x, y):  A point in 2D space
    '''
    # create a base class and concatenate its docstring and the one passed
    _base = namedtuple(typename, field_names, verbose, rename)
    _docstring = ''.join([_base.__doc__, ':  ', docstring])

    # fill in template to create a no-op subclass with the combined docstring
    template = '''class subclass(_base):
        %(_docstring)r
        pass\n''' % locals()

    # execute code string in a temporary namespace
    namespace = dict(_base=_base, _docstring=_docstring)
    try:
        exec template in namespace
    except SyntaxError, e:
        raise SyntaxError(e.message + ':\n' + template)

    return namespace['subclass']  # subclass object created

0

ฉันสร้างฟังก์ชันนี้เพื่อสร้างทูเพิลที่มีชื่ออย่างรวดเร็วและจัดทำเอกสารทูเพิลพร้อมกับพารามิเตอร์แต่ละตัว:

from collections import namedtuple


def named_tuple(name, description='', **kwargs):
    """
    A named tuple with docstring documentation of each of its parameters
    :param str name: The named tuple's name
    :param str description: The named tuple's description
    :param kwargs: This named tuple's parameters' data with two different ways to describe said parameters. Format:
        <pre>{
            str: ( # The parameter's name
                str, # The parameter's type
                str # The parameter's description
            ),
            str: str, # The parameter's name: the parameter's description
            ... # Any other parameters
        }</pre>
    :return: collections.namedtuple
    """
    parameter_names = list(kwargs.keys())

    result = namedtuple(name, ' '.join(parameter_names))

    # If there are any parameters provided (such that this is not an empty named tuple)
    if len(parameter_names):
        # Add line spacing before describing this named tuple's parameters
        if description is not '':
            description += "\n"

        # Go through each parameter provided and add it to the named tuple's docstring description
        for parameter_name in parameter_names:
            parameter_data = kwargs[parameter_name]

            # Determine whether parameter type is included along with the description or
            # if only a description was provided
            parameter_type = ''
            if isinstance(parameter_data, str):
                parameter_description = parameter_data
            else:
                parameter_type, parameter_description = parameter_data

            description += "\n:param {type}{name}: {description}".format(
                type=parameter_type + ' ' if parameter_type else '',
                name=parameter_name,
                description=parameter_description
            )

            # Change the docstring specific to this parameter
            getattr(result, parameter_name).__doc__ = parameter_description

    # Set the docstring description for the resulting named tuple
    result.__doc__ = description

    return result

จากนั้นคุณสามารถสร้างทูเพิลชื่อใหม่:

MyTuple = named_tuple(
    "MyTuple",
    "My named tuple for x,y coordinates",
    x="The x value",
    y="The y value"
)

จากนั้นสร้างอินสแตนซ์ tuple ที่มีชื่อที่อธิบายด้วยข้อมูลของคุณเองเช่น

t = MyTuple(4, 8)
print(t) # prints: MyTuple(x=4, y=8)

เมื่อดำเนินการhelp(MyTuple)ผ่านบรรทัดคำสั่ง python3 จะแสดงสิ่งต่อไปนี้:

Help on class MyTuple:

class MyTuple(builtins.tuple)
 |  MyTuple(x, y)
 |
 |  My named tuple for x,y coordinates
 |
 |  :param x: The x value
 |  :param y: The y value
 |
 |  Method resolution order:
 |      MyTuple
 |      builtins.tuple
 |      builtins.object
 |
 |  Methods defined here:
 |
 |  __getnewargs__(self)
 |      Return self as a plain tuple.  Used by copy and pickle.
 |
 |  __repr__(self)
 |      Return a nicely formatted representation string
 |
 |  _asdict(self)
 |      Return a new OrderedDict which maps field names to their values.
 |
 |  _replace(_self, **kwds)
 |      Return a new MyTuple object replacing specified fields with new values
 |
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |
 |  _make(iterable) from builtins.type
 |      Make a new MyTuple object from a sequence or iterable
 |
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |
 |  __new__(_cls, x, y)
 |      Create new instance of MyTuple(x, y)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  x
 |      The x value
 |
 |  y
 |      The y value
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  _fields = ('x', 'y')
 |  
 |  _fields_defaults = {}
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from builtins.tuple:
 |  
 |  __add__(self, value, /)
 |      Return self+value.
 |  
 |  __contains__(self, key, /)
 |      Return key in self.
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getitem__(self, key, /)
 |      Return self[key].
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __hash__(self, /)
 |      Return hash(self).
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __len__(self, /)
 |      Return len(self).
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __mul__(self, value, /)
 |      Return self*value.
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __rmul__(self, value, /)
 |      Return value*self.
 |  
 |  count(self, value, /)
 |      Return number of occurrences of value.
 |  
 |  index(self, value, start=0, stop=9223372036854775807, /)
 |      Return first index of value.
 |      
 |      Raises ValueError if the value is not present.

หรือคุณสามารถระบุประเภทของพารามิเตอร์ผ่านทาง:

MyTuple = named_tuple(
    "MyTuple",
    "My named tuple for x,y coordinates",
    x=("int", "The x value"),
    y=("int", "The y value")
)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.