exec to add a function into a class
Asked Answered
F

2

2

So I've looked at similar questions, and I've found some solutions to this, but I can't quite figure out how to do this.

What I'm trying to do is add a method to a class from a string. I can do this with the setattr() method, but that won't let me use self as an attribute in the extra method. Here's an example: (and I apologize for the variable names, I always use yolo when I'm mocking up an idea)

class what:
    def __init__(self):
        s = 'def yolo(self):\n\tself.extra = "Hello"\n\tprint self.extra'
        exec(s)
        setattr(self,"yolo",yolo)

what().yolo()

returns this:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: yolo() takes exactly 1 argument (0 given)

and if s = 'def yolo():\n\tself.extra = "Hello"\n\tprint self.extra' then I get this result:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 2, in yolo
NameError: global name 'self' is not defined

This essentially means that I cannot dynamically create methods for classes, which I know is bad practice and unpythonic, because the methods would be unable to access the variables that the rest of the class has access to.

I appreciate any help.

Fae answered 6/10, 2013 at 5:3 Comment(4)
Why do you want to dynmically add methods to a class?Bubble
@Bubble Haha... Is because I can a good enough reason?Fae
No, because it's completely unpythonic. I hear ruby loves that shit, though.Bubble
Just about everything in this question is a bad idea. And especially so for someone who hasn't bothered to learn how methods and locals and so on work before trying to write hacky code for no reason.Poston
B
7

You have to bind your function to the class instance to turn it into a method. It can be done by wrapping it in types.MethodType:

import types

class what:
    def __init__(self):
        s = 'def yolo(self):\n\tself.extra = "Hello"\n\tprint self.extra'
        exec(s)
        self.yolo = types.MethodType(yolo, self)

what().yolo()

On a side note, why do you even need exec in this case? You can just as well write

import types

class what:
    def __init__(self):
        def yolo(self):
            self.extra = "Hello"
            print self.extra

        self.yolo = types.MethodType(yolo, self)

what().yolo()

Edit: for the sake of completeness, one might prefer a solution through the descriptor protocol:

class what:
    def __init__(self):
        def yolo(self):
            self.extra = "Hello"
            print self.extra

        self.yolo = yolo.__get__(self)

what().yolo()
Beeline answered 6/10, 2013 at 5:11 Comment(4)
THank you! I am just using this as proof of concept, so in this case I really don't need it, but it's more for the sake of the idea than practicality.Fae
One side not I noticed, the code here doesn't allow for expandability as I have to declare the function in the self.yolo = types...line, I think I could use exec() to amend this, but I will have to check still.Fae
If you want to set the attribute name dynamically, you can use setattr (as you did in the original code). I just wrote self.yolo = because the setattr with a string literal as the second argument is quite pointless.Beeline
@Hovestar: Any time you think "I could use exec…" you're almost certainly doing it wrong. Python has ways to dynamically reflect on almost anything without ever needing to use exec. All you're doing is making your code a little slower, a lot less readable, and a whole lot harder to debug, for absolutely no benefit. If you're writing a custom REPL or something else that obviously needs to execute strings, then use exec; in any other case, look for another way, and, if you can't find one, look again.Poston
A
1

Another way, seems more elegant to me:

class what:
    pass

ld = {}
exec("""
def yolo(self):
    self.extra = "Hello"
    print(self.extra)
""", None, ld)
# print('locals got: {}'.format(ld))
for name, value in ld.items():
    setattr(what, name, value)

what().yolo()
Amanita answered 20/7, 2016 at 20:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.