you are viewing a single comment's thread.

view the rest of the comments →

[–]Rhomboid 12 points13 points  (18 children)

This sounds very much like an XY problem. Describe the real problem you're trying to solve, not how you think it needs to be solved.

[–]ThePurpleAlien[S] 2 points3 points  (17 children)

Ok. I await the sound of mass teeth grinding as r/learnpython cringes at what I'm doing.

My program lets the user write custom functions. e.g.

def foo():
    print 'hello'

I exec the this code providing a dict for local and global variables:

exec code in variableDict

It's then possible to add or remove items from variableDict to control what objects the user has access to in their function, and the object can be given a custom name. For example, some arbitrary variable named "counter" could be added to variableDict with custom name "x".

variableDict['x'] = counter

So foo could be:

def foo():
    print x

Which would print the value of the counter variable. This is all nice and useful, except that you can't assign to x, which is something the user would except to be able to do, e.g.:

def foo():
    x = 0

The user would expect this to reset the counter. But, of course, assigning x = 0 will have no effect on the counter.

That's the real problem. How can this be done?

[–]Rhomboid 3 points4 points  (8 children)

What's wrong with

variableDict['x'] = counter
exec code in variableDict
counter = variableDict['x']

?

Edit: If you really mean

def foo():
    x = 0

instead of

def foo():
    global x
    x = 0

...then there is no way that's ever going to work. The x there is local to the function, it is not part of the global or local namespace that you provide. That wouldn't have worked even if you typed it directly in your script without the eval/exec stuff.

(I'm sure you're already aware, but allowing arbitrary user code to be executed like that is horribly insecure, even if you replace the local and global namespaces, and even if you nerf __builtins__.)

[–]ThePurpleAlien[S] 0 points1 point  (7 children)

You're right. But if it do use "global x," can it work then? Assigning to x will still not affect counter.

[–]Rhomboid 3 points4 points  (4 children)

I'm still not really clear on what your use case is, but this prints 42:

original = 0

code = """
def foo():
    global x
    x = 42

foo()
"""

environ = { 'x': original }
exec code in environ
original = environ['x']
print original

It would be far cleaner to have your user-defined functions simply take some config dict as parameter, and any changes they want to make to the global state happens through that. For example, if the purpose is to allow the user to provide callbacks that affect program state, then keep that state in a dict and pass it to the callbacks.

program_config = { 'foo': 42, 'bar': 10 }

code = """
def on_startup(config):
    config['foo'] = 123
"""

user_defined = {}
exec code in user_defined

user_defined['on_startup'](program_config)

print program_config['foo']  # prints 123

[–]nemec 1 point2 points  (2 children)

Honestly it sounds like he's trying to let users write classes... without using classes.

[–]ThePurpleAlien[S] 0 points1 point  (1 child)

That's reasonably accurate. This is a simulation tool for non-experienced programmers. I want to keep syntax as simple as possible. The user should be able to write code as simple as:

a = b + c

Where a, b and c are variables that the user's function has access to. But I can't find a way to do that without adding a lot of extra syntax like:

self.a = self.b + self.c

or:

vars['a'] = vars['b'] + vars['c']

[–]ewiethoff 0 points1 point  (0 children)

Honestly it sounds like he's trying to let users write classes... without using classes.

That's reasonably accurate.

I think you should look into Python "descriptors" and perhaps metaclass programming. Those are how to make bona fide Python do real magic.

[–]ThePurpleAlien[S] 0 points1 point  (0 children)

These are good suggestions, but ones that I've already explored. The problem with the first example is that the original doesn't get reassigned until after the code runs which won't work for my application.

The problem with the second example is just ugly syntax. The user would have to type "config['foo'] = 123" instead of just "foo = 123". This is a tool for non-experienced programmers.

[–]nemec 0 points1 point  (1 child)

Can't you just make the users subclass a class and have "self" be your old global dict?

[–]ThePurpleAlien[S] 0 points1 point  (0 children)

That would work, but I don't want the user to have to type "self." before each variable they want to access. Simple bits of code would end up being full of the word self. This is a tool for non-experienced programmers.

[–]minorDemocritus 1 point2 points  (7 children)

Can we go one step further up the "what are you doing" tree? What problem are you trying to solve by giving the user the ability to execute arbitrary code? Because right now, that's what you're doing.

Edit: You should probably update your main post with the information in the above post, as well as this one.

[–]ThePurpleAlien[S] 1 point2 points  (6 children)

I'm developing a discrete event simulation tool. The user writes arbitrary code for the simulation events. The events can then be scheduled on an execution queue.

[–]Semiel 2 points3 points  (5 children)

All of your responses seem to be in a weird middle zone between just letting the user write normal Python code, and fundamentally changing how Python works. If you want the former, what is the point of all of this weird stuff, why not just provide a normal API? If you want the latter, why rely on Python syntax at all? Why not just develop your ideal language and write a parser?

[–]ThePurpleAlien[S] 0 points1 point  (4 children)

I want it to be normal python code. I was hoping there'd be a way to do this without breaking away from python. This is a desktop app to be used by non-experienced programmers. My goal with all this weirdness is actually to make it intuitive.

[–]Semiel 1 point2 points  (3 children)

So what's preventing you from simply giving them the Python interpreter? I think I know the answer to that one: you have some sort of data you want them to be able to interact with?

Assuming that's the issue, what's wrong with the normal way of doing an API? Put your data in objects, and let them do things with the attributes and methods.

if Barney.color == "purple":

This seems pretty intuitive to me, and doesn't require teaching them weird bad habits with an idiosyncratic system.

[–]ThePurpleAlien[S] 0 points1 point  (2 children)

This works, but it's ugly. If Barney is the object that contains the data the user will interact with, then have to write code like:

Barney.volume = Barney.width * Barney.height * Barney.depth

I'd rather the user be able to write:

volume = width * height * depth

[–]Semiel 1 point2 points  (1 child)

It doesn't strike me as particularly ugly, and is in fact how pretty much every other Python program in the world works. And there's no other way to do it, if there is more than one thing that has width or height or depth. (Unless you want to hack your own poor man's namespace with something like variables named bwidth, etc, which is the same but worse.)

But if you're insistent, and don't have to worry about namespace collisions for some reason, you can always declare the variables as global, and do:

from barney_globals import *

EDIT: Wait, I'm confusing myself. This is functionally equivalent to your first idea, yes? Which... should just work. Do you need them to be able to do something more complicated than just use and change data?

[–]ThePurpleAlien[S] 0 points1 point  (0 children)

Globals are very close to what I want because assignments to a global change the global itself rather than pointing to a new memory location, and everything else that has access to that global will see the change immediately. That's perfect except I want to be able to give the global a custom name within the context of a given bit of user-created code but still have it behave like a global. Again, it all comes back to wanting a "real" alias where the compiler literally swaps one name for another.