Object for accessing identifiers/names

Inspired by the discussions regarding Guido's "M atch statement brainstorm " and " Quick idea: defining variables from functions..." threads, here's an idea regarding names/identifiers. Currently, there is no direct way to work with variable names from within Python. Yes, you can fiddle with __dict__s and locals() and globals(), but there is no convenient general way. To solve this, there could be a way (probably new syntax) for creating an object that can examine and manipulate a name binding conveniently. The object could be created for instance as follows: name_object = identifier some_name However, 'identifier' is so long that I'll use the keyword 'def' instead, regardless of whether it is optimal or not: name_obj = def some_name Now this name_obj thing could provide functionality like assigning to the name some_name, getting the assigned object, and determining whether something has been assigned to the name or not . The object would also be aware of the name 'some_name'. T he functionality might work as follows: bool( name_obj ) # True if something is assigned to some_name name_obj.name() # == 'some_name' name_obj .set(value) # equivalent to `some_name = value` name_obj.get() # equivalent to just `some_name` name_obj.unset() # like `del some_name` Then you could pass this to a function: func(name_obj) Now func will be able to assign to the variable some_name, but with some_name referring to the scope where `name_obj = def some_name` was executed. This is similar to things that can be done with closures. This would also allow things like: if def some_name: #do stuff if the name some_name is bound to something or if not def some_name: some_name = something() - Koos

On Wed, Jun 1, 2016, at 15:22, Koos Zevenhoven wrote:
It sounds like for locals this could be implemented by adding your methods to the cell type (right now cell objects cannot be modified from python code), and having this statement return the cell. Any local variable that is used in this way gets a cell, the same as any local that is used in a closure does now.

On Wed, Jun 1, 2016 at 3:23 PM Koos Zevenhoven <k7hoven@gmail.com> wrote:
Here's an implementation. Can you show a complete example of where it would be useful in a program? class Identifier: ''' Manipulate a variable in the current scope. ''' def __init__(self, name): self.name = name self.scope = None def __enter__(self): caller = inspect.stack()[1][0] try: self.scope = caller.f_locals return self finally: del caller def __exit__(self, *args): self.scope = None def set(self, value): if self.scope is None: raise RuntimeError('must be used as a context manager') self.scope[self.name] = value def get(self): if self.scope is None: raise RuntimeError('must be used as a context manager') return self.scope[self.name] def del(self): if self.scope is None: raise RuntimeError('must be used as a context manager') del self.scope[self.name] if __name__ == '__main__': with Identifier('x') as name_x: name_x.set(5) print(name_x.get()) print(x) It's a context manager because I got nervous about reference cycles as warned by the docs of the inspect module. It might not be an issue, because this is referencing the calling frame rather than the current frame, but it's better to be safe than sorry.

On Wed, Jun 01, 2016 at 10:22:39PM +0300, Koos Zevenhoven wrote:
Currently, there is no direct way to work with variable names from within Python. Yes, you can fiddle with __dict__s and locals() and globals(),
The usual way is to write the name as a string and operate on the namespace. That means that variables (names) are not themselves values: a name is bound to a value, but there is no value which is itself a name. Instead, we use a string as a form of indirection.
Before getting too interested in the syntax for creating these, can you explain what you want to do with them? Most uses of names don't need to be values: spam = 23 import spam del spam all work well on their own. Can you give some use-cases?
How would that be different from this? name_object = 'some_name'
Most of these things already work using strings: # bind a name to the global scope globals()[name_object] = 999 # lookup a name in some module's namespace vars(module)[name_object] # check for existence name_object in vars(module) What functionality do you think is missing? -- Steve

[This almost two-week-old draft email was apparently waiting for me to hit 'send'] On Fri, Jun 3, 2016 at 9:22 AM, Steven D'Aprano <steve@pearwood.info> wrote:
Indeed the point is to make it possible to, on demand, create a value (object) representing a name.
This is a variation of the 'given <name>' syntax that I proposed in the matching syntax thread. So the truth value of the name object would tell you whether the name is bound to a value or not, as I explain in the OP of this thread too. But the __str__ of the name object would give the name, so that would allow things like: TypeVar(name_object) sympy.Symbol(name_object) and those constructors/callables would have access to the name in the calling scope and would be able to bind the value to it without one having to repeat oneself as in `alpha = sympy.Symbol('alpha')` etc. This could also be used as instead of a callback that just needs to assign to some variable in the calling scope (or the associated closure). You could potentially even `await` these things (until some other thread or task sets the variable), although there is some overlap in functionality with other awaitables of course. I believe this might also help a little in the example that Michael just posted in the other thread for matching and extracting values within nested JSON-like structures. I think there is more functionality that this could provide too. [...]
But changing it does not work similarly. From the docs: """ Note The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter. """
# check for existence name_object in vars(module)
Again from the docs: """ ...however, other objects may have write restrictions on their __dict__ attributes (for example, classes use a dictproxy to prevent direct dictionary updates). """
What functionality do you think is missing?
Well you need to understand locals(), globals(), vars() and __dict__ and their differences to be able to do these things. That's why these existing functions are not very suitable for the uses I'm describing. And they certainly don't do anything to help with TypeVar and sympy.Symbol :-). -- Koos -- + Koos Zevenhoven + http://twitter.com/k7hoven +

On Wed, Jun 1, 2016, at 15:22, Koos Zevenhoven wrote:
It sounds like for locals this could be implemented by adding your methods to the cell type (right now cell objects cannot be modified from python code), and having this statement return the cell. Any local variable that is used in this way gets a cell, the same as any local that is used in a closure does now.

On Wed, Jun 1, 2016 at 3:23 PM Koos Zevenhoven <k7hoven@gmail.com> wrote:
Here's an implementation. Can you show a complete example of where it would be useful in a program? class Identifier: ''' Manipulate a variable in the current scope. ''' def __init__(self, name): self.name = name self.scope = None def __enter__(self): caller = inspect.stack()[1][0] try: self.scope = caller.f_locals return self finally: del caller def __exit__(self, *args): self.scope = None def set(self, value): if self.scope is None: raise RuntimeError('must be used as a context manager') self.scope[self.name] = value def get(self): if self.scope is None: raise RuntimeError('must be used as a context manager') return self.scope[self.name] def del(self): if self.scope is None: raise RuntimeError('must be used as a context manager') del self.scope[self.name] if __name__ == '__main__': with Identifier('x') as name_x: name_x.set(5) print(name_x.get()) print(x) It's a context manager because I got nervous about reference cycles as warned by the docs of the inspect module. It might not be an issue, because this is referencing the calling frame rather than the current frame, but it's better to be safe than sorry.

On Wed, Jun 01, 2016 at 10:22:39PM +0300, Koos Zevenhoven wrote:
Currently, there is no direct way to work with variable names from within Python. Yes, you can fiddle with __dict__s and locals() and globals(),
The usual way is to write the name as a string and operate on the namespace. That means that variables (names) are not themselves values: a name is bound to a value, but there is no value which is itself a name. Instead, we use a string as a form of indirection.
Before getting too interested in the syntax for creating these, can you explain what you want to do with them? Most uses of names don't need to be values: spam = 23 import spam del spam all work well on their own. Can you give some use-cases?
How would that be different from this? name_object = 'some_name'
Most of these things already work using strings: # bind a name to the global scope globals()[name_object] = 999 # lookup a name in some module's namespace vars(module)[name_object] # check for existence name_object in vars(module) What functionality do you think is missing? -- Steve

[This almost two-week-old draft email was apparently waiting for me to hit 'send'] On Fri, Jun 3, 2016 at 9:22 AM, Steven D'Aprano <steve@pearwood.info> wrote:
Indeed the point is to make it possible to, on demand, create a value (object) representing a name.
This is a variation of the 'given <name>' syntax that I proposed in the matching syntax thread. So the truth value of the name object would tell you whether the name is bound to a value or not, as I explain in the OP of this thread too. But the __str__ of the name object would give the name, so that would allow things like: TypeVar(name_object) sympy.Symbol(name_object) and those constructors/callables would have access to the name in the calling scope and would be able to bind the value to it without one having to repeat oneself as in `alpha = sympy.Symbol('alpha')` etc. This could also be used as instead of a callback that just needs to assign to some variable in the calling scope (or the associated closure). You could potentially even `await` these things (until some other thread or task sets the variable), although there is some overlap in functionality with other awaitables of course. I believe this might also help a little in the example that Michael just posted in the other thread for matching and extracting values within nested JSON-like structures. I think there is more functionality that this could provide too. [...]
But changing it does not work similarly. From the docs: """ Note The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter. """
# check for existence name_object in vars(module)
Again from the docs: """ ...however, other objects may have write restrictions on their __dict__ attributes (for example, classes use a dictproxy to prevent direct dictionary updates). """
What functionality do you think is missing?
Well you need to understand locals(), globals(), vars() and __dict__ and their differences to be able to do these things. That's why these existing functions are not very suitable for the uses I'm describing. And they certainly don't do anything to help with TypeVar and sympy.Symbol :-). -- Koos -- + Koos Zevenhoven + http://twitter.com/k7hoven +
participants (4)
-
Koos Zevenhoven
-
Michael Selik
-
Random832
-
Steven D'Aprano