Class introspection and dynamically determining function arguments

Bengt Richter bokr at oz.net
Fri Jan 21 19:31:33 EST 2005


On Thu, 20 Jan 2005 11:24:12 -0000, "Mark English" <Mark.English at liffe.com> wrote:

>I'd like to write a Tkinter app which, given a class, pops up a
>window(s) with fields for each "attribute" of that class. The user could
>enter values for the attributes and on closing the window would be
>returned an instance of the class. The actual application I'm interested
>in writing would either have simple type attributes (int, string, etc.),
>or attributes using types already defined in a c-extension, although I'd
>prefer not to restrict the functionality to these requirements.
My first reaction is that if you can limit the problem, it could be done fairly simply.
[... having read to the end by now, I see you were thinking of inspecting the __init__
method, so you are close to what I was thinking, but maybe this will be useful anyway,
so since it's done, I'll post it ;-) ]

E.g., if the attributes of interest are all set by simple attribute assignment in
the __init__ method of the class, using __init__ arguments with default arguments,
then you could inspect the __init__ function for parameter names and infer types
from the default arguments.

If the instance attribute name are bound in the __new__ method, you could still conceivably
do something if you had a similar known signature, but the for unrestricted general case
your Tkinter app would have to supply too much information than can't be inferred or be known
by the user. E.g., how would a Tkinter app (or any other) know what to ask the user to supply
to set the attributes of

    class WhatEver:
        def __init__(self, attr):
            self.attr = attr

?

>
>The only way I can imagine to do this is to create an instance of the
>class in question, and then start poking around in its attributes
>dictionary (initially just using dir). So firstly, if there is instead a
>way to do this without creating an instance I'd be interested.
As above. IMO you will have to restrict the problem. But perhaps that is
no so bad.

>
>Secondly, the code won't know exactly how to initialise the class
>instance used to determinte the attributes. Do I need to make it a
>prerequesite that all instances can be created with no arguments ?
I think that is a byproduct of the approach I suggested (i.e., all args
having default values for type inference), but there's probably other
ways.

>Should I force/allow the user to pass an instance instead of a class ?
I would say no. Especially considering possible side effects.

>Should I be using inspect.getargspec on the class __init__ method and
>then a loop with a try and a lot of except clauses, or is there a nicer
I don't know if you need a _lot_ of except clauses, but yes, at least one,
and a loop to allow retrying after typos etc.

>way to do this ? Presumably the pickling code knows how do
>serialise/deserialise class instances but I'm not sure how I'd use this
>without already having a class instance to hand.
>
>Lastly, does such an app already exist ?
Don't know. Wouldn't be surprised.

>
>Thanks for any help.

This shows you are probably close to solving your own problem with inspect.getargspec:

 >>> class C(object):
 ...     def __init__(self, a=1, b='bee', c=1.2):
 ...         self.a = a
 ...         self.b = b
 ...         self.c = c
 ...
 >>> inspect.getargspec(C.__init__)
 (['self', 'a', 'b', 'c'], None, None, (1, 'bee', 1.2))
 >>> print inspect.getargspec.__doc__
 Get the names and default values of a function's arguments.

     A tuple of four things is returned: (args, varargs, varkw, defaults).
     'args' is a list of the argument names (it may contain nested lists).
     'varargs' and 'varkw' are the names of the * and ** arguments or None.
     'defaults' is an n-tuple of the default values of the last n arguments.

 >>> args,_,_,defaults = inspect.getargspec(C.__init__)
 >>> values = []

Obviously you need an inner loop for correcting typos etc., and try/except to catch relevant
errors, but I'm just typing interactively here, so her goes:

 >>> for name, defv in zip(args[-len(defaults):], defaults):
 ...     vstr = raw_input('Please enter a string suitable for %s(s): '%type(defv).__name__)
 ...     values.append(type(defv)(vstr))
 ...
 Please enter a string suitable for int(s): 123
 Please enter a string suitable for str(s): 'this is a string'
 Please enter a string suitable for float(s): 1.5
 >>> values
 [123, "'this is a string'", 1.5]
 >>> c = C(*values)
 >>> c.a
 123
 >>> c.b
 "'this is a string'"
 >>> c.c
 1.5
 >>> cdef = C()
 >>> cdef.a, cdef.b, cdef.c
 (1, 'bee', 1.2)

I guess the single quotes on the str(s) entry shows that all the characters get entered ;-)
You might want to think about whether to process backslashes as escapes in your input. E.g.,

 >>> raw_input('Enter some escape example: ')
 Enter some escape example: Not an newline: \n -- nor '\n' ;-)
 "Not an newline: \\n -- nor '\\n' ;-)"
 >>> print raw_input('Enter some escape example: ')
 Enter some escape example: Not an newline: \n -- nor '\n' ;-)
 Not an newline: \n -- nor '\n' ;-)

BTW, You can avoid importing inspect and get the data of

 >>> inspect.getargspec(C.__init__)
 (['self', 'a', 'b', 'c'], None, None, (1, 'bee', 1.2))

that you want by looking at the unbound method C.__init__
(though im_func may be deprecated eventually?):

 >>> C.__init__.im_func.func_code.co_varnames
 ('self', 'a', 'b', 'c')
 >>> C.__init__.im_func.func_defaults
 (1, 'bee', 1.2)

or by getting the __init__ function as such, by avoiding the attribute
access that makes it and unbound or bound method

 >>> C.__dict__['__init__'].func_code.co_varnames
 ('self', 'a', 'b', 'c')
 >>> C.__dict__['__init__'].func_defaults
 (1, 'bee', 1.2)

Regards,
Bengt Richter



More information about the Python-list mailing list