Создание центрального реестра, который можно распределять между модулями. - PullRequest
0 голосов
/ 07 февраля 2020

Я пытаюсь создать реестр, который можно загрузить с помощью пар имя-фабрика-метод, чтобы клиентский код мог использовать реестр для создания экземпляров этих объектов по заданным именам. Я могу заставить это работать, если я загружаю реестр парами в модуле реестра.

Однако я не могу загрузить реестр, если распределяю загрузку среди других модулей (например, с помощью заводских методов). Я бы предпочел последний вариант, так как тогда модуль реестра не должен знать обо всех возможных фабричных методах. Но я не могу заставить это работать.

Я создал простую трехмодульную версию, которая работает, а затем ту, которая не работает ниже:

Рабочая версия

registry.py

registry = {}

def register_thing(description, thingmaker):
    registry[description] = thingmaker

def get_thing(description, *args, **kwargs):
    thingmaker = registry[description]
    return thingmaker(*args, **kwargs)

def show_things():
    return registry.keys()

from things import Thing1
from things import Thing2
register_thing("Thing1", Thing1)
register_thing("Thing2", Thing2)

things.py

class Thing1(object):
      def __init__(self):
          pass
      def message(self):
          return "This is a thing"

class Thing2(object):
      def __init__(self, *args, **kwargs):
          self.args = args
          self.kwargs = kwargs
      def message(self):
          return "This is a different thing with args %r and kwargs %r" \
             % (self.args, self.kwargs)

use_things.py

import registry

print("The things in the registry are: %r" % registry.show_things())

print("Getting a Thing1")
thing = registry.get_thing("Thing1")
print("It has message %s" % thing.message())

print("Getting a Thing2")
thing = registry.get_thing("Thing2", "kite", on_string="Mothers new gown")
print("It has message %s" % thing.message())

Запуск use_things.py дает:

The things in the registry are: dict_keys(['Thing1', 'Thing2'])
Getting a Thing1
It has message This is a thing
Getting a Thing2
It has message This is a different thing with args ('kite',) and kwargs {'on_string': 'Mothers new gown'}

Сбой распределенной версии registry.py

registry = {}

def register_thing(description, thingmaker):
    registry[description] = thingmaker

def get_thing(description, *args, **kwargs):
    thingmaker = registry[description]
    return thingmaker(*args, **kwargs)

def show_things():
    return registry.keys()

things.py

import registry

class Thing1(object):
      def __init__(self):
          pass
      def message(self):
          return "This is a thing"

class Thing2(object):
      def __init__(self, *args, **kwargs):
          self.args = args
          self.kwargs = kwargs
      def message(self):
          return "This is a different thing with args %r and kwargs %r" \
             % (self.args, self.kwargs)


register.register_thing("Thing1", Thing1)
register.register_thing("Thing2", Thing2)

use_things.py (как и прежде)

Теперь, если я запускаю use_things.py, я получаю следующее:

The things in the registry are: dict_keys([])
Getting a Thing1
Traceback (most recent call last):
  File "use_things.py", line 6, in <module>
    thing = registry.get_thing("Thing1")
  File "/home/luke/scratch/registry_example/registry.py", line 7, in get_thing
    thingmaker = registry[description]
KeyError: 'Thing1'

Ясно, что модуль things.py никогда не вызывается и не заполняет реестр. Если я снова добавлю следующую строку внизу registry.py, она снова будет работать:

import things

Но опять же, для этого требуется registry.py, чтобы знать о необходимых модулях. Я бы предпочел, чтобы реестр заполнялся автоматически модулями ниже определенного каталога, но я не могу заставить это работать. Кто-нибудь может помочь?

Ответы [ 2 ]

1 голос
/ 07 февраля 2020

То, что вы описываете, в основном представляет собой «подключаемую» программную архитектуру, и существуют разные способы ее реализации. Я лично считаю, что использование пакета Python для этого является хорошим подходом, потому что это четко определенный "pythoni c" способ организации модулей, и языки поддерживают его напрямую, что немного облегчает выполнение некоторых задач. .

Вот кое-что, что я думаю, делает в основном все, что вы хотите. Он основан на моем ответе на вопрос Как импортировать элементы всех модулей в пакете? , который требует помещения всех заводских сценариев в каталог пакета, в файловой иерархии, например:

use_things.py              
things/                    
    __init__.py            
    thing1.py              
    thing2.py              

Имена скриптов пакета и фабрики могут быть легко изменены на что-то другое, если вы будете иметь sh.

Вместо явного реестра publi c, он просто использует имя пакета things в этом примере. (Однако в модуле есть частный словарь _registry, если вы чувствуете, что он действительно нужен по какой-то причине.)

Хотя пакет действительно должен быть явно импортирован, его __init__.py Сценарий инициализации будет импортировать остальные файлы в подкаталоге автоматически - поэтому добавление или удаление одного из них - это просто вопрос размещения его скрипта в подкаталоге или удаления его оттуда.

Там нет функции register_thing() в этой реализации, потому что частная функция _import_all_modules() в скрипте __init__.py эффективно делает это автоматически, но учтите, что она "автоматически регистрирует" все publi c в каждом скрипте модуля фабрики. Вы можете, конечно, изменить, как это работает, если вы хотите, чтобы это было сделано по-другому. (у меня есть пара идей, если вам интересно.)

Вот содержимое каждого из файлов, как указано выше:

use_things.py :

import things  # Import package.

print("The things in the package are: %r" % things.show_things())

print("Getting a Thing1")
thing = things.get_thing("Thing1")
print(f"It has message {thing.message()!r}")

print("Getting a Thing2")
thing = things.get_thing("Thing2", "kite", on_string="Mothers new gown")
print(f"It has message {thing.message()!r}")

things/__init__.py:

def _import_all_modules():
    """ Dynamically imports all modules in this package directory. """
    import traceback
    import os
    globals_, locals_ = globals(), locals()

    registry = {}
    # Dynamically import all the package modules in this file's directory.
    for filename in os.listdir(__name__):
        # Process all python files in directory that don't start with an underscore
        # (which also prevents this module from importing itself).
        if filename[0] != '_' and filename.split('.')[-1] in ('py', 'pyw'):
            modulename = filename.split('.')[0]  # Filename sans extension.
            package_module = '.'.join([__name__, modulename])
            try:
                module = __import__(package_module, globals_, locals_, [modulename])
            except:
                traceback.print_exc()
                raise
            for name in module.__dict__:
                if not name.startswith('_'):
                    registry[name] = module.__dict__[name]

    return registry

_registry = _import_all_modules()

def get_thing(description, *args, **kwargs):
    thingmaker = _registry[description]
    return thingmaker(*args, **kwargs)

def show_things():
    return list(_registry.keys())

things/thing1.py

class Thing1(object):
    def __init__(self):
        pass
    def message(self):
        return f'This is a {type(self).__name__}'

things/thing2.py:

class Thing2(object):
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
    def message(self):
        return (f"This is a different thing with args {self.args}"
                f" and kwargs {self.kwargs}")

Запуск use_things.py дает:

The things in the package are: ['Thing1', 'Thing2']
Getting a Thing1
It has message 'This is a Thing1'
Getting a Thing2
It has message "This is a different thing with args ('kite',) and kwargs {'on_string': 'Mothers new gown'}"
0 голосов
/ 07 февраля 2020

Примечание: Мартино в основном ответил на мой вопрос, и здесь есть изощренное. Однако у меня было небольшое дополнительное требование (в моем вопросе), но оно было не очень ясным. Я использовал ответ Мартино для создания полного ответа, и я поделился им здесь для всех, кто хочет его увидеть.

Дополнительные требования заключались в том, что я мог использовать любой factory_method (не просто класс '* 1005). * function) и что я хотел явно зарегистрировать те, которые я хотел, в моем реестре.

Так вот моя окончательная версия ...

Я использую ту же структуру каталогов, что и Martineau:

use_things.py              
things/                    
    __init__.py            
    thing1.py              
    thing2.py              

Чтобы продемонстрировать другой тип factory_method, я расширил use_things.py на пару строк:

import things  # Import package.

print("The things in the package are: %r" % things.show_things())

print("Getting a Thing1")
thing = things.get_thing("Thing1")
print(f"It has message {thing.message()!r}")

print("Getting a Thing2")
thing = things.get_thing("Thing2", "kite", on_string="Mothers new gown")
print(f"It has message {thing.message()!r}")

print("Getting a Thing2 in a net")
thing = things.get_thing("Thing2_in_net", "kite", on_string="Mothers new gown")
print(f"It has message {thing.message()!r}")

Обратите внимание, что получение Thing2_in_net создает объект типа Thing2, но с некоторыми предварительными вычислениями.

thing1.py теперь явно регистрирует конструктор Thing1 (__init__), объявляя кортеж с именем, начинающимся с _register_<something>. Другой класс UnregisteredThing не зарегистрирован.

class Thing1(object):
    def __init__(self):
        pass
    def message(self):
        return f'This is a {type(self).__name__}'

_register_thing1 = ('Thing1', Thing1)

class UnregisteredThing(object):
    def __init__(self):
        pass
    def message(self):
        return f'This is an unregistered thing'

И thing2.py регистрирует двух создателей, один из которых является базовым c конструктором Thing2 и один из фабричного метода:

class Thing2(object):
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
    def message(self):
        return (f"This is a different thing with args {self.args}"
                f" and kwargs {self.kwargs}")

def build_thing2_in_net(*args, **kwargs):
    return Thing2(*args, located='in net', **kwargs)


_register_thing2 = ('Thing2', Thing2)
_register_thing2_in_net = ('Thing2_in_net', build_thing2_in_net)

Наконец, сценарий __init__.py, модифицированный так, чтобы специально искать атрибуты модуля с именем _register_<something>, будет обрабатывать их как пару ключ / создатель для регистрации:

def build_registry():
    """ Dynamically imports all modules in this package directory. """
    import traceback
    import os
    globals_, locals_ = globals(), locals()
    registry = {}
    for filename in os.listdir(__name__):
        # Process all python files in directory that don't start with an underscore
        # (which also prevents this module from importing itself).
        if filename[0] != '_' and filename.split('.')[-1] in ('py', 'pyw'):
            modulename = filename.split('.')[0]  # Filename sans extension.
            package_module = '.'.join([__name__, modulename])
            try:
                module = __import__(
                    package_module, globals_, locals_, [modulename])
            except:
                traceback.print_exc()
                raise
            for name in module.__dict__:
                ## look for attributes of module starting in _register_
                if name.startswith('_register_'):
                    # if so assume they are key-maker pair and register them
                    key, maker = module.__dict__[name]
                    registry[key] = maker
    return registry

_registry = build_registry()

def get_thing(description, *args, **kwargs):
    thingmaker = _registry[description]
    return thingmaker(*args, **kwargs)

def show_things():
    return list(_registry.keys())

Полученный результат показывает, что только зарегистрированные вещи появляются в реестре, и это может быть любой метод, который создает объект:

The things in the package are: ['Thing2', 'Thing2_in_net', 'Thing1']
Getting a Thing1
It has message 'This is a Thing1'
Getting a Thing2
It has message "This is a different thing with args ('kite',) and kwargs {'on_string': 'Mothers new gown'}"
Getting a Thing2 in a net
It has message "This is a different thing with args ('kite',) and kwargs {'located': 'in net', 'on_string': 'Mothers new gown'}"
...