dynamic type returning NameError:

Steven D'Aprano steve+comp.lang.python at pearwood.info
Mon Jul 29 07:43:39 CEST 2013


On Sun, 28 Jul 2013 18:38:10 -0700, Tim O'Callaghan wrote:

> Hi,
> 
> I hope that this hasn't been asked for the millionth time, so my
> apologies if it has.
[...]
> I hope that this was clear enough, apologies if it wasn't.

Clear as mud. 


> It's late(ish), I'm tired and borderline frustrated :)

I see your smiley, but perhaps you would get better results by waiting 
until you can make a better post.

It *really* helps if you post actual "working" (even if "working" means 
"fails in the way I said"), *short*, *simple* code. Often you'll find 
that trying to simplify the problem gives you the insight to solve the 
problem yourself.

http://www.sscce.org/


I'm going to try to guess what you're attempting, but I may get it 
completely wrong. Sorry if I do, but hopefully you'll get some insight 
even from my misunderstandings.


> I have a base class (BaseClass - we'll call it for this example) with an
> http call that i would like to inherit into a dynamic class at runtime.
> We'll call that method in BaseClass;  'request'.

If I read this literally, you want to do this:

class BaseClass(DynamicParent):
    def request(self):
        ...

except that DynamicParent isn't known until runtime. Am I close?

Obviously the above syntax won't work, but you can use a factory:

def make_baseclass(parent):
    class BaseClass(parent):
        def request(self):
            ...
    return BaseClass

class Spam: ...

BaseClass = make_baseclass(Spam)


Or you can use the type() constructor directly:

BaseClass = type('BaseClass', (Spam,), dict_of_methods_and_stuff)


which is probably far less convenient. But all this assumes I read you 
literally, and reading on, I don't think that's what you are after.


> I have a dictionary(json) of key (class name): value(method) that I
> would like to create inheriting this 'request' method from the
> BaseClass. So the derived class would look something like this
> 
> definition in json:
> {"Whatever": [{"method1": "Some Default", "async": True},{"method2":
> "Some Other Default", "async": True}]}


Pure gobbledygook to me. I don't understand what you're talking about, 
how does a derived class turn into JSON? (Could be worse, it could be 
XML.) Is BaseClass the "derived class", or are you talking about 
inheriting from BaseClass? What's "Some Default"? It looks like a string, 
and it certainly isn't a valid method name, not with a space in it.

Where did async and method2 come from? How do these things relate to 
"request" you talk about above? I think you're too close to the problem 
and don't realise that others don't sharing your knowledge of the problem.

But, moving along, if I've understood you correctly, I don't think 
inheritance is the right solution here. I think that composition or 
delegation may be better. Something like this:

class BaseClass:
    def request(self):
        # Delegate to a method set dynamically, on the instance.
        return self.some_method()


a = BaseClass()
a.some_method = one_thing.method1

b = BaseClass()
b.some_method = another_thing.method2


Now you have instance a.request calling method1 of another object, and 
b.request calling method2 of a different object. Does that solve your 
problem, or am I on a wild-goose chase?


> Ideally I'd like the class def to look something like this if i were to
> type it out by hand
> 
> [excuse the indents]
> 
> class Whatever(BaseClass):
>     def method1(self):
>         stupid_data = super(Whatever, self).request("method1")
>         return stupid_data
>     
>      def method2(self):
>         stupid_data = super(Whatever, self).request("method1")
>         return stupid_data


Since request is not the method you are currently in, the above is 
equivalent to:

class Whatever(BaseClass):
    def method1(self):
         return self.request("method1")
    def method2(self):
         return self.request("method2")

where "request" is defined by BaseClass, and assuming you don't override 
it in the subclass. (I assume "method1" in your code above was a typo.)


> Now, I've been trying to do this using the python cli, with out success.
> 
> So, attempting this at runtime I get a plethora of wonderful errors that
> I suspect has broken my brain.
> 
> Here is what i've tried:
> 
> # trying with just an empty object of type BaseClass 
> obj = type("Object", (BaseClass,), {})

"obj" here is a class called "Object", inheriting from BaseClass. It 
overrides no methods. Why does it exist? It doesn't do anything.


> whatever = type("WhatEver", (obj,), {"method1": super(WhatEver,
> self).request("method1")})
> 
> but when i try this I get 'NameError: name 'self' is not defined'

This is what you are doing:

* look up names WhatEver and self, in the current scope (i.e. the scope 
where you are running this call to type, which is likely the global 
scope);

* pass those objects (if they exist!) to super(), right now;

* on the object returned, look up the attribute "request", right now;

* call that object with string argument "method1", right now;

* take the result of that sequences of calls, let's call it x, and set it 
in a new dict with key "method1";

* and then create a new type using dict {'method1': x}.


Notice that everything you do is done immediately. You should be getting 
a NameError for WhatEver too, but since you are not, I can only imagine 
that you have already (accidentally?) defined something, anything, with 
that name.

What you actually want (but probably not what you *need*, see above about 
delegation) is to delay evaluation of super() and subsequent calls until 
after your WhatEver class is created and initialised and the method1 
*method* is called. That means putting it all those calls inside a 
function object, for later use, instead of executing them directly *right 
now*:

def method1(self):
    return super(WhatEver, self).request("method1")

WhatEver = type("WhatEver", (BaseClass,), {"method1": method1})

You could alternatively use lambda:

WhatEver = type("WhatEver", (BaseClass,), 
    {"method1": 
        lambda self: super(WhatEver, self).request("method1")
        }
    )


Note that the class name inside super() *must* match the global name the 
class is assigned to, "WhatEver". The internal class __name__ -- the 
first argument to type() -- doesn't have to match, but it should, to 
avoid confusion.

The above should fix the NameError you are getting, and it might even 
work, but I think delegation is a better solution to this problem.



-- 
Steven



More information about the Python-list mailing list