1

I have a module, ui. I'd like to get a list of all the classes that ui defines which are a subclass of ui.View, and that can be called without arguments.

I've tried the following code:

ui_elements=[d for d in ui.__dict__.values() if type(d)==type and inspect.getargspec(d.__init__)[0]==1 and isinstance(d(),ui.View)]

But I get the following error from inside the inspect module:

TypeError: <slot wrapper '__init__' of 'object' objects> is not a Python function

I'm using inspect instead of a try: except: just because checking beforehand seems like a better solution than trying it to see if it works.

So, how can I get a list of all the classes that subclass ui.View and do not require arguments to create? I'd like to do this inline if possible, and I'd also like to avoid using try:/except: statements.

Luke Taylor
  • 8,631
  • 8
  • 54
  • 92
  • 2
    Note that you can use `issubclass` rather than trying to initialise them. Also, you could take failing to initialise them with no parameters as a sign that they require parameters - why the requirement for inline/no `try`? – jonrsharpe Feb 23 '16 at 15:19
  • @jonrsharpe I guess I thought `try` was a pretty in-pythonic way to count arguments. Is there a better way? – Luke Taylor Feb 23 '16 at 15:20
  • 3
    It's not very Pythonic to count parameters! Handling errors is idiomatic; *"it is easier to ask forgiveness than permission"*. Could you take a step back and describe the broader problem you're trying to solve? That way we avoid [XY problems](http://meta.stackexchange.com/q/66377/248731). – jonrsharpe Feb 23 '16 at 15:22
  • I'm using a Python distribution that includes a `ui` module. It's got a lot of different UI elements, all of which subclass `ui.View`. I'd like to make a `ui.View` with sub views for each type of UI element. I'm avoiding the ones that take extra arguments purely for simplicity's sake. – Luke Taylor Feb 23 '16 at 15:25
  • Please edit the question to include the additional context. – jonrsharpe Feb 23 '16 at 15:38
  • Just *try* to create them and catch the exceptions when creating them doesn't work. Having arguments doesn't mean that one cannot be made without them. They can be optional. – Alfe Feb 23 '16 at 15:40
  • @Alfe Yeah, that works if there's not a better way. – Luke Taylor Feb 23 '16 at 15:40
  • It seems to be necessary to explain (in the question) why you consider using introspection tools (like `inspect`) to count the number of arguments "better" than doing the simple `try`/`except` routine. – Alfe Feb 23 '16 at 15:43
  • @Alfe It's inline, and more pythonic probably. – Luke Taylor Feb 23 '16 at 15:44
  • 2
    You cannot *really* check beforehand, that's the core of our disagreement. If you check the number of arguments you might get classes which cannot be instantiated on Tuesdays. But you will not get the ones which have an optional argument for the background color. That would be two wrong predicates. And you *still* didn't give a reason why checking beforehand would be any better than trying and handling errors. – Alfe Feb 23 '16 at 15:49
  • @Alfe ok, I see your point. Try/catch is good enough. I was trying to see if I could get it into a single list comprehension, but that's not a requirement. – Luke Taylor Feb 23 '16 at 15:50
  • Is the module `ui` written by you? Can you add a list to that module that has all of the zero-argument Views in it? Or can you make them all a subclass of View that takes no arguments and then simple do `issubclass`? – djhoese Feb 23 '16 at 15:51
  • @daveydave400 No, it's a part of [Pythonista](http://omz-software.com/pythonista/index.html). Since it's an iOS app, built in modules are part of `PythonistaKit.framework`, and are therefore sandboxed and completely inaccessible. – Luke Taylor Feb 23 '16 at 15:53

2 Answers2

1

As said in comments, you should use try/catch, but if you really want to do it that way...

First, use inspect to check if your symbol is a class, use issubclass as suggested in comments and you want the number of arguments to be 1, not the first argument to be 1. So, something like this:

ui_elements = [ d for d in ui.__dict__.values()
    if (inspect.isclass(d) and
        issubclass(d, ui.View) and
        inspect.ismethod(d.__init__) and
        len(inspect.getargspec(d.__init__)[0]) == 1) ]
Guillaume
  • 10,463
  • 1
  • 33
  • 47
  • I accepted this for the `try/catch`, but the comprehension still throws the `TypeError` that ` is not a Python function` – Luke Taylor Feb 23 '16 at 15:58
  • 1
    Yeah, that happens because your class doesn't have an init method at all. So I guess your `View` class doesn't have one, or it would be inherited. I edited my code to handle this... but you may want to use the Alfe answer instead :) – Guillaume Feb 23 '16 at 16:11
  • Yeah, I did. Try/catch is good enough. My one question is, if it doesn't have __init__, why does `ui.TextField.__init__` return a `slot wrapper` (whatever that is) instead of raising an `AttributeError`? – Luke Taylor Feb 23 '16 at 16:13
  • That's because there's a C class behind, see: http://stackoverflow.com/q/15512183/1486118 – Guillaume Feb 23 '16 at 16:32
1

To get a list comprehension with try/except you can use a local function:

def tryCreate(class_):
  try:
    return class_()
  except:
    return None

ui_elements = set(tryCreate(d) for d in ui.__dict__.values()
    if issubclass(d, ui.View)) - set(None)

Okay, it's a set comprehension in the end ;-)

Alfe
  • 56,346
  • 20
  • 107
  • 159