0

If I have a function f(x) and variable var of any type and scope is there any way to modify var inside call to f(var)?

Meaning that function f does some magic stuff to get reference to original (passed) var (like reference/pointer in C++) and modifies that original var, for any type of var (even int/float/str/bytes).

Wrapping var into dict or list or any other class is not allowed. Because it is known that dict/list are passed by reference.

Returning new value from a function to re-assign variable is not allowed too.

Modifying scope of original variable (like making it global) is not allowed too.

In fact any change to function's caller's code is not allowed.

This variable can be of any imaginary type and scope, no assumptions should be done about them.

So that next code should work (such code can be placed both inside another wrapping function or globally, should not matter):

def test():
    def magic_inplace_add(x, val):
        # ...
    var = 111
    magic_inplace_add(var, 222) # modifies int var by adding 222
    print(var) # prints 333

test()

If needed to solve the task this function can do any complex manipulations like using inspect module.

Basically, I need somehow to break a convention about simple types being passed by value, even if to achieve this in some non-simple/cryptic way.

I'm pretty sure this task can be solved by tools from standard reverse-engineering modules like inspect / ast / dis.

Just to clarify my need for this task - right now for this task I'm not trying to code nicely using tidy Python style and methods, you can imagine that this task in future is a kind of interview question for companies specializing in reverse-engineering / antiviruses / code security / compilers implementation. Also tasks like this are interesting in exploring all hidden possibilities of languages like Python.

Arty
  • 14,883
  • 6
  • 36
  • 69
  • 1
    use global variables – Sivaram Rasathurai Oct 04 '20 at 11:23
  • @rcvaram It is not allowed to do any assumptions on the type of original variable, making it global is not allowed too. – Arty Oct 04 '20 at 11:24
  • 1
    @rcvaram In fact it is not allowed to change function-calling code in any way. – Arty Oct 04 '20 at 11:25
  • 1
    Do you have a real need to break the basic Python rule that immutable variables can't be modified this way? – Thierry Lathuille Oct 04 '20 at 11:34
  • @ThierryLathuille Yes, at least there is a scientific reason for having such possibility. But yes, it is needed for a special case. – Arty Oct 04 '20 at 11:36
  • 1
    "it is known that dict / list are passed by reference" it is not known, because that is false. Python *never* passes anything by reference – juanpa.arrivillaga Oct 04 '20 at 11:36
  • 1
    @Arty what special case? What do you mean there is a "scientific reason"? What exactly are you trying to do and why? – juanpa.arrivillaga Oct 04 '20 at 11:36
  • 1
    Somebody closed my question in favor of [this one](https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference), but that one is about how to somehow pass a reference, in any way. And my question is different, I know that wrapping into dict/list is always possible. But I need a solution if exists at all to do without any wrapping or change of caller's code. – Arty Oct 04 '20 at 11:38
  • And yes, I want to break Python's convention about simple types being passed by value. – Arty Oct 04 '20 at 11:40
  • @Arty **simple types aren't pass by value**. Nothing is *ever* pass by value or pass by reference, Python uses *another evaluation strategy*. You have several fundamental misunderstandings of what is going on in Python. – juanpa.arrivillaga Oct 04 '20 at 11:40
  • @juanpa.arrivillaga I know that simple types are passed by value, but [inspect](https://docs.python.org/3/library/inspect.html) module allows many hacky things to break almost any standard behavior. I'm sure it can give me somehow reference to variable of simple type. – Arty Oct 04 '20 at 11:42
  • @Arty no, you don't know that because *that isn't the case*, simple types *are not passed by value*. **Nothing is ever passed by value in Python**. It is unclear exactly what you are even asking, i.e., do you want to actually modify the object, or simply the variable. "variables" aren't passed in to function, *objects are*. – juanpa.arrivillaga Oct 04 '20 at 11:43
  • @juanpa.arrivillaga I want a code like provided in my question to work without any modification to function caller's code. Only function itself can be changed. – Arty Oct 04 '20 at 11:45
  • 2
    @Arty that isn't answering my question at all. What you are asking is not fully specified, you keep making false statements about Python's semantics to try to explain what you want to do, and that makes it even more confusing. If you want to do something very hacky, then you need to be able to explain exactly what it is you are trying to do. – juanpa.arrivillaga Oct 04 '20 at 11:46
  • There isn’t a single function in Python that behaves as you describe, not even the builtin keywords which could behave any way they wanted. The duplicate question which contains many different takes on the topic also lists nothing of the sort. What kind of special case do you think you have here and why are you so sure this is possible in any way? – deceze Oct 04 '20 at 12:00
  • @deceze I'm looking into any possibilities, at least it is definitely possible by finding variable's location in memory and patching that location in memory by some `C++`/`Assembler` code. I've added last paragraph in my question's text to clarify the need for this task. – Arty Oct 04 '20 at 12:47
  • @juanpa.arrivillaga I've added last paragraph in my question's text to clarify the need for this task. – Arty Oct 04 '20 at 12:47
  • 1
    @Arty you need to understand **variables don't have locations in memory, objects do**. In any case, you can use `ctype` or write C-extensio to *mutate anything* if you really want, look at [this example](https://www.reddit.com/r/Python/comments/2441cv/can_you_change_the_value_of_1/) but that will *definitely not* work without making various hard assumptions about your input, and will likely be wholly implementation dependent. But again, you *really* need to understand Python semantics, that would be step *one* of these sorts of things, like stop repeating that things are passed by reference – juanpa.arrivillaga Oct 04 '20 at 20:15
  • @Arty--playing with inspect I find this is doable when function magic_inplace_add is at the module level but not when it is within a nested function. Issue in this case is var is in locals() which can't be updated from another stack frame i.e. [How can I force update the Python locals() dictionary of a different stack frame](https://stackoverflow.com/questions/36678241/how-can-i-force-update-the-python-locals-dictionary-of-a-different-stack-frame). Is handling the non-nested case sufficient? – DarrylG Oct 05 '20 at 07:11
  • @DarrylG Seems like this question didn't get any solutions at all, so at least if you post your solution for case of global functions that already is a good start point for solving the task. So please post your solution, I'll upvote it for sure if it works for global function. – Arty Oct 07 '20 at 15:43
  • @Arty--showed example for module-level functions and why approach does work for nested functions. – DarrylG Oct 07 '20 at 17:28

2 Answers2

2

For module scope variables.

import inspect
import re

def magic_inplace_add(var, val):
    '''
        Illustation of updating reference to var when function is called at module level (i.e. not nested)
    '''
    # Use inspect to get funcstion call syntax
    previous_frame = inspect.currentframe().f_back
    (filename, line_number, 
     function_name, lines, index) = inspect.getframeinfo(previous_frame)
    # lines contains call syntax
    # i.e. ['magic_inplace_add(x, 222)\n']
    args = re.findall("\w+", lines[0]) # get arguments of calling syntax i.e.  ('magic_inplace_add', 'x', 222)
    
    # Update variable in context of previous frame
    # i.e. args[1] == 'x' in example
    if args[1] in previous_frame.f_globals:
        # update variable from global
        previous_frame.f_globals.update({args[1]:var + val})
    elif args[1] in previous_frame.f_locals:
        # for nested function variable would be in f_locals
        # but this doesn't work in Python 3 this f_locals is a copy of actual locals in calling frame
        # see Martijn Pieters answer at
        # https://stackoverflow.com/questions/36678241/how-can-i-force-update-the-python-locals-dictionary-of-a-different-stack-frame
        previous_frame.f_locals.update({args[1]:var + val})  # this is what would be used for nested functions
     

Example

x = 111 # x module scope
y = 222 # y module scope
z = 333 # z module scope
magic_inplace_add(x, 222)
magic_inplace_add(y, 222)
magic_inplace_add(z, 222)
print(x)  # Output: 333
print(y)  # Output: 444
print(z)  # Output: 555
DarrylG
  • 16,732
  • 2
  • 17
  • 23
  • Thanks! UpVoted. At least works for me when testing code is in global scope. But strange, but when testing code is within a function it doesn't work, locals dict is not changed, although magic func is at module level, [see example online here](https://cutt.ly/JgrsrAv) – Arty Oct 08 '20 at 03:16
  • @Arty--not strange--that's the comment I made about not working when the function is nested (i.e. within another function). The issue is the elif branch that updates locals updates a copy of locals rather than the actual locals in Python 3 as the link I provided attests. – DarrylG Oct 08 '20 at 03:45
  • @DarryIG I thought that what you meant by saying that func should be in module/global scope, but as in my link to code in previous comment - it appears to be that variables should be also at module/global scope? So both function and variable should be all at global scope - is that right pre-requisite for working of your code? But still is very nice that at least one working solution has been posted, I up-voted your solution! – Arty Oct 08 '20 at 03:59
  • @DarryIG BTW, what's the reason that Python doesn't allow to modify locals of previous frame? Is it for security reasons to protect from doing things like we intended to do or some other reason? – Arty Oct 08 '20 at 04:16
  • @Arty--seems this could have been made to work in Python 2 but a Python 3 performance optimization changed the behaviour i.e. [Change the value of a local variable where variable name will be expressed as a string](https://stackoverflow.com/questions/32440101/change-the-value-of-a-local-variable-where-variable-name-will-be-expressed-as-a/32440576#32440576). – DarrylG Oct 08 '20 at 08:29
-1

as @rcvaram suggested use global variables, like this

def magic_inplace_add(x, val):
    global var
    var = 333
global var
var = 111
magic_inplace_add(var, 222) # modifies int var by adding 222
print(var)
Shoaib Mirzaei
  • 512
  • 4
  • 11
  • 2
    I told in my question that it is not allowed to change type or scope of a variable. In fact it is not allowed to do any modifications to function's caller's code. – Arty Oct 04 '20 at 11:32