Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #90266 > unrolled thread

A __call__ "method" which is itself another callable class instance?

Started bySkip Montanaro <skip.montanaro@gmail.com>
First post2015-05-08 07:24 -0500
Last post2015-05-08 07:24 -0500
Articles 1 — 1 participant

Back to article view | Back to comp.lang.python


Contents

  A __call__ "method" which is itself another callable class instance? Skip Montanaro <skip.montanaro@gmail.com> - 2015-05-08 07:24 -0500

#90266 — A __call__ "method" which is itself another callable class instance?

FromSkip Montanaro <skip.montanaro@gmail.com>
Date2015-05-08 07:24 -0500
SubjectA __call__ "method" which is itself another callable class instance?
Message-ID<mailman.241.1431087871.12865.python-list@python.org>

[Multipart message — attachments visible in raw view] — view raw

This is just a pedantic post. It has no real useful application, just
something to think about. I apologize that the setup is rather long. The
stuff to think about is at the end. (I also apologize for using rich
text/html. In this HTML world in which we live nowadays, I'm never certain
that mail systems preserve leading whitespace, even in plain text mail.)

I have a script which accepts a CSV file as input and the definition of a
transform function. It applies the transform function to each row of the
CSV file and produces a new CSV file on standard out.

One of the things I discovered I needed in certain circumstances was to
inject user-defined variables into the system. Since it's a single pass
sort of thing (not long-lived), I decided to inject those variables into
the function's globals. (I could have added them to builtins, I suppose,
but it didn't occur to me when I first wrote the script.)

The challenge was to find the appropriate globlas dictionary into which to
inject these variable definitions. My code wound up looking like this:

    if inspect.ismethod(func):
func_globals = func.im_func.func_globals
    elif inspect.isfunction(func):
func_globals = func.func_globals
    elif inspect.isbuiltin(func):
func_globals = sys.modules[func.__module__].__dict__
    elif inspect.ismethoddescriptor(func):
raise TypeError("Can't get globals of a method descriptor")

What if func is actually an instance of a class with a __call__ method? So
I added another branch:

    elif hasattr(func, "__call__"):
func = func.__call__

But now what? This required me to to start the process over again. I also
needed an else clause. Let's add that first:

    else:
raise TypeError("Don't know how to find globals from %s objects" %
type(func))

I need to repeat the tests in the case where I found a non-function-like
object with a __call__ attribute. I decided to wrap this logic in a for
loop which could only run twice:

    for _i in (0, 1):
        if inspect.ismethod(func):
            func_globals = func.im_func.func_globals
        elif inspect.isfunction(func):
            func_globals = func.func_globals
        elif inspect.isbuiltin(func):
            func_globals = sys.modules[func.__module__].__dict__
        elif inspect.ismethoddescriptor(func):
            raise TypeError("Can't get globals of a method descriptor")
        elif hasattr(func, "__call__"):
            func = func.__call__
            # Try again...
            continue
        else:
            raise TypeError("Don't know how to find globals from %s
objects" % type(func))
        break
    else:
        raise TypeError("Don't know how to find globals from %s objects" %
type(func))

I thought about using while True, but thought, "nah, that's never going to
happen." Then I wondered if it *could* happen. I constructed a short chain
of __call__ attributes which might require the loop to repeat more than
twice, but I can't think of a situation where it would actually be useful:

    class C1(object):
       def __call__(self):
           print "called"

    class C2(object):
       __call__ = C1()

If you instantiate C2 and call the instance, it works as you would expect:

    >>> o = C2()
    >>> o()
    called

>From that simple example, it's straightforward to create a chain of classes
of any length, thus requiring my for loop to potentially be a while True
loop. The question for the esteemed gathering here: have you ever
encountered the need for this class-instance-as-a-dunder-call-method
before? Perhaps in some sort of code generation situation? Or cleaner
functions-with-attributes?

Skip

[toc] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web