diff --git a/pykat/components.py b/pykat/components.py index dbd95a23875dc33af6e3dc2931864868df92df51..a55a09ef30321f2ca9a8476b15a33b4da4a864b8 100644 --- a/pykat/components.py +++ b/pykat/components.py @@ -84,6 +84,8 @@ class NodeGaussSetter(object): def qy(self, value): self.__node().setGauss(self.__comp(), self.qx, complex(value)) +id___ = 0 + class Component(object): __metaclass__ = abc.ABCMeta @@ -91,7 +93,13 @@ class Component(object): # This creates an instance specific class for the component # this enables us to add properties to instances rather than # all classes - return object.__new__(type(cls.__name__, (cls,), {}), *args, **kwargs) + global id___ + id___ += 1 + cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id___)) + + cnew = type(cnew_name, (cls,), {}) + + return object.__new__(cnew, *args, **kwargs) def __init__(self, name=None): @@ -116,10 +124,11 @@ class Component(object): When deep copying a kat object we need to take into account the instance specific properties. """ - result = self.__class__.__new__(self.__class__) - result.__dict__ = copy.deepcopy(self.__dict__, memo) - result.__update_node_setters + # Here we create a copy of this object based of the base class + # of this one, otherwise we're making a copy of a copy of a copy... + result = self.__class__.__new__(self.__class__.__base__) + result.__dict__ = copy.deepcopy(self.__dict__, memo) return result @@ -163,13 +172,19 @@ class Component(object): # need to remove them. This function should get called if the nodes # are updated, either by some function call or the GUI key_rm = [k for k in self.__dict__ if k.startswith("__nodesetter_", 0, 13)] - + # now we have a list of which to remove for key in key_rm: ns = self.__dict__[key] - delattr(self, '__nodesetter_' + ns.node.name) - delattr(self.__class__, ns.node.name) + name = str(ns.node.name) + + if '__nodesetter_' + name in self.__dict__: + delattr(self, '__nodesetter_' + name) + if name in self.__class__.__dict__: + delattr(self.__class__, name) + + # Now re-add them pointing to the recent nodes for node in self.nodes: if type(node) != pykat.node_network.DumpNode: ns = NodeGaussSetter(self, node) @@ -180,9 +195,12 @@ class Component(object): if not isinstance(ns, NodeGaussSetter): raise exceptions.ValueError("Argument is not of type NodeGaussSetter") - name = ns.node.name + name = str(ns.node.name) fget = lambda self: self.__get_node_setter(name) + if name == "nITM1": + print(self.__class__) + setattr(self.__class__, name, property(fget)) setattr(self, '__nodesetter_' + name, ns) diff --git a/pykat/finesse.py b/pykat/finesse.py index 9b469143a7d0e826a413f8c9f7b7491d1cccac8c..287a815f48a4b212708e9ce6a7a465e56910ff8f 100644 --- a/pykat/finesse.py +++ b/pykat/finesse.py @@ -416,7 +416,9 @@ class Block: def name(self): return self.__name Constant = namedtuple('Constant', 'name, value, usedBy') - + +id___ = 0 + class kat(object): def __new__(cls, *args, **kwargs): @@ -428,7 +430,10 @@ class kat(object): # kat objects share the same class definition they also have the # same properties regardless of whether they have the actual # object added to it. So we create an instance specific class. - return object.__new__(type(pykat.finesse.kat.__name__, (pykat.finesse.kat,), {}), *args, **kwargs) + global id___ + id___ += 1 + cnew = type(pykat.finesse.kat.__name__ + str("_") + str(id___), (pykat.finesse.kat,), {}) + return object.__new__(cnew, *args, **kwargs) def __init__(self, kat_file=None, kat_code=None, katdir="", katname="", tempdir=None, tempname=None): self.scene = None # scene object for GUI @@ -477,17 +482,32 @@ class kat(object): self.loadKatFile(kat_file) def __deepcopy__(self, memo): - cls = self.__class__ - result = cls.__new__(cls) + """ + When deep copying a kat object we need to take into account + the instance specific properties. This is because when + the kat object adds new components it also adds properties for + each of these. There properties are unique to each kat object, + but properties are part of the class definition. Thus if two + kat objects share the same class definition they also have the + same properties regardless of whether they have the actual + object added to it. So we create an instance specific class. + """ + result = self.__class__.__new__(self.__class__) memo[id(self)] = result - - for k, v in self.__dict__.items(): - setattr(result, k, copy.deepcopy(v, memo)) - + result.__dict__ = copy.deepcopy(self.__dict__, memo) + + # Find all properties in class we are copying + # and deep copy these to the new class instance + for x in self.__class__.__dict__.items(): + if isinstance(x[1], property): + setattr(result.__class__, x[0], x[1]) + + result.nodes._NodeNetwork__update_nodes_properties() + # Update any weakrefs for c in result.components: result.components[c]._Component__update_node_setters() - + return result @property diff --git a/pykat/node_network.py b/pykat/node_network.py index f75c055e2871700a8ff864d72c0b90c328f5481b..2500812d48665b21e6dec1ee5040fb6d384e626d 100644 --- a/pykat/node_network.py +++ b/pykat/node_network.py @@ -22,13 +22,21 @@ from pykat.detectors import BaseDetector as Detector from pykat.optics.gaussian_beams import beam_param from copy import deepcopy +id___ = 0 + class NodeNetwork(object): - + def __new__(cls, *args, **kwargs): - # This creates an instance specific class for the component - # this enables us to add properties to instances rather than - # all classes - return object.__new__(type(cls.__name__, (cls,), {}), *args, **kwargs) + # This creates an instance specific class for the component + # this enables us to add properties to instances rather than + # all classes + global id___ + id___ += 1 + cnew_name = str("%s.%s_%i" % (cls.__module__, cls.__name__, id___)) + + cnew = type(cnew_name, (cls,), {}) + + return object.__new__(cnew, *args, **kwargs) def __init__(self, kat): self.__nodes = {} @@ -37,7 +45,20 @@ class NodeNetwork(object): self.__componentNodes = {} # dictionary of tuples containing which nodes are connected to a given component self.__componentCallback = {} self.__node_id = 1 - + + def __deepcopy__(self, memo): + """ + When deep copying a kat object we need to take into account + the instance specific properties. + """ + + # Here we create a copy of this object based of the base class + # of this one, otherwise we're making a copy of a copy of a copy... + result = self.__class__.__new__(self.__class__.__base__) + result.__dict__ = deepcopy(self.__dict__, memo) + + return result + @property def kat(self): return self.__kat @@ -151,7 +172,29 @@ class NodeNetwork(object): self.__nodeComponents[node.id] = tuple(l) if do_callback: self.__componentCallback[comp.id]() - + + def __update_nodes_properties(self): + # check if any node setters have already been added. If so we + # need to remove them. This function should get called if the nodes + # are updated, either by some function call or the GUI + key_rm = [k for k in self.__dict__ if k.startswith("__node_", 0, 7)] + + # now we have a list of which to remove + for key in key_rm: + ns = self.__dict__[key] + name = str(ns.name) + + if '__node_' + name in self.__dict__: + delattr(self, '__node_' + name) + + if name in self.__class__.__dict__: + delattr(self.__class__, name) + + # Now re-add them pointing to the recent nodes + for node in self.__nodes: + if not self.__nodes[node].isDump: + self.__add_node_attr(self.__nodes[node]) + def createNode(self, node_name): """ This creates a new node object. It won't be connected to anything or added to a @@ -285,6 +328,7 @@ class NodeNetwork(object): kat.nodes.replaceNode(kat.bs1, "n1", kat.nodes.createNode("test1")) name = node.name + delattr(self, '__node_' + name) delattr(self.__class__, name) diff --git a/test/test_deepcopying_references.py b/test/test_deepcopying_references.py new file mode 100644 index 0000000000000000000000000000000000000000..04ad0996e1c3376ac622f4782977ce6317b84631 --- /dev/null +++ b/test/test_deepcopying_references.py @@ -0,0 +1,46 @@ +""" +Test file to ensure that references between deep copied objects are handled properly +""" + +import pykat +from copy import deepcopy + +kat0 = pykat.finesse.kat() + +kat0.parseCommands("m m1 1 0 0 n0 n1") + +kat1 = deepcopy(kat0) + +assert(kat0 != kat1) +assert(kat0.__class__ != kat1.__class__) + +assert(kat0.m1 != kat1.m1) +assert(kat0.m1.__class__ != kat1.m1.__class__) + +assert(kat0.m1.n0 != kat1.m1.n0) +assert(kat0.m1.n0.node != kat1.m1.n0.node) +assert(kat0.nodes.n0 != kat1.nodes.n0) + +new = kat1.nodes.createNode("n4") + +kat1.nodes.replaceNode(kat1.m1, kat1.m1.n0, new) + +assert(not hasattr(kat1.nodes, "n0")) +assert(hasattr(kat1.nodes, "n4")) + +assert(hasattr(kat0.nodes, "n0")) +assert(not hasattr(kat0.nodes, "n4")) + +assert(hasattr(kat1.m1, "n4")) +assert(not hasattr(kat1.m1, "n0")) + +assert(hasattr(kat0.m1, "n0")) +assert(not hasattr(kat0.m1, "n4")) + +assert(kat0.nodes.n0 == kat0.m1.n0.node) +assert(kat0.nodes.n1 == kat0.m1.n1.node) + +assert(kat1.nodes.n4 == kat1.m1.n4.node) +assert(kat1.nodes.n1 == kat1.m1.n1.node) + +print("PASSED") \ No newline at end of file