0

I have an object:

c = Character(...)

I convert it to a string by using:

p = "{0}".format(c)
print(p)
 
>>> <Character.Character object at 0x000002267ED6DA50>

How do i get the object back so i can run this code?

p.get_name()
Code Pal
  • 68
  • 8
  • 3
    This has a bit of an XY feel ... please update the question to explain the use-case. – S3DEV Sep 28 '22 at 15:05
  • It depends a *lot* on what's in the `Character` class definition. To do the exact thing you're asking you would need to modify the class such that its `__str__` returns a serialized representation of the class, and add a function that constructs an equivalent class instance based on that string. But if you can at all avoid having to serialize your object, and just keep the original `c`, do that instead, because it's much easier. Serialization only becomes necessary when you're transferring an object across processes (e.g. between a backend and frontend via HTTP). – Samwise Sep 28 '22 at 15:05
  • @Samwise so there's no way to cast a string back to a '''Character'''? like i have the memory adress where its stored aswell – Code Pal Sep 28 '22 at 15:12
  • @CodePal I literally just answered that question and the tl;dr is "yes but it might be complicated". :) If you want a more specific answer you need to include the `Character` class definition in your question. The memory address doesn't help you in the general case because the actual object is likely to have been GCed once you lose the reference to it. (You can avoid that by being very careful to keep the reference -- but then why not just use the reference?) – Samwise Sep 28 '22 at 15:14
  • If the Character class had a __repr__ defined, you might be able to create a new instance. – Terry Spotts Sep 28 '22 at 15:18
  • @g.d.d.c just proved you wrong mate T_T – Code Pal Sep 28 '22 at 15:26
  • @CodePal they actually didn't... I'm going to go ahead and post an answer to demonstrate before you try to use this in a situation that's gonna crash your app :P – Samwise Sep 28 '22 at 15:46

3 Answers3

1

You absolutely can if you are using CPython (where the id is the memory address). Different implementations may not work the same way.

>>> import ctypes
>>> class A:
...   pass
... 
>>> a = A()
>>> id(a)
140669136944864
>>> b = ctypes.cast(id(a), ctypes.py_object).value
>>> b
<__main__.A object at 0x7ff015f03ee0>
>>> a is b
True

So we've de-referenced the id of a back into a py_object and snagged its value.

g.d.d.c
  • 46,865
  • 9
  • 101
  • 111
  • While this specific example works, this strategy will crash the CPython interpreter if `a` has gone out of scope at the time you try to dereference its ID. – Samwise Sep 28 '22 at 15:39
  • Added an answer illustrating how this will crash your app and/or cause incredibly bizarre bugs. – Samwise Sep 28 '22 at 15:59
0

If your main goal is to serialize and deserialize objects. (ie. turn objects into string and back while preserving all the data and functions) your can use pickle. You can use pickle.dumps to convert any object into a string and pickle.loads to convert it back into an object. docs

>>> import pickle
>>> class Student:
...     def __init__(self, name, age):
...             self.name = name
...             self.age  = age
... 
>>> a = Student("name", 20)
>>> pickle.dumps(a)
b'\x80\x04\x951\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x07Student\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94h\x05\x8c\x03age\x94K\x14ub.'
>>> s = pickle.dumps(a)
>>> b = pickle.loads(s)
>>> b
<__main__.Student object at 0x7f04a856c910>
>>> b.name == a.name and b.age == a.age
True
0

It is not possible in the general case to use the ID embedded in the default __str__ implementation to retrieve an object.

Borrowing from g.d.d.c's answer, let's define a function to do this:

import ctypes

def get_object_by_id(obj_id):
    return ctypes.cast(obj_id, ctypes.py_object).value

def get_object_by_repr(obj_repr):
    return get_object_by_id(int(obj_repr[-19:-1], 16))

It works for any object that is still in scope, provided that it's using the default __repr__/__str__ implementation that includes the hex-encoded id at the end of the string:

>>> class A:
...     pass
...
>>> a = A()
>>> r = str(a)
>>> r
'<__main__.A object at 0x000001C584E8BC10>'
>>> get_object_by_repr(r)
<__main__.A object at 0x000001C584E8BC10>

But what if our original A has gone out of scope?

>>> def get_a_repr():
...     a = A()
...     return str(a)
...
>>> r = get_a_repr()
>>> get_object_by_repr(r)

(crash)
(and I don't mean an uncaught exception, I mean Python itself crashes)

You don't necessarily need to define a in a function to do this; it also can happen if you just rebind a in the local scope (note: GC isn't necessarily guaranteed to happen as soon as you rebind the variable, so this may not behave 100% deterministically, but the below is a real example):

>>> a = A()
>>> r = str(a)
>>> get_object_by_repr(r)
<__main__.A object at 0x000001C73C73BBE0>
>>> a = A()
>>> get_object_by_repr(r)
<__main__.A object at 0x000001C73C73BBE0>
>>> a
<__main__.A object at 0x000001C73C73B9A0>
>>> get_object_by_repr(r)

(crash)

I'd expect this to also happen if you passed this string between processes, stored it in a file for later use by the same script, or any of the other things you'd normally be doing with a serialized object.

The reason this happens is that unlike C, Python garbage-collects objects that have gone out of scope and which do not have any references -- and the id value itself (which is just an int), or the string representation of the object that has the id embedded in it, is not recognized by the interpreter as a live reference! And because ctypes lets you reach right into the guts of the interpreter (usually a bad idea if you don't know exactly what you're doing), you're telling it to dereference a pointer to freed memory, and it crashes.

In other situations, you might actually get the far more insidious bug of getting a different object because that memory address has since been repurposed to hold something else (I'm not sure how likely this is).

To actually solve the problem of turning a str() representation into the original object, the object must be serialized, i.e. turned into a string that contains all the data needed to reconstruct an exact copy of the object, even if the original object no longer exists. How to do this depends entirely on the actual content of the object, but a pretty standard (language-agnostic) solution is to make the class JSON-serializable; check out How to make a class JSON serializable.

Samwise
  • 68,105
  • 3
  • 30
  • 44
  • THANKS SO MUCH FOR THE IN DEBT EXPLANATION – Code Pal Sep 29 '22 at 08:01
  • If I understand correctly the main problem happens when the object you trying to retrieve is no longer in the scope, and even worst instead of giving an error it fully crashes python. Then I have one more question: shouldn't Python be made aware of this? u managed to find a series of built in command that consistently causes Python to crash instead of returning an exception. – Code Pal Sep 29 '22 at 08:13
  • When you're using `ctypes` you're executing more or less arbitrary code (it's meant for calling into C/C++ DLLs and that kind of thing), so crashing the interpreter is always a risk. As I said, don't use it if you don't know exactly what you're doing. – Samwise Sep 29 '22 at 13:59
  • If you access an object via an actual native Python reference (i.e. a variable name) you will *never* have this problem because Python keeps track of all of those. Crashing the interpreter is only a risk if you try to "cheat" by taking an underlying memory address that you've stored without its knowledge (which as demonstrated may or may not be valid at the time you access it) and then using `ctypes` to dereference it directly. – Samwise Sep 29 '22 at 14:06