Add ability to use `id(obj)` to get `obj`

now i see what you all want to say. python by design is not pointer friendly and emulating pointer behavior is the only way of using them.

thanks @Nineteendo for your explanation.

i had an idea of this:

class Parent(Human):
    is_parent: bool = True
    @classmethod
    def from_child(self, child):
        ...
class Child(Human):
    is_parent: bool = False
    def grow_up(self):
        self = Parent.from_child(self)

right now this doesn’t work because self as a variable in local scope will be replaced, not the instance of Child

maybe i am the problem and this is not the correct way to do it, tell me if i’m wrong.

To do what exactly? What are you intending the result of that code to be? You can do it right now, it’s just that as you say, the effect is just to assign to a local variable called self, which won’t have any visible effect elsewhere in the program.

The question is what other behavior elsewhere in the program is supposed to depend on “replacing the object” here, and how. As others have noted, most external bits of code won’t expect an object to change its identity out from under them. And for code that does know about and is ready to handle such a change, there’s no clear reason to do it this way. It all depends on what from_child does and how other code accesses these objects.

For instance, if that other code is using isinstance to check if the object is a Parent or Child, it can just. . . not do that. Instead it could check the is_parent variable that your code already includes. In your example, the class carries no functionality or data apart from this boolean saying whether it’s a parent or child, which is redundant with the class name. So why do we need two classes for Parent and Child when we could instead just have a Human class with an is_parent boolean? What is the gain from structuring the code in this way?

It would be helpful if you can provide a real example that actually accomplishes some task and show how accomplishing that task becomes easier when you can “replace the object” instead of using other means.

i read your message three times and i just don’t understand it, mostly because english is not my first language and a little because you are using long sentences and i just can’t comprehend that.

but overall i think you are saying that this behavior can be achieved by making is_parent a part of Human class or a new subclass (so that we don’t modify Human class).

you are correct, my example was bad. but if you look at the general picture it is just objects morphing from one type to another.

instead of doing this (even though it’s still not enough!)

class SpecialThing:
    ...
class Egg:
    def __init__(self):
        print("egg created")
    def hatch(self):
        new_self = SpecialThing()
        for key in globals():
            if self is globals()[key]:
                globals()[key] = new_self

we would do

class SpecialThing:
    ...
class Egg:
    def __init__(self):
        print("egg created")
    def hatch(self):
        print("egg hatched")
        self = SpecialThing()
        # or
        # objects()[id(self)] = SpecialThing()

what you are referring to does not align with id

id is not always a memory address. one implementation can use it to track number of objects.
first user defined object having id 0, second having id 1 and etc.

Even in C++, this kind of in-place type morphing would be considered an unnecessary misuse of pointers, better replaced with making hatch() just return the SomethingSpecial().

The main use for writing to a pointer in C/C++ is to work around the fact that you can only return one thing from a function so you’d use writeable pointer inputs as outputs:

void divmod(int x, int y, int *div, int *mod) {
  *div = x / y;
  *mod = x - (*div) * y;
}

void main() {
  int div;
  int mod;
  divmod(20, 7, &div, &mod);
  printf("divmod(20, 7) = (%i, %i)\n", div, mod);
}

But in Python, we can just return a tuple so this use case doesn’t exist.

Another common use case is to avoid having to transfer memory ownership across functions. Again Python doesn’t have this problem because we have reference counting.

4 Likes

fair.

how about globals() and locals()?
if they are useful, objects() is just another kind of them.

You’ve just given us a reason not to implement objects().

1 Like

Although it’s pretty nasty and I wouldn’t really recommend it, this is
one possibility:

class Child(Human):
def grow_up(self):
self.class = Parent

There’s nothing inherently wrong with dynamically changing the class of an object as long as care is taken to ensure that the classes involved share compatible layouts and attributes.

I consider this pattern an efficient and elegant solution to transitioning the state of an object to another so it can alter multiple behaviors in a class-controlled manner, as opposed to creating a new instance of a target class based on a transitioning object (inefficient), or using a state attribute in a bunch of conditional statements to control behaviors (inelegant).

Static type checkers can’t deal with objects that change type dynamically though so the practice is better reserved for non-exposed APIs.

1 Like

That’s not necessarily the best way to do that (though I can’t think of any other one-liner rn), but it works, and iirc some typing features even use(d) this.

Hmm, this looks a lot like C code :slight_smile::

>>> class SpecialThing:
...     pass
...
>>> class Egg:
...     def __init__(self):
...         print("egg created")
...     @staticmethod
...     def hatch(node):
...         print("egg hatched")
...         target, key = node
...         target[key] = SpecialThing() # *eggp = ...
...
>>> egg = Egg()
egg created
>>> node = locals(), "egg" # eggp = &egg
>>> Egg.hatch(node) # hatch(eggp)
egg hatched
>>> egg
<__main__.SpecialThing object at 0x10325c590>

Based on the code examples this is a case of let’s propose a unsafe language feature to avoid patterns that solve this safely

There’s multiple patterns for solving this without messing with pointers

In all cases the concepts need to be decomposed

For example for a person thats able to grow up ita important what growing up us considered

Like legal contract rights vs vehicle lincense compliance (for example big motorbikes in Germany

The general solution is to move the behavior set one wants to change to a policy object and using that

Some more wild hacks involve abusing dunder class as policy object

I think this topic belongs more in the Python Help category than the Idea category. The proposed (very vague) idea cannot be implemented because it is not compatible with the Python data model. This is a learning issue.

6 Likes

This is an idea, i don’t know what makes it a help request, i nearly know every concept in python (other than some builtin libs).

The help category is “General help/discussion forum”. The ideas category is generally for plausible changes to Python. Although the line is a bit subjective, the consensus here is that you are trying to propose something that has zero chance of entering the language and may reflect a misunderstanding of some key concepts.

Okay, but you’ve provided examples that have not convinced people that changing the language is better than changing your code. Understanding Python’s data model does not imply a good working knowledge of design patterns that work well in the language.

1 Like

You’ve presented these two alternatives as if they would do the same thing. They won’t, and it might be instructive to play around with them to see why.

I think the main concept you are missing is how Python variables and memory are very different than C variables and memory.

In C, int varname = 17; makes varname a shorthand for a memory address. The value of varname is the bits stored at that address, interpreted as an int. Initially, the bits for 17 are stored at that address.

In Python, varname: int = 17 makes varname a name that can refer to an object somewhere in memory. In this case, it refers to the int object 17. The type is inherent in the value, not in the variable. In fact, varname = "seventeen" is then a valid thing to do, it makes varname refer to a string.

In C, p = &varname gives you a pointer to the memory location that varname is short for. That is, p is the memory address for varname. Then *p = 23 stores the bits for 23 at that address, changing the value of varname. Now varname equals 23.

In Python, p = id(varname) gives you the id (In CPython, the memory address) of the object that varname refers to. It does not tell you anything about where the varname variable is. There is no way in Python to create a reference to a variable, only to an object. There is nothing you can do with p that will change what varname means.

This is how variables are different in C and Python. One way to look at it: it’s not that Python has no pointers, it’s that in Python everything is a pointer. There’s no need to create another way to make references, because every value is already reached through a reference.

This gets at the question you haven’t yet answered: “Wherever you would store the id for later, why not just store the object?” Storing the object is storing a reference (a pointer), so you don’t need to fiddle with ids.

7 Likes

I think the Pythonic way of doing is to just give your mapping an actual name:

things = {}

@dataclass
class Egg:
  name: str

  def hatch(self, things: dict[str, Creature]):
    new_self = SpecialCreature(self.name)
    assert things[self.name] is self
    things[self.name] = new_self

Essentially, self.name supplants id and things supplants globals().

Well, it breaks type safety and asserted invariants, so I would avoid it.

1 Like

Ok, so (again), do U want to mutate the object into another?, or do U want to change where do all references point to? In your examples U are calling types, which creates new objects – new identities. Does overwriteing identity creates a shallow copy and destroys the previous representation (like C++'s std::move)? Or maybe call to id on such object is illegal? Or maybe it returns a tuple of it’s identities?

I still don’t know, what the idea is.

This is actually a pattern that is useful (if used with _extreme_ care, and I mean it!) when using multiple sub-interpreters.

It is possible to have an object from Interpreter 1 to be used on Interpreter N by instantiating it by ID like that (althought last I tried, ctypes wasn’t loading on subinterpreters - I had not tried it recently - I made a 2-liner C function for the same code) .

(And this is all I have to say on the subject - I do not support the O.P. here at all - even with this use case for retrieving an object from its Id in cpython: if done casually it will simply crash the runtime.
One have to ensure the “resurected” object in the other interpreter is de-referenced there while still alive in the interpreter it was created)