sbpy icon indicating copy to clipboard operation
sbpy copied to clipboard

Allow @cite decorator on base class?

Open jianyangli opened this issue 4 years ago • 5 comments

When subclass a class that has a @cite decorator directly on the class definition, an exception is raised:

from sbpy.bib import cite

@cite({'ref': 'paper 1'}) 
class A(): 
    pass 

class B(A): 
    pass 

Exception:

TypeError: function() argument 1 must be code, not str

jianyangli avatar Jul 21 '19 13:07 jianyangli

Oh, A is not a class after being decorated!

mkelley avatar Jul 21 '19 16:07 mkelley

As far as I understand, what needs to happen is that @cite creates a class that is a subclass of A, decorates its __init__ function, then returns the result. This is going to obfuscate things at the command line:

>>> from sbpy.bib import cite
>>> 
>>> 
>>> def cite_class(citations):
...     def class_decorator(cls):
...         class ClassWrapper(cls):
...             @cite(citations)
...             def __init__self(self, *args, **kwargs):
...                 return super().__init__(*args, **kwargs)
...         return ClassWrapper
...     return class_decorator
... 
>>> 
>>> @cite_class({'ref': 'paper 1'})
... class A():
...     pass
... 
>>> 
>>> class B(A):
...     pass
... 
>>> 
>>> print(issubclass(A, A))
True
>>> print(issubclass(B, A))
True
>>> 
>>> print(A)
<class '__main__.cite_class.<locals>.class_decorator.<locals>.ClassWrapper'>
>>> print(B)
<class '__main__.B'>

Notice the horrendous class name for A? I don't know how to get around that.

mkelley avatar Jul 21 '19 18:07 mkelley

This would make the citation report looking ugly. Is there a way to fix that? If that can be fixed, then I don't think this is a problem.

jianyangli avatar Jul 21 '19 23:07 jianyangli

I've gotten this far, but notice how the signature of __init__ is still based on the true class name:

from sbpy import bib


def cite_class(citations):
    def class_decorator(cls):
        class ClassWrapper(cls):
            @bib.cite(citations)
            def __init__(self, *args, **kwargs):
                return super().__init__(*args, **kwargs)

            __module__ = cls.__module__
            __name__ = cls.__name__
            __qualname__ = cls.__qualname__
            __doc__ = cls.__doc__

        return ClassWrapper
    return class_decorator


@cite_class({'ref': 'paper 1'})
class A():
    pass


class B(A):
    pass


print(issubclass(A, A))
print(issubclass(B, A))

print(A)
print(B)

bib.track()
print('\nbib tracking on')
a = A()
b = B()

print(bib.show())
bib.stop()
bib.reset()
notwo /tmp $ python3 test.py
True
True
<class '__main__.A'>
<class '__main__.B'>

bib tracking on
__main__.cite_class.<locals>.class_decorator.<locals>.ClassWrapper.__init__:
  ref:
    paper 1

mkelley avatar Jul 22 '19 11:07 mkelley

Well, looks like we need to explore other approaches. I was thinking of engineering the bib.register function to remove the horrendous class names, but the problem is the real class name is now <local> and no longer visible. Let's leave this for the future.

For now, although function decorator @cite works on class, we should only use it on functions because decorated class is no longer class. The consequences are:

  1. isinstance() and issubclass() no longer work.
  2. Static method no longer works

I think we should include this note in the documentation.

jianyangli avatar Jul 30 '19 14:07 jianyangli