3

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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
ArekBulski
  • 4,520
  • 4
  • 39
  • 61

1 Answers1

1

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.

shuttle87
  • 15,466
  • 11
  • 77
  • 106
  • Essentially `Bit = BitInteger(1)`. You have a typo in Bit definition, it doesnt return a class but an instance. Worse, it doesnt seem to work on PY3 and it doesnt inherit / >> operators. – ArekBulski Sep 17 '16 at 13:37
  • I ran this with cPython 3.5. If you could put some of the definition of the `BitsInteger` class along with what you need to achieve in the question it might help me improve the answer here. I'm not entirely clear on what the original problem you are trying to solve is. Do you need `Bit` to be callable and to always return the same (singleton) object? – shuttle87 Sep 18 '16 at 03:27
  • Bit sould be a class instance, that has the docstring taken from the function not the class. That's it. I accept your solution, even tho it sometimes works. – ArekBulski Sep 24 '16 at 11:58
  • Can you clarify your requirements in the question, I'm sure what you want can be achieved with wrapt. If you are trying to cache instances of the Bit classes I might write a blog post about it as it's an interesting challenge. – shuttle87 Sep 24 '16 at 13:02