Python'da jenerikler / şablonlar?


86

Python genel / şablon türü senaryolarını nasıl işler? Diyelim ki harici bir dosya "BinaryTree.py" oluşturmak ve bunun ikili ağaçları işlemesini sağlamak, ancak herhangi bir veri türü için.

Böylece ona özel bir nesne türünü iletebilirim ve bu nesnenin ikili bir ağacına sahip olabilirim. Bu python'da nasıl yapılır?


16
python'da ördek şablonları var
David Heffernan

Yanıtlar:


84

Python, ördek yazmayı kullanır , bu nedenle birden çok türü işlemek için özel sözdizimine ihtiyaç duymaz.

C ++ arka planından iseniz, şablon işlevinde / sınıfında kullanılan işlemler bir tür üzerinde T(sözdizimi düzeyinde) tanımlandığı sürece, şablonda bu türü kullanabileceğinizi hatırlayacaksınız T.

Yani, temelde aynı şekilde çalışır:

  1. ikili ağaca eklemek istediğiniz kalem türleri için bir sözleşme tanımlayın.
  2. bu sözleşmeyi belgeleyin (yani sınıf belgelerinde)
  3. ikili ağacı yalnızca sözleşmede belirtilen işlemleri kullanarak gerçekleştirin
  4. zevk almak

Bununla birlikte, açık tür denetimi yazmadığınız sürece (ki bu genellikle önerilmez), bir ikili ağacın yalnızca seçilen türden öğeleri içermesini zorlayamayacağınızı unutmayın.


6
André, Python'da açık tür kontrolünün normalde neden önerilmediğini anlamak istiyorum. Kafam karıştı, çünkü dinamik olarak yazılmış bir dil gibi görünüyor, işleve girecek olası türleri garanti edemezsek çok fazla sorun yaşayabiliriz. Ama yine de Python'da çok yeniyim. :-)
ScottEdwards2000

3
@ ScottEdwards2000 PEP 484 ve bir tür denetleyicide tür ipuçları ile örtük tür kontrolüne sahip olabilirsiniz
noɥʇʎԀʎzɐɹƆ

7
Python Purist bakış olarak, Python dinamik bir dil ve ördek-yazma olduğunu paradigma; yani, tür güvenliği 'Pythonic dışı' olarak yönetilir. Bu benim için kabul edilebilir bulması zor olan bir şeydi - bir süreliğine - C # 'a büyük ölçüde sahip olduğum için. Bir yandan tip güvenliğini bir gereklilik olarak görüyorum. .Net dünyası ve Pythonic paradigması arasındaki ölçekleri dengelediğim için, tip güvenliğinin gerçekten bir emzik olduğunu kabul ettim ve gerekirse, tek yapmam gereken ya da ... oldukça basit. if isintance(o, t):if not isinstance(o, t):
IAbstract

2
Teşekkür yorumcular, harika cevaplar. Onları okuduktan sonra, kendi hatalarımı yakalamak için gerçekten sadece tip kontrolünü istediğimi fark ettim. Bu yüzden örtük tür denetimi kullanacağım.
ScottEdwards2000

3
Pek çok Pythonistin bununla ilgili noktayı kaçırdığını düşünüyorum - jenerikler, aynı zamanda özgürlük ve güvenlik sağlamanın bir yoludur. Jenerikleri bir kenara bırakıp ve sadece yazılan parametreleri kullanarak bile, işlev yazıcısı, sınıfın sağladığı herhangi bir yöntemi kullanmak için kodunu değiştirebileceğini bilir; ördek yazarak daha önce kullanmadığınız bir yöntemi kullanmaya başlarsanız, aniden ördeğin tanımını değiştirmişsinizdir ve muhtemelen işler bozulacaktır.
Ken Williams

60

Diğer cevaplar tamamen iyi:

  • Python'da jenerikleri desteklemek için özel bir sözdizimine ihtiyaç yoktur
  • Python, André'nin belirttiği gibi ördek yazmayı kullanır .

Ancak yine de yazılan bir varyant istiyorsanız , Python 3.5'ten beri yerleşik bir çözüm var.

Genel sınıflar :

from typing import TypeVar, Generic

T = TypeVar('T')

class Stack(Generic[T]):
    def __init__(self) -> None:
        # Create an empty list with items of type T
        self.items: List[T] = []

    def push(self, item: T) -> None:
        self.items.append(item)

    def pop(self) -> T:
        return self.items.pop()

    def empty(self) -> bool:
        return not self.items
# Construct an empty Stack[int] instance
stack = Stack[int]()
stack.push(2)
stack.pop()
stack.push('x')        # Type error

Genel işlevler:

from typing import TypeVar, Sequence

T = TypeVar('T')      # Declare type variable

def first(seq: Sequence[T]) -> T:
    return seq[0]

def last(seq: Sequence[T]) -> T:
    return seq[-1]


n = first([1, 2, 3])  # n has type int.

Referans: jenerikler hakkında mypy belgeleri .


1
Kesinlikle en iyi cevap burada
Denis Itskovich

Docs.python.org/3.5/library/typing.html'deki önemli not , "Generic tarafından kullanılan meta sınıf, abc.ABCMeta'nın bir alt sınıfıdır" şeklindedir.
Jonathan Komar

20

Aslında artık Python 3.5+ ile jenerikleri kullanabilirsiniz. Bkz PEP-484 ve yazma modülü belgeleri .

Uygulamama göre, özellikle Java Generics'e aşina olanlar için çok kusursuz ve net değil, ancak yine de kullanılabilir.


12
Bu, jenerik ilaçların ucuz bir soygununa benziyor. Sanki birisi jenerikler almış, onları bir blendere koymuş, çalışsın ve blender motoru yanana kadar unutmuş gibi, ve 2 gün sonra onu çıkarıp "hey jenerik var" dedi.
Herkes

3
Bunlar "tip ipuçları", jeneriklerle hiçbir ilgisi yok.
wool.in.silver

Typcript'te aynı ama orada Java'daki gibi çalışıyor (sözdizimsel olarak). Bu dillerdeki Jenerik sadece tip ipuçları vardır
Davide

11

Python'da jenerik türler yapma konusunda bazı iyi düşünceler bulduktan sonra, aynı fikre sahip olan diğerlerini aramaya başladım, ancak hiçbirini bulamadım. İşte burada. Bunu denedim ve iyi çalışıyor. Türlerimizi python'da parametrelendirmemize izin verir.

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 

Artık türleri bu genel türden türetebilirsiniz.

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

Bu çözüm basittir ve sınırlamaları vardır. Her genel tür oluşturduğunuzda, yeni bir tür oluşturacaktır. Bu nedenle, List( str )ebeveyn olarak miras alan birden fazla sınıf , iki ayrı sınıftan miras alacaktır. Bunun üstesinden gelmek için, yeni bir tane oluşturmak yerine, iç sınıfın çeşitli biçimlerini depolamak ve önceden oluşturulan iç sınıfı geri döndürmek için bir karar oluşturmanız gerekir. Bu, aynı parametrelere sahip yinelenen türlerin oluşturulmasını engeller. İlgilenirse, dekoratörler ve / veya metasınıflarla daha şık bir çözüm yapılabilir.


Yukarıdaki örnekte diktenin nasıl kullanılabileceğini açıklar mısınız? Bunun için git veya başka bir yerde pasajınız var mı? Teşekkür ederim ..
gnomeria

Bir örneğim yok ve şu anda biraz zaman alabilir. Ancak ilkeler o kadar da zor değil. Dikte, önbellek görevi görür. Yeni sınıf oluşturulduğunda, bu tür için bir tanımlayıcı ve parametre yapılandırması oluşturmak için tür parametrelerine bakması gerekir. Daha sonra, önceden var olan sınıfı aramak için bunu bir diktede anahtar olarak kullanabilir. Bu şekilde, o bir sınıfı tekrar tekrar kullanacaktır.
Ariyo Canlı

İlham aldığınız için teşekkürler - bu tekniğin metasınıflarla genişletilmesi için cevabımı görün
Eric

4

Python dinamik olarak yazıldığından, nesnelerin türleri çoğu durumda önemli değildir. Herhangi bir şeyi kabul etmek daha iyi bir fikirdir.

Ne demek istediğimi göstermek için, bu ağaç sınıfı iki dalı için her şeyi kabul edecek:

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

Ve şu şekilde kullanılabilir:

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

7
Nesnelerin türleri yapmak meselesi. Konteynerin öğeleri üzerinde döngü yapıyorsanız ve fooher nesnede bir yöntem çağırıyorsanız , konteynere dizeler koymak kötü bir fikirdir. Hiçbir şeyi kabul etmek daha iyi bir fikir değil . Bununla birlikte, kaptaki tüm nesnelerin sınıftan türetilmesini gerektirmemek uygundur . HasAFooMethod
André Caron

1
Aslında tip yapar madde: o kadar talimatı verdi.
Fred Foo

Ah tamam. O zaman yanlış anladım.
Andrea

3

Python dinamik olarak yazıldığından, bu çok kolaydır. Aslında, BinaryTree sınıfınızın herhangi bir veri türüyle çalışmaması için fazladan iş yapmanız gerekir.

Örneğin, nesneyi ağaçta yerleştirmek için kullanılan anahtar değerlerinin, key()sadece key()nesneleri çağırdığınız gibi bir yöntemden nesne içinde mevcut olmasını istiyorsanız . Örneğin:

class BinaryTree(object):

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

Object_to_insert sınıfının ne tür olduğunu tanımlamanıza asla gerek olmadığını unutmayın. Bir key()yöntemi olduğu sürece işe yarayacaktır.

Bunun istisnası, dizeler veya tamsayılar gibi temel veri türleriyle çalışmasını istemenizdir. Genel BinaryTree'nizle çalışmasını sağlamak için onları bir sınıfa bağlamanız gerekecek. Kulağa çok ağır geliyorsa ve sadece dizeleri depolamanın ekstra verimliliğini istiyorsanız, üzgünüm, Python'un iyi olduğu şey bu değil.


7
Aksine: tüm veri türleri Python'da nesnelerdir. Paketlenmeleri gerekmez (Java'da Integerkutulama / kutudan çıkarma ile olduğu gibi ).
George Hilliard

2

İşte bu cevabın karmaşık sözdiziminden kaçınmak için metasınıflar kullanan ve typing-style List[int]sözdizimini kullanan bir çeşidi :

class template(type):
    def __new__(metacls, f):
        cls = type.__new__(metacls, f.__name__, (), {
            '_f': f,
            '__qualname__': f.__qualname__,
            '__module__': f.__module__,
            '__doc__': f.__doc__
        })
        cls.__instances = {}
        return cls

    def __init__(cls, f):  # only needed in 3.5 and below
        pass

    def __getitem__(cls, item):
        if not isinstance(item, tuple):
            item = (item,)
        try:
            return cls.__instances[item]
        except KeyError:
            cls.__instances[item] = c = cls._f(*item)
            item_repr = '[' + ', '.join(repr(i) for i in item) + ']'
            c.__name__ = cls.__name__ + item_repr
            c.__qualname__ = cls.__qualname__ + item_repr
            c.__template__ = cls
            return c

    def __subclasscheck__(cls, subclass):
        for c in subclass.mro():
            if getattr(c, '__template__', None) == cls:
                return True
        return False

    def __instancecheck__(cls, instance):
        return cls.__subclasscheck__(type(instance))

    def __repr__(cls):
        import inspect
        return '<template {!r}>'.format('{}.{}[{}]'.format(
            cls.__module__, cls.__qualname__, str(inspect.signature(cls._f))[1:-1]
        ))

Bu yeni metasınıf ile, bağladığım cevaptaki örneği şu şekilde yeniden yazabiliriz:

@template
def List(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

l = List[int]()
l.append(1)  # ok
l.append("one")  # error

Bu yaklaşımın bazı güzel faydaları var

print(List)  # <template '__main__.List[member_type]'>
print(List[int])  # <class '__main__.List[<class 'int'>, 10]'>
assert List[int] is List[int]
assert issubclass(List[int], List)  # True

1

Yerleşik konteynerlerin bunu nasıl yaptığına bakın. dictve listvb ne gibi türleri heterojen öğeler içerir. Diyelim ki insert(val)ağacınız için bir işlev tanımlarsanız, bir noktada benzer bir şey node.value = valyapacak ve gerisini Python halledecektir.


1

Neyse ki python'da jenerik programlama için bazı çabalar oldu. Bir kütüphane var: genel

İşte bunun için belgeler: http://generic.readthedocs.org/en/latest/

Yıllar geçtikçe ilerlemedi, ancak kendi kitaplığınızı nasıl kullanacağınız ve oluşturacağınız konusunda kabaca bir fikriniz olabilir.

Şerefe


1

Python 2 kullanıyorsanız veya java kodunu yeniden yazmak istiyorsanız. Bunun için gerçek bir çözüm değiller. İşte bir gecede çalıştığım şey: https://github.com/FlorianSteenbuck/python-generics Hala bir derleyici bulamıyorum, bu yüzden şu anda böyle kullanıyorsunuz:

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)

YAPILACAKLAR

  • Derleyici
  • Genel Sınıfları ve Türleri çalıştırma (Benzeri şeyler için <? extends List<Number>>)
  • superDestek ekleyin
  • ?Destek ekleyin
  • Kod Temizleme
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.