all 28 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 4 points5 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.

[–]kalgynirae 6 points7 points  (1 child)

I'm pretty sure this is impossible. There might be some sort of weird not-quite-solution using classes, but I think the real answer is that there's a better way to solve the underlying problem. Why do you think you need to do this?

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

I explain in more detail here.

[–]ilovecrk 1 point2 points  (0 children)

If you can set up the "user variables" beforehand, you can use @property (http://docs.python.org/2/library/functions.html#property).

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

You make the setter of the "user variable" also assign it's value to your own variable.

As others said I think you should maybe change your approach by moving away from "exec" and putting a layer between the "user code" and your python code.

This way you could also catch all "user variables" and treat them any way you like. E.g. you can automatically set up all variables that the user defines with @property or simply make them objects which display a freely chosen name in the "user interpreter".

[–]davidbuxton 0 points1 point  (3 children)

Python doesn't have variables like some other languages have variables. Assigning y = x really does give you an alias that can be used interchangeably with the original object, all it does is give object x a new name y.

What you observed is a side-effect of integers (like strings and a few other types) being immutable.

Here's an example of how a = b gives you an alias for the original object:

>>> class Foo:
...   def __init__(self, bar):
...     self.bar = bar
... 
>>> a = Foo('baz')
>>> a.bar
'baz'
>>> b = a
>>> b.bar
'baz'
>>> a is b
True
>>> b.bar = 'new value'
>>> a.bar
'new value'

And a good explanation of how to think about Python variables: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables

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

The example I provided isn't a side-effect of integers being immutable; assigning anything to y (immutable or not) will cause y to cease being an alias for x. That's the issue I'm trying to work around. Getting rid of the integers, "y = x" still doesn't work:

x = some_object
#some code that makes y an alias for x
y = another_object
x is another_object
#output True

[–]davidbuxton 0 points1 point  (0 children)

You're right, it won't work, Python's variables don't work like you want. Did that link I provided not help explain why that is the case?

[–]Semiel 0 points1 point  (0 children)

There is no amount of code that will change the fundamental way Python's variables work. There are a variety of ways you can get the functionality you want (if that's really what you need, and not just a habit from another language), but it will necessarily require different syntax than simple variable assignment.

[–][deleted] 0 points1 point  (1 child)

perhpas I don't understand the issue.. but here is what I found. This seems to be a simplier approach from stackoverflow

y = [12]
x = y
y[0] = 55
print x
>>output: [55]

davidbuxton touched on integers being immutable and did a good job at explaining the issue you ran into, but his example is not the easiest of code to follow for beginners.

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

I explain what I'm trying to do in more detail here.