-2

This is the common example for string immutability

>>> s1='Robert'
...            
>>> id(s1)
...            
2039784292400
>>> s1.replace('R','B')
...            
'Bobert'
>>> id(s1)
...            
2039784292400
>>> s1
...            
'Robert'

Is there a way to find the id of the object holding the value 'Bobert'?

Edit: I do know I can assign it to another variable. I was wondering if there is a default object where such values are stored and if it is possible to get the id of such objects.

S3DEV
  • 8,768
  • 3
  • 31
  • 42
Monty Swanson
  • 695
  • 16
  • 41
  • 1
    `id(s1.replace('R','B'))`? – blhsing Aug 24 '22 at 06:12
  • 1
    Yes… like this… `s2 = s1.replace(‘R’, ‘B’)` then of course you know to do `print(id(s2))` – Jarvis Aug 24 '22 at 06:12
  • 1
    This question sounds like it's rooted in some sort of misunderstanding. What are you trying to do with the ID? (Getting the ID is super straightforward, but unlikely to help you with whatever you think you need it for.) – user2357112 Aug 24 '22 at 06:12
  • Strings are immutable. Therefore, s1.replace() does **not** alter s1 in any way so its id isn't going to change – DarkKnight Aug 24 '22 at 06:14
  • `replace`: "Return a copy of the string with all occurrences of substring old replaced by new". So it returns a new object with its `id`, as you can see by @blhsing' comment. But in no sense is that a "default" object - it's just the object created and returned by the method – gimix Aug 24 '22 at 06:42

2 Answers2

3

The short answer:

There is not a 'default object' into which Python interpreter stores values (memory addresses) which have not been assigned.

But rather, the interpreter executes a statement (for example s1.replace('R', 'B')) and stores the result ('Bobert') into a memory location; which can be viewed as: id(s1.replace('R', 'B')). However, as there are no references pointing to that memory location - when garbage collection time comes - any unlinked references are deleted, thus freeing the memory for later use.

Interactive interpreters:

A special thanks to @acrobat for pointing out the use of the special underscore variable.

Note: This section only applies to interactive Python interpreters. If this logic is used in a script or program, a NameError: name '_' is not defined error will be thrown.

In regard to interactive interpreters, the returned value from the latest execution is stored into the 'special' underscore variable: _. Therefore, the return value of s1.replace('R', 'B') is stored to _, as this was the last returned (unassigned) value. However, the next call which returns a value overwrites the underscore.

For example:

# Assignment example.
a = 'Robert'
a.replace('R', 'B')

# What does _ contain?
print(_)  # >>> 'Bobert'

# Get the object type; returns 'str'
type(_)  # >>> str

# 'str' is now stored, as this was the last returned value.
print(_)  # >>> str

Worked example:

Note: The underscore logic is not considered in these examples, due to the transient nature of the special variable.

import ctypes

# Create the initial string object.
s1 = 'Robert'
s1_addr = id(s1)
print(f'{s1=} {s1_addr=}')

# Get the memory address to which the replacement points.
s2_addr = id(s1.replace('R', 'B'))
print(f'{s2_addr=}')

# Display (memory address) reference counts for each address.
s1_refs = ctypes.c_long.from_address(s1_addr).value
s2_refs = ctypes.c_long.from_address(s2_addr).value
print(f'{s1_refs=} {s2_refs=}')

Output:

s1='Robert' s1_addr=139940475739184
s2_addr=139940484802928
s1_refs=1 s2_refs=0

As you can see above, the s1 string is stored to a memory address. The replacement is also stored to a memory address. However, when it comes time to count the number of objects which point to each given address (as garbage collection will do), there are no references (objects) pointing to s2_addr, so the memory will be freed when gc is called.

Digging a little deeper:

If you refer to the bytecode for the following statements:

# Not using assignment.
s1 = "Robert"; s1.replace("R", "B")
# Using assignment.
s1 = "Robert"; s2 = s1.replace("R", "B")

You'll note the following:

>>> dis('s1 = "Robert"; s1.replace("R", "B")'

  1           0 LOAD_CONST               0 ('Robert')
              2 STORE_NAME               0 (s1)
              4 LOAD_NAME                0 (s1)
              6 LOAD_METHOD              1 (replace)
              8 LOAD_CONST               1 ('R')
             10 LOAD_CONST               2 ('B')
             12 CALL_METHOD              2       # <-- str.replace called.
                                                 # <-- No reference created.
             14 POP_TOP
             16 LOAD_CONST               3 (None)
             18 RETURN_VALUE

You'll note the str.replace method was called and thus acted upon the string, however as the STORE_NAME instruction was not called, an object reference is not created to its memory address.

In this example, note the call to STORE_NAME:

>>> dis('s1 = "Robert"; s2 = s1.replace("R", "B")')

  1           0 LOAD_CONST               0 ('Robert')
              2 STORE_NAME               0 (s1)
              4 LOAD_NAME                0 (s1)
              6 LOAD_METHOD              1 (replace)
              8 LOAD_CONST               1 ('R')
             10 LOAD_CONST               2 ('B')
             12 CALL_METHOD              2       # <-- str.replace called.
             14 STORE_NAME               2 (s2)  # <-- Object reference created here.
             16 LOAD_CONST               3 (None)
             18 RETURN_VALUE
S3DEV
  • 8,768
  • 3
  • 31
  • 42
0

Since you are not storing the replaced valued, it would be already in the garbage collection.

So, store it, and find its id.

a = 'Robert'

>>> id(a)
2554774561584

>>> b = a.replace('R', 'B')
>>> id(b)
2554774568432
Nitish
  • 392
  • 2
  • 7
  • I see. I do know I can store it in another variable and get the ID easily. I asked the question because I was wondering if there is some default object where such values are stored. Thanks – Monty Swanson Aug 24 '22 at 06:15