Generics / template trong python?


83

Python xử lý các tình huống loại chung / mẫu như thế nào? Giả sử tôi muốn tạo một tệp bên ngoài "BinaryTree.py" và để nó xử lý cây nhị phân, nhưng đối với bất kỳ kiểu dữ liệu nào.

Vì vậy, tôi có thể chuyển cho nó kiểu của một đối tượng tùy chỉnh và có một cây nhị phân của đối tượng đó. Làm thế nào điều này được thực hiện trong python?


14
python có mẫu vịt
David Heffernan

Câu trả lời:


82

Python sử dụng kiểu gõ vịt , vì vậy nó không cần cú pháp đặc biệt để xử lý nhiều kiểu.

Nếu bạn từ nền tảng C ++, bạn sẽ nhớ rằng, miễn là các hoạt động được sử dụng trong hàm / lớp mẫu được xác định trên một số kiểu T(ở cấp cú pháp), bạn có thể sử dụng kiểu đó Ttrong mẫu.

Vì vậy, về cơ bản, nó hoạt động theo cùng một cách:

  1. xác định hợp đồng cho loại mục bạn muốn chèn vào cây nhị phân.
  2. ghi lại hợp đồng này (tức là trong tài liệu lớp học)
  3. triển khai cây nhị phân chỉ sử dụng các hoạt động được chỉ định trong hợp đồng
  4. thưởng thức

Tuy nhiên, bạn sẽ lưu ý rằng trừ khi bạn viết kiểm tra kiểu rõ ràng (thường không được khuyến khích), bạn sẽ không thể thực thi rằng một cây nhị phân chỉ chứa các phần tử của kiểu đã chọn.


5
André, tôi muốn hiểu tại sao việc kiểm tra kiểu rõ ràng thường không được khuyến khích trong Python. Tôi bối rối vì có vẻ như với một ngôn ngữ được nhập động, chúng tôi có thể gặp rất nhiều rắc rối nếu chúng tôi không thể đảm bảo các kiểu có thể sẽ đi vào hàm. Nhưng, một lần nữa, tôi rất mới đối với Python. :-)
ScottEdwards2000

2
@ ScottEdwards2000 Bạn có thể có kiểu ngầm kiểm tra với kiểu gợi ý trong PEP 484 và một loại kiểm tra
noɥʇʎԀʎzɐɹƆ

6
Trong quan điểm của purist Python, Python là một ngôn ngữ năng động và vịt-gõ là các mô hình; tức là, an toàn kiểu được quy định là 'không phải Pythonic'. Đây là điều mà tôi rất khó chấp nhận - trong một thời gian - vì tôi được giao rất nhiều trong C #. Một mặt, tôi thấy an toàn kiểu chữ là một điều cần thiết. Vì tôi đã cân bằng quy mô giữa thế giới .Net và mô hình Pythonic, tôi đã chấp nhận rằng an toàn kiểu thực sự là một núm vú giả và nếu tôi cần, tất cả những gì tôi phải làm là if isintance(o, t):hoặc if not isinstance(o, t):... khá đơn giản.
IAbstract

2
Cảm ơn người bình luận, câu trả lời tuyệt vời. Tôi nhận ra sau khi đọc chúng rằng tôi thực sự chỉ muốn kiểm tra kiểu để bắt lỗi của chính mình. Vì vậy, tôi sẽ chỉ sử dụng kiểm tra kiểu ngầm định.
ScottEdwards2000

1
Tôi nghĩ rằng nhiều người theo thuyết Pythonists đã bỏ lỡ quan điểm về điều này - thuốc chung là một cách để cung cấp tự do và an toàn cùng một lúc. Ngay cả khi bỏ qua các thông số chung & chỉ sử dụng các tham số đã nhập, người viết hàm biết rằng họ có thể sửa đổi mã của mình để sử dụng bất kỳ phương thức nào mà lớp cung cấp; với cách gõ vịt nếu bạn bắt đầu sử dụng phương pháp mà bạn không sử dụng trước đây, bạn đột ngột thay đổi định nghĩa của con vịt và mọi thứ có thể sẽ hỏng.
Ken Williams

45

Các câu trả lời khác hoàn toàn ổn:

  • Người ta không cần một cú pháp đặc biệt để hỗ trợ generic trong Python
  • Python sử dụng kiểu gõ vịt như André đã chỉ ra .

Tuy nhiên, nếu bạn vẫn muốn có một biến thể đã nhập , có một giải pháp tích hợp kể từ Python 3.5.

Các lớp chung :

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

Các chức năng chung:

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.

Tham khảo: tài liệu mypy về thuốc generic .


18

Trên thực tế, bây giờ bạn có thể sử dụng generic trong Python 3.5+. Xem PEP-484tài liệu thư viện đánh máy .

Theo thực tế của tôi, nó không được liền mạch và rõ ràng đặc biệt là đối với những người đã quen thuộc với Java Generics, nhưng vẫn có thể sử dụng được.


10
Điều đó trông giống như một sự tách rời rẻ tiền của tbh chung chung. Nó giống như ai đó có thuốc generic, cho chúng vào máy xay sinh tố, để nó chạy và quên nó cho đến khi động cơ máy xay sinh tố cháy hết, rồi 2 ngày sau lấy ra và nói: "hey we got generics".
Mọi người

3
Đó là "gợi ý loại", chúng không liên quan gì đến thuốc chung.
len.in.silver

Tương tự về kiểu chữ nhưng ở đó nó hoạt động giống như trong Java (về mặt cú pháp). Generic trong những ngôn ngữ này chỉ là gợi ý nhập
Davide

11

Sau khi nảy ra một số suy nghĩ hay về việc tạo các loại chung trong python, tôi bắt đầu tìm kiếm những người khác có cùng ý tưởng, nhưng tôi không thể tìm thấy cái nào. Vì vậy, nó đây. Tôi đã thử điều này và nó hoạt động tốt. Nó cho phép chúng ta tham số hóa các kiểu của mình trong 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 

Bây giờ bạn có thể lấy các kiểu từ kiểu chung này.

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

Giải pháp này rất đơn giản và nó có những hạn chế. Mỗi lần bạn tạo một kiểu chung, nó sẽ tạo một kiểu mới. Do đó, nhiều lớp kế thừa List( str )với tư cách là lớp cha sẽ được kế thừa từ hai lớp riêng biệt. Để khắc phục điều này, bạn cần tạo một dict để lưu trữ các dạng khác nhau của lớp bên trong và trả về lớp bên trong đã tạo trước đó, thay vì tạo một lớp mới. Điều này sẽ ngăn việc tạo các loại trùng lặp có cùng thông số. Nếu quan tâm, một giải pháp thanh lịch hơn có thể được thực hiện với máy trang trí và / hoặc kính đo.


Bạn có thể giải thích rõ hơn về cách sử dụng dict trong ví dụ trên? Bạn có đoạn mã cho điều đó bằng git hay gì đó không? Cảm ơn bạn ..
gnomeria

Tôi không có ví dụ và có thể hơi mất thời gian ngay bây giờ. Tuy nhiên, các nguyên tắc không phải là khó khăn. Các dict hoạt động như bộ nhớ cache. Khi lớp mới được tạo, nó cần phải xem các tham số kiểu để tạo định danh cho kiểu và cấu hình tham số đó. Sau đó, nó có thể sử dụng nó như một khóa trong một lệnh để tra cứu lớp đã tồn tại trước đó. Bằng cách này, nó sẽ sử dụng lặp đi lặp lại một lớp đó.
Ché on The Scene

Cảm ơn cảm hứng - xem câu trả lời của tôi cho một phần mở rộng của kỹ thuật này với metaclasses
Eric

4

Bởi vì Python được gõ động, các loại đối tượng không quan trọng trong nhiều trường hợp. Tốt hơn hết là bạn nên chấp nhận bất cứ điều gì.

Để chứng minh ý tôi muốn nói, lớp cây này sẽ chấp nhận bất cứ thứ gì cho hai nhánh của nó:

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

Và nó có thể được sử dụng như thế này:

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

6
Các loại đối tượng có ý nghĩa. Nếu bạn đang lặp lại các mục của vùng chứa và gọi một phương thức footrên mỗi đối tượng, thì việc đặt các chuỗi vào vùng chứa là một ý tưởng tồi. Không nên chấp nhận bất cứ điều gì . Tuy nhiên, sẽ rất tiện lợi khi không yêu cầu tất cả các đối tượng trong vùng chứa bắt nguồn từ lớp HasAFooMethod.
André Caron

1
Trên thực tế, loại không quan trọng: nó phải được đặt hàng.
Fred Foo

Ồ được thôi. Khi đó tôi đã hiểu lầm.
Andrea

3

Vì python được nhập động nên việc này rất dễ dàng. Trên thực tế, bạn phải làm thêm công việc để lớp BinaryTree của bạn không hoạt động với bất kỳ kiểu dữ liệu nào.

Ví dụ: nếu bạn muốn các giá trị khóa được sử dụng để đặt đối tượng trong cây có sẵn bên trong đối tượng từ một phương thức giống như key()bạn vừa gọi key()các đối tượng. Ví dụ:

class BinaryTree(object):

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

Lưu ý rằng bạn không bao giờ cần phải xác định loại object_to_insert là gì. Vì vậy, miễn là nó có một key()phương pháp, nó sẽ hoạt động.

Ngoại lệ là nếu bạn muốn nó hoạt động với các kiểu dữ liệu cơ bản như chuỗi hoặc số nguyên. Bạn sẽ phải gói chúng trong một lớp để chúng hoạt động với BinaryTree chung của bạn. Nếu điều đó nghe có vẻ quá nặng và bạn muốn có thêm hiệu quả khi thực sự chỉ lưu trữ các chuỗi, xin lỗi, đó không phải là những gì Python giỏi.


7
Ngược lại: tất cả các kiểu dữ liệu đều là đối tượng trong Python. Chúng không cần phải được bọc (như trong Java với Integerquyền anh / mở hộp).
George Hilliard

2

Đây là một biến thể của câu trả lời này sử dụng metaclasses để tránh cú pháp lộn xộn và sử dụng cú pháp typing-style List[int]:

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]
        ))

Với siêu kính mới này, chúng ta có thể viết lại ví dụ trong câu trả lời mà tôi liên kết tới là:

@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

Cách tiếp cận này có một số lợi ích tốt

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

Xem cách các thùng chứa tích hợp thực hiện điều đó. dictlistvân vân chứa các yếu tố không đồng nhất thuộc bất kỳ loại nào bạn thích. Chẳng hạn, nếu bạn định nghĩa một insert(val)hàm cho cây của mình, thì đến một lúc nào đó, nó sẽ hoạt động như thế nào đó node.value = valvà Python sẽ lo phần còn lại.


1

May mắn thay, đã có một số nỗ lực cho việc lập trình chung trong python. Có một thư viện: chung

Đây là tài liệu về nó: http://generic.readthedocs.org/en/latest/

Nó không tiến triển qua nhiều năm, nhưng bạn có thể biết sơ bộ về cách sử dụng và tạo thư viện của riêng mình.

Chúc mừng


1

Nếu bạn đang sử dụng Python 2 hoặc muốn viết lại mã java. Họ không phải là giải pháp thực sự cho điều này. Đây là những gì tôi làm trong một đêm: https://github.com/FlorianSteenbuck/python-generics Tôi vẫn không nhận được trình biên dịch nào nên bạn hiện đang sử dụng nó như vậy:

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)

VIỆC CẦN LÀM

  • Trình biên dịch
  • Làm cho các Lớp và Kiểu Chung hoạt động (Đối với những thứ như <? extends List<Number>>)
  • Thêm superhỗ trợ
  • Thêm ?hỗ trợ
  • Xóa mã
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.