How to deep-copy a class object in Python3 - python

GIVEN:
class A:
x = 4711
B = COPY(A)
setattr(B, "x", "0815")
print("A: %s; B: %s;" % (A.x, B.x))
GOAL:
An operation COPY such that the code fragment above results in
A: 4711; B: 0815;
IN PLAIN WORDS:
By what means can a class object be deep-copied, so that it is totally isolated from its original. Using copy.deepcopy() delivers
A: 0185; B: 0185;
so that is not the solution.

from copy import deepcopy
class A:
x = 123
def __init__(self):
self.f()
def f(self):
print("original function", self.x)
def g(self):
print("replacement function", self.x)
B = deepcopy(A)
B.x = 456
B.f = g
a = A()
b = B()
This example prints:
replacement function 456
replacement function 456
Apparently, both A as well as B share the same values for their attributes x and f. Therefore, as you have already noted, copy.deepcopy doesn't work to "copy" a class object. It seems that Python class objects are singletons, because A is deepcopy(A) is True.
So, alternatively, you could just use inheritance instead of copying:
class A:
x = 123
def __init__(self):
self.f()
def f(self):
print("original function", self.x)
def g(self):
print("replacement function", self.x)
class B(A):
pass
B.x = 456
B.f = g
a = A()
b = B()
Which prints:
original function 123
replacement function 456
Like this, we are able to change B.x and B.f without affecting A.x and A.f.
However, isinstance(b, A) will be True, which might be undesired. Also, changes to class attributes of A will propagate to its child B. Therefore, you just change your original A into a dummy A_, first, and then derive both A and B from that:
class A:
x = 123
def __init__(self):
self.f()
def f(self):
print("original function", self.x)
def g(self):
print("replacement function", self.x)
A_ = A
class A(A_):
pass
class B(A_):
pass
B.x = 456
B.f = g
a = A()
b = B()
Now, isinstance(b, A) will be False and changes to class attributes of A will not propagate to B.

Related

How to get the class from which a method was called?

The get_calling_class function must pass the following tests by returning the class of the method that called the A.f method:
class A:
def f(self): return get_calling_class()
class B(A):
def g(self): return self.f()
class C(B):
def h(self): return self.f()
c = C()
assert c.g() == B
assert c.h() == C
Walking the stack should give the answer.
The answer should ideally be, in the caller's stack frame.
The problem is, the stack frames only record the function
names (like so: 'f', 'g', 'h', etc.) Any information about
classes is lost. Trying to reverse-engineer the lost info,
by navigating the class hierarchy (in parallel with the
stack frame), did not get me very far, and got complicated.
So, here is a different approach:
Inject the class info into the stack frame
(e.g. with local variables),
and read that, from the called function.
import inspect
class A:
def f(self):
frame = inspect.currentframe()
callerFrame = frame.f_back
callerLocals = callerFrame.f_locals
return callerLocals['cls']
class B(A):
def g(self):
cls = B
return self.f()
def f(self):
cls = B
return super().f()
class C(B):
def h(self):
cls = C
return super(B, self).f()
def f(self):
cls = C
return super().f()
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
Related:
get-fully-qualified-method-name-from-inspect-stack
Without modifying the definition of subclasses:
Added an "external" decorator, to wrap class methods.
(At least as a temporary solution.)
import inspect
class Injector:
def __init__(self, nameStr, valueStr):
self.nameStr = nameStr
self.valueStr = valueStr
# Should inject directly in f's local scope / stack frame.
# As is, it just adds another stack frame on top of f.
def injectInLocals(self, f):
def decorate(*args, **kwargs):
exec(f'{self.nameStr} = {self.valueStr}')
return f(*args, **kwargs)
return decorate
class A:
def f(self):
frame = inspect.currentframe()
callerDecoratorFrame = frame.f_back.f_back # Note:twice
callerDecoratorLocals = callerDecoratorFrame.f_locals
return callerDecoratorLocals['cls']
class B(A):
def g(self): return self.f()
def f(self): return super().f()
class C(B):
def h(self): return super(B, self).f()
def f(self): return super().f()
bInjector = Injector('cls', B.__name__)
B.g = bInjector.injectInLocals(B.g)
B.f = bInjector.injectInLocals(B.f)
cInjector = Injector('cls', C.__name__)
C.h = cInjector.injectInLocals(C.h)
C.f = cInjector.injectInLocals(C.f)
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
I found this link very interesting
(but didn't take advantage of metaclasses here):
what-are-metaclasses-in-python
Maybe someone could even replace the function definitions*,
with functions whose code is a duplicate of the original;
but with added locals/information, directly in their scope.
*
Maybe after the class definitions have completed;
maybe during class creation (using a metaclass).

parent class self.__class__.__name__ prints name of child class

I have 2 classes: a and b. 'b' inherits from 'a'. How do i get class 'a' to print it's actual name? In the case below, it prints the name of the child class.
class a():
def __init__(self):
print("a -> " + self.__class__.__name__)
class b(a):
def __init__(self):
super().__init__()
print("b -> " + self.__class__.__name__)
bo = b()
prints:
a -> b
b -> b
You can use the __class__ variable (not attribute) available in all methods defined in a class block. So:
In [1]: class a():
...: def __init__(self):
...: print("a -> " + __class__.__name__)
...:
...: class b(a):
...: def __init__(self):
...: super().__init__()
...: print("b -> " + __class__.__name__)
...:
...:
In [2]: b()
a -> a
b -> b
Out[2]: <__main__.b at 0x10ce853a0>
This is part of the Python data model and it was added to allow the no-arg form of super() that became available in Python 3.
__class__ is an implicit closure reference created by the compiler
if any methods in a class body refer to either __class__ or super.
This allows the zero argument form of super() to correctly identify
the class being defined based on lexical scoping, while the class or
instance that was used to make the current call is identified based on
the first argument passed to the method.
So note, this won't be available for dynamically added methods:
In [4]: a.a_method = a_method
In [5]: b().a_method()
a -> a
b -> b
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-5-5887c7d36b49> in <module>
----> 1 b().a_method()
<ipython-input-3-daf632bde4e5> in a_method(self)
1 def a_method(self):
----> 2 print(__class__)
3
NameError: name '__class__' is not defined
The problem is that when you create bo, it's initialized as an object of type b. This means that for bo, the self variable whether it's used in the a class or the b class, will be of type b.
One alternative is to call .__name__ on the class itself rather than on self:
class a():
def __init__(self):
print("a -> " + a.__name__)
class b(a):
def __init__(self):
super().__init__()
print("b -> " + b.__name__)
bo = b()
>>> a -> a
>>> b -> b
However, at that point, you might as well hardcode the print statements.

How to access objects from a different class?

There are three classes :
A, B and C
The __init__ of B creates an object of A. Using the mutators, I will be able to change the attributes of A from B for the instance created.
However, I am not unable to find any way to use that instance of A created by B to be used in C without passing the Object explicitly to the __init__ method [ not C.__init(self, object: A) ]
Is there any way to implicitly allow C to use that instance of A ?
I am new to python and not sure if this a valid question. I have looked at other sources where it explicitly passes the object to class C
class A:
def __init__(self):
x = []
y = []
class C :
def __init__(self):
#[get obj1 without passing the instance in init]
self.value = None
def method1():
self.value = len([]) #len(obj1 of A.x)
class B:
def __init__(self):
obj1 = A()
obj1.x = [1,2,3,4]
obj1.y = [1,2,3]
obj2 = B()
print(obj2.value) #this should be the length of x in the instance A created above
Here is a simple example:
class A:
def __init__(self, i = ""):
self.item = i
class B:
def __init__(self):
self.a = A("hello")
class C:
def __init__(self):
b = B()
print(b.a.item)
c = C()
Output:
hello
Let's say we have classes A and B:
class A:
def hello_world(self):
print("hello world")
class B:
def __init__(self):
self.a = A()
def hello_world(self):
self.a.hello_world()
You create an instance of class B (which will create an instance of class A inside):
b = B()
You can then pass a reference to either b or b.a to any function of an instance of class C (either a constructor or not)
class C:
def hello_world(self, a):
a.hello_world()
c = C()
c.hello_world(b.a)
You can also use global variables:
class C:
def hello_world(self):
b.a.hello_world()
c = C()
c.hello_world()
Here the instances of class C will rely on variable b to be in place and just use its a attribute.
Using global variables in classes is generally considered to be hard to maintain and a bad practice. If your class depends on a value or an instance of some class you should pass the reference in the constructor (__init__ function) or in the function that's using it.
If these classes are in different different python files then you can also use these classes by importing the class name and creating an object of that class:
eg:
file1.py
class A:
def __init__(self):
x = []
y = []
file2.py
from file1 import A
class C :
def __init__(self):
[get obj1 without passing the instance in init]
self.value = None
self.obj_a = A()
def xyz(self):
print "in class c"
file3.py
from file2 import C
from file1 import A
Class B:
def __init__(self):
self.obj_a = A()
self.obj_c = C()
def another_func(self):
print self.obj_c.xyz()# it will print "in class c"

Accessing higher-level class from nested class in Python

Suppose I have two Python classes, A and B, and that B is an attribute of A. Can a method of B modify a property of A? for example, I would like to be able to call
A.B.setXinA(1)
A.x
>>> 1
One way around it would be embed a reference to A in B:
A.B.reftoA = A
But that's rather ugly... Is there a way to access the higher-level class directly? Below is a working example using the second method:
class A:
def __init__(self, b):
b.parent = self
setattr(self, b.name, b)
class B:
def __init__(self, name):
self.name = name
b = B('abc')
a = A(b) # b is now a.abc
abc.parent.x = 1
a.x
>>> 1
What about a method in B like this:
class B:
def __init__(self, name):
self.name = name
def setXinA(self, x):
self.parent.x = x
Then:
>>> b = B('abc')
>>> a = A(b)
>>> b.setXinA(19)
>>> print(A.x)
19
This way requires that setXinA is called by an instance of B rather than just B.setXinA(42) for example. Also, it sets x as an attribue of the class A, rather than any particular instance of A.

Python object conversion

Assume that we have an object k of type class A. We defined a second class B(A). What is the best practice to "convert" object k to class B and preserve all data in k?
This does the "class conversion" but it is subject to collateral damage. Creating another object and replacing its __dict__ as BrainCore posted would be safer - but this code does what you asked, with no new object being created.
class A(object):
pass
class B(A):
def __add__(self, other):
return self.value + other
a = A()
a.value = 5
a.__class__ = B
print a + 10
a = A() # parent class
b = B() # subclass
b.value = 3 # random setting of values
a.__dict__ = b.__dict__ # give object a b's values
# now proceed to use object a
Would this satisfy your use case? Note: Only the instance variables of b will be accessible from object a, not class B's class variables. Also, modifying variables in a will modify the variable in b, unless you do a deepcopy:
import copy
a.__dict__ = copy.deepcopy(b.__dict__)
class A:
def __init__(self, a, b):
self.a = a
self.b = b
class B(A):
def __init__(self, parent_instance, c):
# initiate the parent class with all the arguments coming from
# parent class __dict__
super().__init__(*tuple(parent_instance.__dict__.values()))
self.c = c
a_instance = A(1, 2)
b_instance = B(a_instance, 7)
print(b_instance.a + b_instance.b + b_instance.c)
>> 10
Or you could have a sperate function for this:
def class_converter(convert_to, parent_instance):
return convert_to(*tuple(parent_instance.__dict__.values()))
class B(A):
def __init__(self, *args):
super().__init__(*args)
self.c = 5
But using the 2nd method, I wasn't able to figure out how to pass additional values

Categories