0

EDITED: Let's say I have some classes that inherit from the SuperFoo abstract class:

from abc import ABCMeta, abstractmethod

class SuperFoo(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def do_something():
        pass

class Foo(SuperFoo):
    def __init__(self):
        pass

    def do_something():
        pass

class Bar(SuperFoo):
    def __init__(self):
        pass

    def do_something():
        pass

And a documented function that takes in a subclass of SuperFoo as a parameter:

def build(super_foo):
    """
    Instantiate a SuperFoo class and do some other stuff.
    @param super_foo: The subclass whose constructor will be called
    @type super_foo: ??? <--- What to use here?
    @return: An instance of a SuperFoo subclass
    @rtype: SuperFoo
    """
    # Do some stuff

    instance = class_name()  # Instantiate class
    return instance

foo = build(Foo)
bar = build(Bar)

What @type should I use in the function's docstring? It cannot be SuperFoo because that would correspond to an instance of SuperFoo and not to the type itself.

Gerardo Figueroa
  • 549
  • 2
  • 6
  • 16
  • 1
    1) Everything is an object in Python. 2) Its type is literally just ... `type`. – meowgoesthedog Dec 28 '18 at 09:40
  • 1
    A *name* would be a string, e.g. `"Foo"`. You're actually passing an *object*. – deceze Dec 28 '18 at 09:40
  • In python everything is an object. That being said when we write a Class in your example Foo, then all the class in python inherit the Meta Class type. @type means that the class you are passing should be class which inherit the Meta Class type. To check this behaviour try to run type(Foo) >>> . I hope I was able to explain your problem – Aman Raparia Dec 28 '18 at 09:56
  • @deceze Actually if you see I am not passing the name of the class as a string, but the type itself. It cannot be `object` since objects are not callable. – Gerardo Figueroa Dec 28 '18 at 10:01
  • 1
    It is not the class name itself. It is *the class object itself*. – juanpa.arrivillaga Dec 28 '18 at 10:14
  • "objects are not callable" – Says who? – deceze Dec 28 '18 at 10:14
  • @deceze Says my IDE: `'object' object is not callable. Inspection info: This inspection highlights attempts to call objects which are not callable, like, for example, tuples.` – Gerardo Figueroa Dec 28 '18 at 13:04
  • 1
    @GerardoFigueroa an `object` INSTANCE is not callable. The `object` class IS - of course - callable. ALL classes are callable, that's how you instanciate them. – bruno desthuilliers Dec 28 '18 at 13:08

2 Answers2

3

The simple technical answer has already been posted by motyzk - in your example, what you pass to build are classes so the the (ill-named) class_name param is of type type - with the restriction that (based on your code snippet) this class shouldn't expect any parameter, which doesn't correspond to any unambigous well-defined existing builtin "type".

Now as mentionned in comments, in Python

  • everything is an object (and as such an instance of a class),
  • instanciating a class is done by calling the class object just like you'd call any function,
  • all callables DO return an object, even if implicitely (None is an object too)

so your build function would technically work just the same with just any callable that doesn't expect a param. As an example, this:

def bar():
   return

whatever = build(bar)

is technically correct.

Now you mention in the function's docstring (emphasis is mine):

Instantiate a class and do some other stuff

Since your function would just be totally useless as shown in the snippet you posted, I assume that in your real code the important part is in the (not shown) "do some other stuff", and that this "do some other stuff" part relies on some specific property of the class being passed.

In this case, you should document those specificities, either informally (textually, in the docstring itself) or formally using the abc module. This won't solve your question actually - your function expects a class, not instance, so theoritically @type should be type(YourABC), but then what you get is abc.ABCMeta. At this point, you either have to write a custom metaclass (inherithing from abc.ABCMeta) and specify it as the proper @type - but this won't say anything useful as far as documentation is concerned - or just textually describe it as "a MyABC subclass" (which is the correct description but not usable for code instrumentation).

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Thanks for the clarifications. I edited the question to indicate that the parameter is a subclass of an abstract class `SuperFoo`, so `type` would be too broad here. – Gerardo Figueroa Dec 28 '18 at 12:58
  • 1
    But if I do `@type super_foo: SuperFoo` the function would expect an instance of `SuperFoo`, when it should really expect the `SuperFoo` type. The IDE complains as well. – Gerardo Figueroa Dec 28 '18 at 13:15
  • Duh - you're right of course - what the function expects is a SuperFoo subclass, not a SuperFoo instance, my bad (time for a coffee I think ). – bruno desthuilliers Dec 28 '18 at 14:02
  • I edited my answer again. I'm sorry to have to say that I can't think of any formal unambiguous answer here... OTHO if it's _only_ for documentation, just stating "@type: a `SuperFoo subclass` is more than enough. Python is a dynamic language and has used "implied interfaces" (ie "a file-like object" or "a dict-like object" for decades, so "A SuperFoo subclass" is rather more explicit than what you'll find in most docs. – bruno desthuilliers Dec 28 '18 at 14:16
2

The type is "type", as can be seen, running this:

class Foo(object):
    def __init__(self):
        pass


def f(t):
    print(type(t)) # <class 'type'>

f(Foo)
motyzk
  • 366
  • 3
  • 14