How to make a python objects self or attribute not get assigned to the referencer object when it is references?

974 views Asked by At

I want when a object is referenced by another, the refernecer's self object or one of it's attributes to be different at the referencer object

This is what I want to do:

class MyClass:
    .
    .
    .
    .

a = MyClass()
b = a
print(b is a) #must print false
print(b == a) #must print true

#or
a = MyClass()
b = a
print(b.attr is a.attr) #must print false
print(b.attr == a.attr) #must print true

How can I achieve this, normally the when an assignment is made like a = b, b is a reference to a, any help would be appreciated, I want b to be a copy/deepcopy of a, same for the attribute

Thanks from now for the people who will answer the question

Note: I’m using CPython (the official implemention of Python) version Python 3.8

I'm open for using dark magic

3

There are 3 answers

0
theEpsilon On

You shouldn't try to do anything with overloading assignment as that's not very pythonic. Either use the deepcopy function or make a copy "constructor". Then override the __eq__ function so that the two variables test equal.

0
The Matt On

Overloading an assignment operator in the way you are suggesting is not very pythonic.

The assignment operator in Python is meant to refer to the same variable, and not to create a copy.

So, such an object may not behave as expected in some situations, such as the results of hash(object.attr) or from using the pickle module on such an object.

However, if you are up for some dark magic...


To be clear, I am providing this answer just to show Python does offer the ability to do such things.


One approach would be to use the __getattribute__() function to create a copy of any attribute when it is accessed.

import copy

class MyClass:
    def __init__(self):
        self.attr = ["foo", "bar"]
        
    def __getattribute__(self, name):
        """Access an attribute of this class"""
        attr = object.__getattribute__(self, name)
        return copy.copy(attr)

a = MyClass()
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True

More information on __getattribute__() can be found here: https://docs.python.org/3/reference/datamodel.html


Alternatively, property() could be used to do this only for a single attribute on a class.

class MyClass:
def __init__(self):
    self.__attr = ["foo", "bar"]

@property
def attr(self):
    """Get the attribute."""
    # Copy could be used instead
    # This works too, if we assume the attribute supports slicing
    return self.__attr[:]

@attr.setter
def attr(self, value):
    """Setting the attribute."""
    # Makes assignment work on this attribute
    # a.attr = []
    # a.attr is a.attr (False)
    self.__attr = value

a = MyClass()
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True

These solutions work for most object types. But this will actually fail under some situations. This is because some strings and some integers will be marked as having the same identity.

Such as 4 is 2*2 which will be True, but

a = -6
b = -6
print(a is b) # Will print False

This is called "interning" and is discussed briefly in the sys module: https://docs.python.org/3/library/sys.html?highlight=intern#sys.intern

According to Real Python: https://realpython.com/lessons/intern-objects/

in CPython 3.7, integers between -5 and 256 are interned, as with strings that are less than 20 characters and contain only ASCII letters, digits, or underscores.

For example, if attr = 5 or attr = 'foo' then both approaches above fail.

a = MyClass()
a.attr = 5
b = a
print(b.attr is a.attr) # prints True
print(b.attr == a.attr) # prints True

This can be circumvented by wrapping these types in a subclass. According to RealPython, only strings and some integers need to be modified. The catch with this approach would be a type comparison would fail:

print(type(b.attr) is type(a.attr)) # prints False

So if you wanted to wrap the objects, making sure the is operation always fails, you could do this:

import copy
from collections import UserString # Because of course Python has this built in

class UserInt(int): pass

class MyClass:
    def __init__(self, attr = ["foo", "bar"]):
        self.attr = attr
        
    def __getattribute__(self, name):
        """Access an attribute of this class"""
        attr = object.__getattribute__(self, name)

        if isinstance(attr, int) and -5 <= attr <= 256:
            return UserInt(attr) # Prevent a.attr is b.attr if this is an int
        elif isinstance(attr, str):
            return UserString(attr) # Prevent a.attr is b.attr for strings
        #else
        return copy.copy(attr)

a = MyClass()
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True

a.attr = 7
b = a
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True

a.attr = "Hello"
print(b.attr is a.attr) # prints False
print(b.attr == a.attr) # prints True
1
Timur U On

may use for ex: copy.copy and copy.deepcopy...

for python3:

print(MyClass() == MyClass())
print(MyClass() is MyClass())

outs:

False
False
>>> 

and other shaman methods will be analogue ...