Search code examples
pythonjoininstancepersistentchain

combine class instance into another instance in python?


I am in designing one "jigsaw puzzle" like tool to manage different water pipe like parts combination for fun.

  1. I have different single parts type with different purpose(cooler, heater, purifier...)
  2. Those parts with different interfaces size can be connected with each other (1/4 inch. 1/6 inch ....)

I want those parts can be stored in database and can be combined into a total new parts combination(randomly or purposely), but still can be considering as a function-able part.

Here is the initial thinking

class MetaInfo():
    def __init__(self, name, intype,outtype,shape,serialno):
        this.name = name
        this.intype = intype
        this.outtype = outtype
        this.shape = shape
        this.sn = serialno


def parts():
    def __init__(self, meta):
        this.meta = meta

def linkwith(self, part):
    if part.meta.shape == this.meta.shape: 
    # make it simple, logical here is just same shape can be connected each other 
        return ???         # a new parts combination 
    else:
        raise Error 

m1 = MetaInfo("cooler", "hotwater", "coldwater", "1/4 inch round", "SN:11111" )
m2 = MetaInfo("heater", "coldwater", "hotwater", "1/4 inch round", "SN:22222" )
m3 = MetaInfo("purifier", "coldwater", "hotwater", "1/6 inch round", "SN:33333" )


a = parts(m1)
b = parts(m2)
c = parts(m3)

Here is what I need your help:

  1. how to save m1, m2, m3 as a list which can persistent in a human readable database, next time only change that database itself I can add meta?

  2. how to chain different parts as a new combination? Such as

    e = a.linkwith(b)
    d = c.linkwith(a)
    

    and store it in that database as well?

  3. can I make a long chain, make a new parts instance , such as

    f = c.linkwith(a,b,d,e)
    

and findout easily which part is incapable to link in this chain, here part c with different size?

Many thanks.


Solution

  • I got bored. It's very rough but it works. If you take this far enough, you will want to use a database; but I understand wanting to use a human readable format.

    from copy import copy
    import csv
    
    class Part_Base(object):
        pass
    
    class MultiPart_Base(list):
        pass
    
    class part_meta(type):
        part_names = {}
        parts      = []
        def __init__(cls, cls_name, cls_bases, cls_dict):
            super(part_meta, cls).__init__(cls_name, cls_bases, cls_dict)
            if(not Part_Base in cls_bases):
                part_meta.part_names[cls_name] = cls
    
        def __call__(self, *args, **kwargs):
            name = kwargs.get("name", "")
            if(part_meta.part_names.has_key(name) and not (self is part_meta.part_names[name])):
                obj = part_meta.part_names[name].__call__(*args, **kwargs)
            else:
                obj = None
                if(not part_meta.part_names.has_key(self.__name__)):
                    new_class = part_meta(name, (Generic_Part,), {})
                    globals()[name] = new_class
                    obj = new_class(*args, **kwargs)
                else:
                    obj = super(part_meta, self).__call__(*args, **kwargs)
            if not obj in part_meta.parts:
                part_meta.parts.append(obj)
            return obj
    
        @classmethod
        def save(cls):
            all_fields = list(reduce(lambda x, y: x | set(y.fields), cls.parts, set([])))
            with open("parts.csv", "w") as file_h:
                writer = csv.DictWriter\
                (
                    file_h,
                    all_fields,
                    restval        = "",
                    extrasaction   = "ignore",
                    dialect        = "excel",
                    lineterminator = "\n",
    
                )
                writer.writeheader()
                for part in cls.parts:
                    writer.writerow({field : getattr(part, field) for field in part.fields})
    
        @classmethod
        def load(cls):
            with open("parts.csv", "r") as file_h:
                reader = csv.DictReader(file_h)
                for row in reader:
                    Part(**row)
    
    class Part(Part_Base):
        __metaclass__ = part_meta
        fields        = []
        def __init__(self, **kwargs):
            for name, value in kwargs.items():
                setattr(self, name, value)
            self.fields += kwargs.keys()
    
        def __repr__(self):
            return "<%s>" % self.description
    
        @property
        def description(self):           
            return "%s: %s %s %s %s" % (self.name, self.intype, self.outtype, self.shape, self.serialno)
    
        def linkwith(self, *parts):
            return Generic_MultiPart(self, *parts)
    
    class Generic_Part(Part):
        def __init__(self, **kwargs):
            kwargs["name"] = self.__class__.__name__
            super(Generic_Part, self).__init__(**kwargs)
    
    class Generic_MultiPart(MultiPart_Base):
        def __init__(self, *parts):
            super(Generic_MultiPart, self).__init__()
            if len(parts) >= 2:
                self.shape = parts[0].shape
                self.linkwith(*parts)
            else:
                raise ValueError("Not enough parts")
    
        def __repr__(self):
            return "<MultiPart: %s>" % super(Generic_MultiPart, self).__repr__()
    
        def linkwith(self, *parts):
            for part in parts:
                if part.shape == self.shape:
                    if isinstance(part, Part):
                        self.append(part)
                    elif isinstance(part, MultiPart_Base):
                        self.extend(part)
                else:
                    raise ValueError("Incompatible parts")
            return self
    
    class cooler(Generic_Part):
        intype  = "hotwater"
        outtype = "coldwater"
        fields  = ["intype", "outtype"]
    
    class heater(Generic_Part):
        intype  = "coldwater"
        outtype = "hotwater"
        fields  = ["intype", "outtype"]
    
    def make_some_parts():
        some_parts = \
        [
            # This is actually a cooler object
            # The metaclass uses the cooler class from above
            # to create the object
            Part
            (
                name     = "cooler",
                shape    = "1/4 inch round",
                serialno = "SN:11111"
            ),
            # Using the heater class directly
            heater
            (
                shape    = "1/4 inch round",
                serialno = "SN:22222"
            ),
            Part
            (
                name     = "purifier",
                intype   = "coldwater",
                outtype  = "hotwater",
                shape    = "1/6 inch round",
                serialno = "SN:33333"
            ),
            Part
            (
                name     = "carbon_filter",
                intype   = "coldwater",
                outtype  = "coldwater",
                shape    = "1/4 inch round",
                serialno = "SN:33333"
            )
        ]
    
        useless_part = some_parts[0].linkwith(some_parts[1])
        print useless_part
        filter_part  = copy(useless_part).linkwith(some_parts[3])
        print filter_part
    
        part_meta.save()
    
    def load_some_parts():
        part_meta.load()
        print part_meta.parts
    

    You can manually edit parts.csv (in Excel or other) and it will make the parts described. The save/restore functionality hasn't been extended to MultiParts; you can do that.