Pull to refresh

«Наследование» не от классов

Reading time5 min
Views4.9K
image
В Питоне совсем не важно, что вы поместите в определение класса. Это могут быть строки, числа, объекты, переменные. В третьем Питоне можно даже передавать именованные аргументы.

Реализация


Сначала необходимо разочаровать: в Питоне нельзя наследовать не от классов, т.е. нельзя передавать конструктору классов type параметр bases, в котором есть неклассы. Однако нигде не оговорено, что вы будете включать в определение класса.
Ничего сложно в реализации кода выше нет, для этого просто потребуется создать метакласс, в котором будут отфильтровываться неклассы из bases (которые можно дальше использовать по своему усмотрению). Думаю, пример все объяснит, copy&run:
Copy Source | Copy HTML
  1. # coding: utf-8
  2.  
  3. class Metaclass(type):
  4.  
  5.     '''<br/>    Metaclass печатает все объекты, от которых наследует класс<br/>    и которые не являеются классами.<br/>    '''
  6.  
  7.     def __new__(cls, name, bases, dict):
  8.         import inspect
  9.  
  10.         new_bases = []
  11.         for base in bases:
  12.             # отфильтруем классы,
  13.             # остальное просто напечатаем
  14.             if inspect.isclass(base):
  15.                 new_bases.append(base)
  16.             else:
  17.                 print base
  18.  
  19.         return type.__new__(cls, name, tuple(new_bases), dict)
  20.  
  21.  
  22. class Print(object):
  23.  
  24.     __metaclass__ = Metaclass
  25.  
  26.  
  27. class String(Print, 'Programming', 'is', 'all', 'about', 'architecture.', ''):
  28.     pass
  29.  
  30. class Numbers(Print,  0, 1, 2.718, 3.1459, ''):
  31.     pass
  32.  
  33. class More(Print, None, True, 'or', False, ['to be', 'or', 'not to be'], ''):
  34.     pass
  35.  
  36. the_end = 'The end.'
  37. class End(Print, the_end):
  38.     pass

Использование именованных аргументов в определении класса в третьем Питоне, copy&run:
Copy Source | Copy HTML
  1. # Python 3+
  2.  
  3. def metaclass(name, bases, dict, private=True):
  4.     print('Private:', private)
  5.     return type(name, bases, dict)
  6.  
  7.  
  8. class MyClass(object, metaclass=metaclass, private=False):
  9.  
  10.     pass


Практическая целесообразность


В большинстве случаев эта возможность вообще не требуется. Более того, код становится сложным и «магическим», что совсем не приветствуется. Однако в некоторых случаях она оказывается незаменима, как и метаклассы в целом. По сути, можно выделить два преимущества:
  1. Управление метаклассами и
  2. Упрощение API.

Управление конструкцией классов (и вообще метаклассами)

Передача параметров для метакласса непосредственно в определении класса. Конечно, можно передать те же самые параметры через атрибуты обычного класса, но это засоряет класс и не всегда выглядит читабельно. Например, пусть необходимо наследовать либо полный бокал, либо пустой (пример притянут за уши, простого и понятного не придумал, а взять из готового кода не получилось). Copy&run:
Copy Source | Copy HTML
  1. #coding: utf-8
  2.  
  3. class EmptyGlass: pass
  4. class FullGlass: pass
  5.  
  6.  
  7. class GlassMetaclass(type):
  8.  
  9.     '''<br/>    GlassMetaclass создает либо класс EmptyGlass, либо FullGlass,<br/>    в зависимости от того, было ли указано True или False в определении<br/>    класса.<br/>    '''
  10.  
  11.     empty_glass = EmptyGlass
  12.     full_glass = FullGlass
  13.  
  14.     def __new__(cls, name, bases, dict):
  15.         if bases[-1] is True: # is True, потому что нам надо именно объект True
  16.             # полный бокал
  17.             bases = [cls.full_glass] + list(bases[:-1])
  18.         elif bases[-1] is False:
  19.             # пустой
  20.             bases = [cls.empty_glass] + list(bases[:-1])
  21.  
  22.         return super(GlassMetaclass, cls).__new__(cls, name, tuple(bases), dict)
  23.  
  24.  
  25. class Glass(object):
  26.  
  27.     __metaclass__ = GlassMetaclass
  28.  
  29.  
  30. if __name__ == '__main__':
  31.     class MyGlass(Glass, True): pass # полный стакан
  32.     class YourGlass(Glass, False): pass # пустой стакан
  33.  
  34.     print issubclass(MyGlass, FullGlass) # True
  35.     print issubclass(YourGlass, EmptyGlass) # True 

Упрощение API

Допустим, нам требуется создать дерево из классов (не структуру данных, а просто иерархию). Пусть это будет иерархические регулярные выражения. Каждому классу в этом случае требуется передать строку, которая будет скомпилирована в регулярное выражение. В классическом виде этом будет выглядеть примерно так:
Copy Source | Copy HTML
  1. class Music(MyRe):
  2.  
  3.     pattern = '/music'
  4.  
  5.     class Artist(MyRe):
  6.  
  7.         pattern = '/artist/\d+'
  8.  
  9.     class Song(MyRe):
  10.  
  11.         pattern = '/song/\d+'
  12.  
  13.     class Album(MyRe):
  14.  
  15.         pattern = '/album/\d+'

Включение строк прямо в определение классов позволяет получить более красивый и понятный код:
Copy Source | Copy HTML
  1. class Music(MyRe, '/music'):
  2.  
  3.     class Artist(MyRe, '/artist/\d+'): pass
  4.     class Song(MyRe, '/song/\d+'): pass
  5.     class Album(MyRe, '/album/\d+'): pass

Заключение


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

Дополнительная литература по метаклассам


  1. Официальная документация, Customizing class creation. docs.python.org/3.1/reference/datamodel.html#customizing-class-creation
  2. Unifying types and classes in Python 2.2 #Metaclasses, Guido van Rossum. www.python.org/download/releases/2.2/descrintro/#metaclasses
  3. PEP-3115, Metaclasses in Python 3000. www.python.org/dev/peps/pep-3115
  4. Python Metaclasess: Who? Why? When?, Mike C. Fletcher at PyCon 2004, презентация. www.vrplumber.com/programming/metaclasses-pycon.pdf
Tags:
Hubs:
+41
Comments33

Articles