I have a function and class decorator that changes them to singletons but the docstrings are kind of dropped. Transforming function returns a class object that has its own docstring which should be overwritten. How can I work around this?
def singleton(cls):
return cls()
def singletonfunction(func):
return func()
@singletonfunction
def Bit():
"""A 1-bit BitField; must be enclosed in a BitStruct"""
return BitsInteger(1)
Inspecting Bit
yields the BitInteger
docstring, not the function's one.
Lets assume the original version is something like this:
class BitsInteger:
"""BitsInteger docstring"""
def __init__(self, num):
pass
def singleton(cls):
return cls()
def singletonfunction(func):
return func()
@singletonfunction
def Bit():
"""A 1-bit BitField; must be enclosed in a BitStruct"""
return BitsInteger(1)
b = Bit
print("b.__doc__ :", b.__doc__)
Running this with Python3.5 gives the output:
b.__doc_ : BitsInteger docstring
This might not be what you expect. When we run with python -i original.py
we can have a look around at what is really going on here:
>>> vars()
{'__name__': '__main__', '__builtins__': <module 'builtins' (built-in)>, 'b': <__main__.BitsInteger object at 0x7ff05d2ae3c8>, '__spec__': None, 'singletonfunction': <function singletonfunction at 0x7ff05d2b40d0>, 'singleton': <function singleton at 0x7ff05d30cd08>, '__cached__': None, 'BitsInteger': <class '__main__.BitsInteger'>, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x7ff05d2eb4a8>, '__package__': None, 'Bit': <__main__.BitsInteger object at 0x7ff05d2ae3c8>, '__doc__': None}
>>>
As you can see b
is actually of type BitsInteger
The reason is because what's really going on is that when you write:
@singleton_function
def Bit():
"""Bit docstring"""
...
return BitsInteger(1)
You are really just getting syntactic sugar for this:
def Bit():
"""Bit docstring"""
...
return BitsInteger(1)
Bit = singleton_function(Bit)
In this case Bit
is the return value of singleton_function
which is actually a BitsInteger
. I find when you strip back the syntactic sugar it's much clearer what's going on here.
If you would like to have the convenience of a decorator that doesn't change the docstring I'd recommend using wrapt
import wrapt
class BitsInteger:
"""BitsInteger docstring"""
def __init__(self, num):
pass
def singleton(cls):
return cls()
@wrapt.decorator
def singletonfunction(func):
return func()
@singletonfunction
def Bit():
"""A 1-bit BitField; must be enclosed in a BitStruct"""
return BitsInteger(1)
b = Bit
print("b.__doc_ :", b.__doc__)
This outputs:
b.__doc_ : A 1-bit BitField; must be enclosed in a BitStruct
Now when you look at Vars()
you see that 'b': <FunctionWrapper at 0x7f9a9ac42ba8 for function at 0x7f9a9a9e8c80>
which is no longer a BitInteger
type. Personally I like using wrapt because I get what I want immediately. Implementing that functionality and covering all the edge cases takes effort and I know that wrapt is well tested and works as intended.