关于元编程:在Python描述符中创建动态文档字符串

Creating dynamic docstrings in Python descriptor

我试图动态生成一些类定义(用于包装C++扩展)。以下描述符可以正常工作,除非我尝试使用help()访问字段的docstring,否则它会为描述符提供默认文档,而不是它自己的字段。但是,当我执行帮助(classname)时,它检索传递给描述符的docstring:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class FieldDescriptor(object):
    def __init__(self, name, doc='No documentation available.'):
        self.name = name
        self.__doc__ = doc

    def __get__(self, obj, dtype=None):
        if obj is None and dtype is not None:
            print 'Doc is:', self.__doc__
            return self
        return obj.get_field(self.name)

    def __set__(self, obj, value):
        obj.set_field(self.name, value)

class TestClass(object):
    def __init__(self):
        self.fdict = {'a': None, 'b': None}

    def get_field(self, name):
        return self.fdict[name]

    def set_field(self, name, value):
        self.fdict[name] = value

fields = ['a', 'b']
def define_class(class_name, baseclass):
    class_obj = type(class_name, (baseclass,), {})
    for field in fields:
        setattr(class_obj, field, FieldDescriptor(field, doc='field %s in class %s' % (field, class_name)))
    globals()[class_name] = class_obj


if __name__ == '__main__':
    define_class('DerivedClass', TestClass)
    help(DerivedClass.a)
    help(DerivedClass)
    v = DerivedClass()
    help(v.a)

"python test.py"打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Doc is: field a in class DerivedClass
Help on FieldDescriptor in module __main__ object:

class FieldDescriptor(__builtin__.object)
 |  Methods defined here:
 |  
 |  __get__(self, obj, dtype=None)
 |  
 |  __init__(self, name, doc='No documentation available.')
 |  
 |  __set__(self, obj, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Doc is: field a in class DerivedClass
Doc is: field b in class DerivedClass
Help on class DerivedClass in module __main__:

class DerivedClass(TestClass)
 |  Method resolution order:
 |      DerivedClass
 |      TestClass
 |      __builtin__.object
 |  
 |  Data descriptors defined here:
 |  
 |  a
 |      field a in class DerivedClass
 |  
 |  b
 |      field b in class DerivedClass
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from TestClass:
 |  
 |  __init__(self)
 |  
 |  get_field(self, name)
 |  
 |  set_field(self, name, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from TestClass:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Help on NoneType object:

class NoneType(object)
 |  Methods defined here:
 |  
 |  __hash__(...)
 |      x.__hash__()  hash(x)
 |  
 |  __repr__(...)
 |      x.__repr__()  repr(x)

你知道怎样才能得到help(class.field)descriptor.__doc__吗?是否有一种方法可以绕过这个问题,并为Doc提供类似getter函数的功能,而不必将Doc字符串存储在描述符中?

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class FieldDescriptor(object):
    def __init__(self, name, doc='No documentation available.'):
        self.name = name
        self.__doc__ = doc

    def __get__(self, obj, dtype=None):
        if obj is None and dtype is not None:
            print 'Doc is:', self.__doc__
            return self
        return obj.get_field(self.name)

    def __set__(self, obj, value):
        obj.set_field(self.name, value)

    # This is what I'd like to have
    def __doc__(self, obj, dtype):
       return dtype.generate_docstring(self.name)

更新:实际上,我从__get__的定义开始:

1
2
def __get__(self, obj, dtype=None):
    return obj.get_field(self.name)

问题是当我说:

1
help(DerivedClass.a)

python抛出了一个异常,表明我正试图调用None.get_field。因此,help()使用obj=Nonedtype=DerivedClass调用__get__方法。这就是我决定在obj=none和dtype时返回fielddescriptor实例的原因!=无。我的印象是,help(xyz)试图展示xyz.__doc__。根据这个逻辑,如果__get__返回descriptor_instance,那么descriptor_instance.__doc__应该由help()打印,这是整个类〔help(DerivedClass)的情况,而不是单个字段〔help(DerivedClass.a)的情况。


接下来,当您请求help(DerivedClass.a)时,python计算圆括号内的表达式,即描述符的__get__方法返回的对象,并搜索该对象的帮助(包括docstring)。

实现这种工作(包括动态doc string生成)的一种方法是让您的__get__方法重新定义一个动态生成的对象,该对象具有所需的docstring。但是这个对象本身需要是一个与原始对象相匹配的合适的代理对象,并且会在代码上产生一些开销——以及许多特殊情况。

无论如何,让它像您希望的那样工作的唯一方法是修改__get__本身返回的对象,使它们的行为像您希望的那样。

我建议,如果您希望帮助中的所有内容都像您所做的那样是一些信息,那么您可能希望从__get__返回的对象属于定义__repr__方法的类(而不仅仅是__doc__字符串)。