Выбрать главу

Более надежным способом было бы использование создание программы, управляемой данными, т.е. кода, который анализировал бы форму и члены инициализатора, опрашивал бы определения классов об их членах, а затем согласовывал бы оба набора.

Программисты, работающие с Lisp и Java, называют данную методику интроспекцией (introspection). В некоторых других объектно-ориентированных языках она называется программированием метаклассов (metaclass hacking) и, как правило, считается "черной магией", понятной только "посвященным". В большинстве объектно- ориентированных языков данная методика не поддерживается вообще, а в тех языках, где она поддерживается (среди них Perl и Java), она часто сложна и ненадежна. Однако в языке Python средства интроспекции и программирования метаклассов исключительно доступны.

В примере 9.3 приведен фрагмент кода приблизительно со строки 1895 в версии 1.43.

Пример 9.3. Код метакласса copy_instance

def copy_instance(toclass, fromdict):

#Make a class object of given type from a conformant dictionary.

 class_sig = toclass.__dict__.keys(); class_sig.sort()

 dict_keys = fromdict.keys(); dict_keys.sort()

 common = set_intersection(class_sig, dict_keys)

 if 'typemap' in class_sig:

  class_sig.remove('typemap')

 if tuple(class_sig) != tuple(dict_keys):

  print "Conformability error"

#print "Class signature: " + `class_sig`

#print "Dictionary keys: " + `dict_keys`

  print "Not matched in class signature: "+ \

   `set_diff(class_sig, common)`

  print "Not matched in dictionary keys: "+ \

   `set_diff(dict_keys, common)`

  sys.exit(1)

 else:

  for x in dict_keys:

   setattr(toclass, x, fromdict[x])

Большую часть в примере представляет код контроля ошибок, учитывая возможность того, что члены класса и генерация отчета --configdump выпали из синхронизации. Такая проверка гарантирует, что в случае возникновения сбой в коде будет обнаружен на ранней стадии, т.е. реализуется правило исправности. Главной частью кода являются две последние строки, которые устанавливают атрибуты в классе из соответствующих членов в словаре. Данные строки эквивалентны следующим строкам.

def copy_instance(toclass, fromdict):

 for x in fromdict.keys():

  setattr(toclass, x, fromdict[x])

Если разрабатываемый код настолько же прост, то весьма вероятно, что он верен. В примере 9.4 приведен код, вызывающий данный метакласс.

Ключевым моментом в данном коде является то, что он проходит 3 уровня инициализатора (конфигурация/сервер/пользователь), устанавливая корректные объекты каждого уровня в списки, содержащиеся в следующем объекте более высокого уровня. Поскольку метакласс copy_instance управляется данными и является полностью общим, его можно использовать во всех 3 уровнях для 3 различных типов объектов.

Данный пример — пример новой школы. Язык Python был создан гораздо позднее 1990 года. Однако пример отражает идеи, которые возвращаются к Unix-традициям 1969 года. Если бы размышления над Unix-программированием, практикуемым предшественниками, не научили бы автора "конструктивной лени" — настаивая на повторном использовании кода и отказе от написания дублирующегося связующего кода в соответствии с правилом SPOT — он мог бы "удариться" в программирование синтаксического анализатора на языке Python. Главное понимание того, что сама программа fetchmail могла бы превратиться в синтаксический анализатор конфигурации fetchmailconf, возможно, никогда бы не пришло.

Пример 9.4. Вызывающий контекст для copy_instance

#Сложная часть - инициализация объектов из глобального класса

#`configuration'.

#`Configuration' - верхний уровень объектного дерева,

#который планируется изменить

Configuration = Controls()

copy_instance(Configuration, configuration)

Configuration.servers = [];

for server in configuration['servers']:

 Newsite = Server()

 copy_instance(Newsite, server)

 Configuration.servers.append(Newsite)

 Newsite.users = [];

 for user in server['users']:

  Newuser = User()

  copy_instance(Newuser, user)

  Newsite.users.append(Newuser)

Другое понимание (того, что метакласс copy_instance может быть общим) происходит из Unix-традиции старательного поиска способов избежать кодирования вручную. Но особенно, Unix-программисты привыкли к написанию спецификаций для генерации синтаксических анализаторов для обработки языков разметки. Это скоро привело к предположению, что остальная часть работы может быть выполнена путем некоторого общего обхода дерева конфигурационной структуры. Для четкого разрешения задачи проектирования необходимо было два отдельных (один над другим) этапа создания программы, управляемой данными.