Je conçois actuellement un logiciel qui doit gérer une certaine configuration matérielle.

La configuration matérielle est la suivante:

System design

Système - Le système contient deux périphériques identiques et possède certaines fonctionnalités par rapport à l'ensemble du système.

Périphérique - Chaque périphérique contient deux sous-périphériques identiques et possède certaines fonctionnalités par rapport aux deux sous-périphériques.

Sous-périphérique - Chaque sous-périphérique a 4 entités configurables (contrôlées via la même commande matérielle - donc je ne les compte pas comme un sous-sous-périphérique).

Ce que je veux réaliser:

Je veux contrôler toutes les entités configurables via le gestionnaire de système (les entités sont comptées de manière série), ce qui signifie que je serais en mesure de faire ce qui suit:

system_instance = system_manager_class(some_params)
system_instance.some_func(0) # configure device_manager[0].sub_device_manager[0].entity[0]
system_instance.some_func(5) # configure device_manager[0].sub_device_manager[1].entity[1]
system_instance.some_func(8) # configure device_manager[1].sub_device_manager[1].entity[0]

Ce que j'ai pensé faire:

Je pensais créer une classe abstraite, qui contient toutes les fonctions de sous-périphérique (avec un appel à une fonction de conversion) et dont le system_manager, device_manager et sub_device_manager en hériteraient. Ainsi, toutes les classes auront le même nom de fonction et je pourrai y accéder via le gestionnaire de système. Quelque chose autour de ces lignes:

class abs_sub_device():
    @staticmethod
    def convert_entity(self):
        sub_manager = None
        sub_entity_num = None
        pass

    def set_entity_to_2(entity_num):
        sub_manager, sub_manager_entity_num = self.convert_entity(entity_num)
        sub_manager.some_func(sub_manager_entity_num)


class system_manager(abs_sub_device):
    def __init__(self):
        self.device_manager_list = [] # Initiliaze device list
        self.device_manager_list.append(device_manager())
        self.device_manager_list.append(device_manager())

    def convert_entity(self, entity_num):
        relevant_device_manager = self.device_manager_list[entity_num // 4]
        relevant_entity         = entity_num % 4
        return relevant_device_manage, relevant_entity

class device_manager(abs_sub_device):
    def __init__(self):
        self.sub_device_manager_list = [] # Initiliaze sub device list
        self.sub_device_manager_list.append(sub_device_manager())
        self.sub_device_manager_list.append(sub_device_manager())        

    def convert_entity(self, entity_num):
        relevant_sub_device_manager = self.sub_device_manager_list[entity_num // 4]
        relevant_entity         = entity_num % 4
        return relevant_sub_device_manager, relevant_entity

class sub_device_manager(abs_sub_device):
    def __init__(self): 
        self.entity_list = [0] * 4

    def set_entity_to_2(self, entity_num):
        self.entity_list[entity_num] = 2
  • Le code est pour une compréhension générique de ma conception, pas pour une fonctionnalité réelle.

Le problème:

Il me semble que le système que j'essaie de concevoir est vraiment générique et qu'il doit y avoir une manière python intégrée de le faire, ou que tout mon regard orienté objet est faux.

J'aimerais vraiment savoir si quelqu'un a une meilleure façon de procéder.

10
Rohi 12 avril 2018 à 19:02

5 réponses

Meilleure réponse

Après mûre réflexion, je pense avoir trouvé un moyen assez générique de résoudre le problème, en utilisant une combinaison de décorateurs, d'héritage et de création de fonctions dynamiques.

L'idée principale est la suivante:

1) Chaque couche crée dynamiquement toutes les fonctions pertinentes de la sous-couche pour elle-même (à l'intérieur de la fonction init, en utilisant un décorateur sur la fonction init)

2) Chaque fonction créée convertit dynamiquement la valeur de l'entité selon une fonction de conversion (qui est une fonction statique de la classe abs_container_class) et appelle la fonction de couche inférieure du même nom (voir make_convert_function_method).

3) Cela entraîne essentiellement la mise en œuvre de toutes les fonctions de sous-couche au niveau supérieur avec une duplication de code nulle.

def get_relevant_class_method_list(class_instance):
    method_list = [func for func in dir(class_instance) if callable(getattr(class_instance, func)) and not func.startswith("__") and not func.startswith("_")]
    return method_list

def make_convert_function_method(name):
    def _method(self, entity_num, *args):
        sub_manager, sub_manager_entity_num = self._convert_entity(entity_num)
        function_to_call = getattr(sub_manager, name)
        function_to_call(sub_manager_entity_num, *args)        
    return _method


def container_class_init_decorator(function_object):
    def new_init_function(self, *args):
        # Call the init function :
        function_object(self, *args)
        # Get all relevant methods (Of one sub class is enough)
        method_list = get_relevant_class_method_list(self.container_list[0])
        # Dynamically create all sub layer functions :
        for method_name in method_list:
            _method = make_convert_function_method(method_name)
            setattr(type(self), method_name, _method)

    return new_init_function


class abs_container_class():
    @staticmethod
    def _convert_entity(self):
        sub_manager = None
        sub_entity_num = None
        pass

class system_manager(abs_container_class):
    @container_class_init_decorator
    def __init__(self):
        self.device_manager_list = [] # Initiliaze device list
        self.device_manager_list.append(device_manager())
        self.device_manager_list.append(device_manager())
        self.container_list = self.device_manager_list

    def _convert_entity(self, entity_num):
        relevant_device_manager = self.device_manager_list[entity_num // 4]
        relevant_entity         = entity_num % 4
        return relevant_device_manager, relevant_entity

class device_manager(abs_container_class):
    @container_class_init_decorator
    def __init__(self):
        self.sub_device_manager_list = [] # Initiliaze sub device list
        self.sub_device_manager_list.append(sub_device_manager())
        self.sub_device_manager_list.append(sub_device_manager())    
        self.container_list = self.sub_device_manager_list

    def _convert_entity(self, entity_num):
        relevant_sub_device_manager = self.sub_device_manager_list[entity_num // 4]
        relevant_entity         = entity_num % 4
        return relevant_sub_device_manager, relevant_entity

class sub_device_manager():
    def __init__(self): 
        self.entity_list = [0] * 4

    def set_entity_to_value(self, entity_num, required_value):
        self.entity_list[entity_num] = required_value
        print("I set the entity to : {}".format(required_value))

# This is used for auto completion purposes (Using pep convention)
class auto_complete_class(system_manager, device_manager, sub_device_manager):
    pass


system_instance = system_manager() # type: auto_complete_class
system_instance.set_entity_to_value(0, 3)

Il y a toujours un petit problème avec cette solution, la complétion automatique ne fonctionnerait pas car la classe de plus haut niveau n'a presque pas de fonction implémentée statique. Afin de résoudre ce problème, j'ai un peu triché, j'ai créé une classe vide qui a hérité de toutes les couches et déclaré à l'EDI en utilisant la convention pep que c'est le type de l'instance en cours de création (# type: auto_complete_class).

5
Rohi 16 avril 2018 à 10:23

La façon dont je le vois si vous souhaitez configurer dynamiquement différentes parties du système, vous avez besoin d'une sorte d'adressage, donc si vous entrez un ID ou une adresse avec un paramètre, le système saura avec l'adresse sur quel sous-système vous parlez, puis configurez cela système avec paramètre.

La POO est tout à fait correcte pour cela et vous pouvez ensuite facilement manipuler ces données via les opérateurs au niveau du bit.

L'adressage de base est donc effectué via un système binaire, donc pour cela en python, vous devez d'abord implémenter un attribut statique d'adresse à votre classe avec peut-être quelques détails de base si le système se développe.

L'implémentation de base des systèmes d'adresse est la suivante:

bin(71)
1010 1011
and if we divide it into nibbles 
1010 - device manager 10
1011 - sub device manager 11

Donc, dans cet exemple, nous avons un système de 15 gestionnaires de périphériques et 15 gestionnaires de sous-périphériques, et chaque gestionnaire de périphériques et de sous-périphériques a son adresse entière. Vous auriez besoin de leur adresse qui est en binaire 71 et vous iriez avec:

system.config(address, parameter )

system.config funcion ressemblerait à ceci:

def config(self,address, parameter):  
    device_manager = (address&0xF0)>>4 #10  
    sub_device_manager = address&0xf  # 11  
    if device_manager not in range(self.devices): raise LookupError("device manager not found")
    if sub_device_manager not in range(self.devices[device_manager].device): raise LookupError("sub device manager not found")

    self.devices[device_manager].device[sub_device_manager].implement(parameter)  

En profane, vous diriez au système que le sous-périphérique 11 du périphérique 10 doit être configuré avec ce paramètre .

Alors, à quoi ressemblerait cette configuration dans la classe d'héritage python d'une classe de base du système qui pourrait ensuite être composée / héritée en différentes classes:

class systems(object):

    parent = None #global parent element, defaults to None well for simplicity

    def __init__(self):
        self.addrMASK = 0xf # address mask for that nibble
        self.addr = 0x1 # default address of that element
        self.devices = [] # list of instances of device 
        self.data = {  #some arbitrary data
            "param1":"param_val",
            "param2":"param_val",
            "param3":"param_val",
        }


    def addSubSystem(self,sub_system): # connects elements to eachother

        # checks for valiability
        if not isinstance(sub_system,systems):
            raise TypeError("defined input is not a system type") # to prevent passing an integer or something

        # appends a device to system data 
        self.devices.append(sub_system)

        # search parent variables from sub device manager to system
        obj = self
        while 1:
            if obj.parent is not None:
                obj.parent.addrMASK<<=4 #bitshifts 4 bits
                obj.parent.addr <<=4 #bitshifts 4 bits
                obj = obj.parent
            else:break

        #self management , i am lazy guy so i added this part so i wouldn't have to reset addresses manualy
        self.addrMASK <<=4 #bitshifts 4 bits
        self.addr <<=4 #bitshifts 4 bits

        # this element is added so the obj address is coresponding to place in list, this could be done more eloquently but i didn't know what are your limitations
        if not self.devices:
            self.devices[ len(self.devices)-1 ].addr +=1        
        self.devices[ len(self.devices)-1 ].parent = self

    # helpful for checking data ... gives the address of system
    def __repr__(self):

        return "system at {0:X}, {1:0X}".format(self.addr,self.addrMASK)

    # extra helpful lists data as well
    def __str__(self):
        data = [ '{} : {}\n'.format(k,v) for k,v in self.data.items() ]
        return " ".join([ repr(self),'\n',*data ])

    #checking for data, skips looping over sub systems
    def __contains__(self,system_index):

        return system_index-1 in range(len(self.data))

    # applying parameter change  -- just an example
    def apply(self,par_dict):
        if not isinstance(par_dict,dict): 
            raise TypeError("parameter must be a dict type")
        if any( key in self.data.keys() for key in par_dict.keys() ):

            for k,v in par_dict.items():
                if k in self.data.keys():
                    self.data[k]=v
                else:pass

        else:pass

    # implementing parameters trough addresses
    def implement(self,address,parameter_dictionary):

        if address&self.addrMASK==self.addr:

            if address-self.addr!=0:
                item = (address-self.addr)>>4
                self.devices[item-1].implement( address-self.addr,parameter_dictionary )
            else:
                self.apply(parameter_dictionary)






a = systems()
b = systems()
a.addSubSystem(b)
c = systems()
b.addSubSystem(c)

print('a')
print(a)
print('')

print('b')
print(b)
print('')

print('c')
print(c)
print('')

a.implement(0x100,{"param1":"a"})
a.implement(0x110,{"param1":"b"})
a.implement(0x111,{"param1":"c"})

print('a')
print(a)
print('')

print('b')
print(b)
print('')

print('c')
print(c)
print('')
1
Danilo 22 avril 2018 à 08:30

Est-ce que cela résout votre problème?

class EndDevice:
    def __init__(self, entities_num):
        self.entities = list(range(entities_num))

    @property
    def count_entities(self):
        return len(self.entities)

    def get_entity(self, i):
        return str(i)


class Device:
    def __init__(self, sub_devices):
        self.sub_devices = sub_devices

    @property
    def count_entities(self):
        return sum(sd.count_entities for sd in self.sub_devices)

    def get_entity(self, i):
        c = 0
        for index, sd in enumerate(self.sub_devices):
            if c <= i < sd.count_entities + c:
                return str(index) + " " + sd.get_entity(i - c)
            c += sd.count_entities
        raise IndexError(i)


SystemManager = Device # Are the exact same. This also means you can stack that infinite

sub_devices1 = [EndDevice(4) for _ in range(2)]
sub_devices2 = [EndDevice(4) for _ in range(2)]
system_manager = SystemManager([Device(sub_devices1), Device(sub_devices2)])

print(system_manager.get_entity(0))
print(system_manager.get_entity(5))
print(system_manager.get_entity(15))
4
MegaIng 12 avril 2018 à 16:30

Je ne peux pas penser à une meilleure façon de faire cela que la POO, mais l'héritage ne vous donnera qu'un ensemble de fonctions de bas niveau pour le gestionnaire de système, donc ce sera comme avoir un gestionnaire de périphériques et un gestionnaire de sous-périphériques. Une meilleure chose à faire sera, un peu comme les widgets tkinter, d'avoir un gestionnaire système et d'initialiser tous les autres gestionnaires comme des enfants dans une arborescence, donc:

system = SystemManager()
device1 = DeviceManager(system)
subDevice1 = SubDeviceManager(device1)
device2 = DeviceManager(system)
subDevice2 = SubDeviceManager(device2)

#to execute some_func on subDevice1
system.some_func(0, 0, *someParams)

Nous pouvons le faire en conservant une liste des «enfants» des cadres supérieurs et en ayant des fonctions qui référencent les enfants.

class SystemManager:
  def __init__(self):
    self.children = []
  def some_func(self, child, *params):
    self.children[child].some_func(*params)

class DeviceManager:
  def __init__(self, parent):
    parent.children.append(self)
    self.children = []
  def some_func(self, child, *params):
    self.children[child].some_func(*params)

class SubDeviceManager:
  def __init__(self, parent):
    parent.children.append(self)
    #this may or may not have sub-objects, if it does we need to make it its own children list.
  def some_func(self, *params):
    #do some important stuff

Malheureusement, cela signifie que si nous voulons appeler une fonction d'un gestionnaire de sous-périphérique à partir du gestionnaire système sans avoir beaucoup de points, nous devrons la définir à nouveau dans le gestionnaire système. Ce que vous pouvez faire à la place est d'utiliser la fonction intégrée exec(), qui prendra une entrée de chaîne et l'exécutera à l'aide de l'interpréteur Python:

class SystemManager:
  ...
  def execute(self, child, function, *args):
    exec("self.children[child]."+function+"(*args)")

(et gardez le gestionnaire de périphériques identique)

Vous écririez alors dans le programme principal:

system.execute(0, "some_func", 0, *someArgs)

Qui appellerait

device1.some_func(0, someArgs)
2
DarthVlader 15 avril 2018 à 13:51

Voici ce que je pense:

SystemManager().apply_to_entity(entity_num=7, lambda e: e.value = 2)
class EntitySuperManagerMixin():
    """Mixin to handle logic for managing entity managers."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)  # Supports any kind of __init__ call.
        self._entity_manager_list = []

    def apply_to_entity(self, entity_num, action):
        relevant_entity_manager = self._entity_manager_list[index // 4]
        relevant_entity_num = index % 4
        return relevant_entity_manager.apply_to_entity(
            relevant_entity_num, action)


class SystemManager(EntitySuperManagerMixin):

    def __init__(self):
        super().__init__()
        # An alias for _entity_manager_list to improve readability.
        self.device_manager_list = self._entity_manager_list
        self.device_manager_list.extend(DeviceManager() for _ in range(4))


class DeviceManager(EntitySuperManagerMixin):

    def __init__(self):
        super().__init__()
        # An alias for _entity_manager_list to improve readability.
        self.sub_device_manager_list = self._entity_manager_list
        self.sub_device_manager_list.extend(SubDeviceManager() for _ in range(4))


class SubDeviceManager():
    """Manages entities, not entity managers, thus doesn't inherit the mixin."""

    def __init__(self):
        # Entities need to be classes for this idea to work.
        self._entity_list = [Entity() for _ in range(4)]

    def apply_to_entity(self, entity_num, action):
        return action(self._entity_list[entity_num])


class Entity():
    def __init__(self, initial_value=0):
        self.value = initial_value

Avec cette structure:

  • Les fonctions spécifiques à l'entité peuvent rester liées à la classe Entity (à laquelle elle appartient).
  • Le code spécifique au gestionnaire doit être mis à jour à deux endroits: EntitySuperManagerMixin et le gestionnaire de niveau le plus bas (qui aurait de toute façon besoin d'un comportement personnalisé car il traite des entités réelles, pas des autres gestionnaires).
2
Brian Rodriguez 16 avril 2018 à 16:37