22

What is a way to extract arguments from __init__ without creating new instance. The code example:

class Super:
    def __init__(self, name):
        self.name = name

I am looking something like Super.__dict__.keys()type solution. Just to retrieve name argument information without adding any values. Is there such an option to do that?

Vy.Iv
  • 829
  • 2
  • 8
  • 17
  • Question is not clear. Do you mean you want to check value of name and decide if you want to create new instance or not? – ronakg Apr 25 '16 at 19:56
  • I don't want to create instance at all. I want to introspect objects arguments. It is just like question "what arguments this object have". It is all about general introspection not about values. I changed question to make it a bit more understandable. – Vy.Iv Apr 25 '16 at 20:07
  • 1
    Instance is already created by the time `__init__` gets called. See this answer http://stackoverflow.com/questions/674304/pythons-use-of-new-and-init – ronakg Apr 25 '16 at 20:22

2 Answers2

41

Update for Python 3.3+ (as pointed out by beeb in the comments)

You can use inspect.signature introduced in Python 3.3:

class Super:
    def __init__(self, name, kwarg='default'):
        print('instantiated')
        self.name = name

>>> import inspect
>>> inspect.signature(Super.__init__)
<Signature (self, name, kwarg='default')>

Original answer below

You can use inspect

>>> import inspect
>>> inspect.getargspec(Super.__init__)
ArgSpec(args=['self', 'name'], varargs=None, keywords=None, defaults=None)
>>> 

Edit: inspect.getargspec doesn't actually create an instance of Super, see below:

import inspect

class Super:
    def __init__(self, name):
        print 'instantiated'
        self.name = name

print inspect.getargspec(Super.__init__)

This outputs:

### Run test.a ###
ArgSpec(args=['self', 'name'], varargs=None, keywords=None, defaults=None)
>>> 

Note that instantiated never got printed.

Bahrom
  • 4,752
  • 32
  • 41
  • It looks like that it is what I was looking for. One additional question: Is there an option to get that result without "inspect" module. Something like Super.__dict__.keys solution if it is not, that means that inspect.getargspec() is like instance simulation that helps to retrieve arguments? – Vy.Iv Apr 25 '16 at 20:46
  • 1
    There are a bunch of answers for retrieving argument names out of a function - http://stackoverflow.com/questions/582056/getting-list-of-parameter-names-inside-python-function and it looks like inspect is the only way. Even using `globals` returns in a function call, which is not what you want. Incidentally, using `inspect` doesn't result in a function call, editing my answer for an example... – Bahrom Apr 25 '16 at 21:00
  • @Bahrom anyway to do this for a hierarchy of inheriting classes? – Veltzer Doron Dec 18 '17 at 17:09
  • @VeltzerDoron I'll take a look when I get home – Bahrom Dec 18 '17 at 20:55
  • 1
    Actually, I already implemented it by travelling up the class.mro() and getting the __init__ signature along the way, I used it to write a custom JSON encoder that doesn't encode members with default values, works like a charm. :) – Veltzer Doron Dec 19 '17 at 00:45
  • 3
    DeprecationWarning: inspect.getargspec() is deprecated since Python 3.0, use inspect.signature() or inspect.getfullargspec() – beeb Jan 15 '20 at 11:18
1

A metaclass that stores the concrete __init__ parameters in a class field:

class ParameterReader(ABCMeta):
    def __init__(cls, *args, **kwargs):
        parameters = inspect.signature(cls.__init__).parameters
        parameters = {key: value for key, value in parameters.items() if key not in ['self', 'args', 'kwargs']}
        try:
            cls._init_parameters = cls.__bases__[0]._init_parameters.copy()
            cls._init_parameters.update(parameters)
        except AttributeError:
            cls._init_parameters = parameters

        super().__init__(*args, **kwargs)

_init_parameters can then be used within the class instance or outside of it:

class Fruit(metaclass=ParameterReader):
    def __init__(self, color):
        print(color)

class Corn(Fruit):
    def __init__(self, size, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print(size)
        print(self._init_parameters)

print(Corn._init_parameters)

Producing:

{'color': <Parameter "color">, 'size': <Parameter "size">}

As well as in instantiation:

Corn(10, 'yellow')

Producing:

yellow
10
{'color': <Parameter "color">, 'size': <Parameter "size">}

Note how this handles using *args, **kwargs in Corn's __init__ parameters.


Also, note the naming difference between arguments and parameters.

Voy
  • 5,286
  • 1
  • 49
  • 59