Дженерики / шаблоны в python?

как python обрабатывает сценарии универсального / шаблонного типа? Скажем, я хочу создать внешний файл "BinaryTree.py" и пусть он обрабатывает двоичные деревья, но для любого типа данных.

поэтому я мог бы передать ему тип пользовательского объекта и иметь двоичное дерево этого объекта. Как это делается в python?

8 ответов


Python использует утиной типизацией, поэтому для обработки нескольких типов не требуется специальный синтаксис.

Если вы из фона C++, вы запомните это, пока операции, используемые в функции/классе шаблона, определены для некоторого типа T (на уровне синтаксиса), вы можете использовать этот тип T в шаблоне.

Итак, в основном, он работает одинаково:

  1. определить договор для типа элементов, которые вы хотите вставить в бинарное дерево.
  2. документировать этот контракт (т. е. в документации класса)
  3. реализовать бинарное дерево, используя только операции, указанные в договоре
  4. наслаждайтесь

однако вы заметите, что если вы не напишете явную проверку типа (которая обычно не рекомендуется), вы не сможете обеспечить, чтобы двоичное дерево содержало только элементы выбранного типа.


на самом деле теперь вы можете использовать дженерики в Python 3.5+. См.PEP-484 и ввод в библиотеке документации.

в соответствии с моей практикой это не очень бесшовно и ясно, особенно для тех, кто знаком с Java Generics, но все еще можно использовать.


поскольку python динамически типизирован, это очень просто. Фактически, вам нужно будет выполнить дополнительную работу для класса BinaryTree, чтобы не работать с любым типом данных.

например, если вы хотите, чтобы значения ключей, которые используются для размещения объекта в дереве, были доступны в объекте из метода key() вы просто называете key() на объекты. Например:

class BinaryTree(object):

    def insert(self, object_to_insert):
        key = object_to_insert.key()

обратите внимание, что вам никогда не нужно определять, какой класс object_to_insert. До тех пор, пока у него есть key() метод, он будет работать.

исключение, если вы хотите, чтобы он работал с основными типами данных, такими как строки или целые числа. Вам придется обернуть их в класс, чтобы заставить их работать с вашим общим BinaryTree. Если это звучит слишком тяжело, и вы хотите дополнительную эффективность на самом деле просто хранения строк, извините, это не то, что Python хорош.


поскольку Python динамически типизирован, типы объектов во многих случаях не имеют значения. Лучше принять что угодно.

чтобы продемонстрировать, что я имею в виду, этот класс дерева примет что-нибудь для своих двух ветвей:

class BinaryTree:
    def __init__(self, left, right):
        self.left, self.right = left, right

и его можно использовать следующим образом:

branch1 = BinaryTree(1,2)
myitem = MyClass()
branch2 = BinaryTree(myitem, None)
tree = BinaryTree(branch1, branch2)

после того, как я придумал некоторые хорошие мысли о создании общих типов в python, я начал искать других, у которых была та же идея, но я не мог найти их. Итак, вот оно. Я пробовал это, и это работает хорошо. Это позволяет нам параметризовать наши типы в python.

class List( type ):

        def __new__( type_ref, member_type ):

            class List( list ):

                def append( self, member ):

                    if not isinstance( member, member_type ):
                        raise TypeError( 'Attempted to append a "{0}" to a "{1}" which only takes a "{2}"'.format(
                            type( member ).__name__,
                            type( self ).__name__,
                            member_type.__name__ ) )

                    list.append( self, member )

            return List 

Теперь вы можете вывести типы из этого универсального типа.

class TestMember:
        pass

class TestList( List( TestMember ) ):

    def __init__( self ):
        super().__init__()


test_list = TestList()
test_list.append( TestMember() )
test_list.append( 'test' ) # This line will raise an exception

это решение является упрощенным, и у него есть свои ограничения. Каждый раз, когда вы создаете универсальный тип, он создает новый тип. Таким образом, несколько классов наследуют List( str ) как родитель будет наследовать от двух классов. Чтобы преодолеть это, вам нужно создать dict для хранения различных форм внутреннего класса и возврата предыдущего созданного внутреннего класса, а не создания нового. Это предотвратит создание дубликатов типов с одинаковыми параметрами. Если интересно, более элегантное решение можно сделать с декораторами и / или метаклассами.


к счастью, были некоторые усилия для общего программирования в python . Есть библиотека : generic

вот документация для него:http://generic.readthedocs.org/en/latest/

Он не прогрессирует в течение многих лет, но вы можете иметь приблизительное представление о том, как использовать и создавать свою собственную библиотеку.

Ура


посмотрите, как это делают встроенные контейнеры. dict и list и так далее содержат гетерогенные элементы любых типов, которые вам нравятся. Если вы определяете, скажем,insert(val) функция для вашего дерева, она в какой-то момент сделает что-то вроде node.value = val и Python позаботится об остальном.


если вы используете Python 2 или хотите переписать java-код. Их не является реальным решением для этого. Вот что я получаю, работая ночью:https://github.com/FlorianSteenbuck/python-generics я все еще не получаю компилятор, поэтому вы в настоящее время используете его так:

class A(GenericObject):
    def __init__(self, *args, **kwargs):
        GenericObject.__init__(self, [
            ['b',extends,int],
            ['a',extends,str],
            [0,extends,bool],
            ['T',extends,float]
        ], *args, **kwargs)

    def _init(self, c, a, b):
        print "success c="+str(c)+" a="+str(a)+" b="+str(b)

TODOs

  • компилятор
  • получить общие классы и типы, работающие (для таких вещей, как <? extends List<Number>>)
  • добавить super поддержка
  • добавить ? поддержка
  • Чистый Код