Gợi ý kiểu Python mà không cần nhập tuần hoàn


110

Tôi đang cố gắng chia lớp học lớn của mình thành hai; về cơ bản, về cơ bản là lớp "chính" và một mixin với các chức năng bổ sung, như sau:

main.py tập tin:

import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...

mymixin.py tập tin:

class MyMixin(object):
    def func2(self: Main, xxx):  # <--- note the type hint
        ...

Bây giờ, mặc dù điều này hoạt động tốt, nhưng loại gợi ý trong MyMixin.func2tất nhiên không thể hoạt động. Tôi không thể nhập main.py, bởi vì tôi muốn nhập theo chu kỳ và nếu không có gợi ý, trình soạn thảo của tôi (PyCharm) không thể biết đó selflà gì .

Tôi đang sử dụng Python 3.4, sẵn sàng chuyển sang 3.5 nếu có giải pháp ở đó.

Có cách nào tôi có thể chia lớp của mình thành hai tệp và giữ tất cả các "kết nối" để IDE của tôi vẫn cung cấp cho tôi tính năng tự động hoàn thành và tất cả các tiện ích khác đến từ nó khi biết các loại không?


2
Tôi không nghĩ rằng thông thường bạn nên chú thích kiểu của selfnó, vì nó sẽ luôn là một lớp con của lớp hiện tại (và bất kỳ hệ thống kiểm tra kiểu nào cũng có thể tự tìm ra điều đó). Đang func2cố gọi func1, mà không được xác định trong MyMixin? Có lẽ nó phải là (như một abstractmethod, có thể)?
Blckknght

cũng lưu ý rằng các lớp học nói chung cụ thể (ví dụ như mixin của bạn) nên đến các cơ sở lớp học còn lại trong định nghĩa lớp tức là class Main(MyMixin, SomeBaseClass)để các phương pháp từ lớp hơn cụ thể có thể ghi đè lên những người từ các lớp cơ sở
Anentropic

3
Tôi không chắc những nhận xét này hữu ích như thế nào, vì chúng liên quan đến câu hỏi đang được hỏi. velis đã không yêu cầu đánh giá mã.
Jacob Lee,

Gợi ý kiểu Python với các phương thức lớp được nhập cung cấp một giải pháp hữu ích cho vấn đề của bạn.
Ben Mares

Câu trả lời:


168

Tôi e rằng không có một cách cực kỳ thanh lịch nào để xử lý các chu trình nhập. Lựa chọn của bạn là thiết kế lại mã của bạn để loại bỏ sự phụ thuộc theo chu kỳ hoặc nếu nó không khả thi, hãy làm như sau:

# some_file.py

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    def func2(self, some_param: 'Main'):
        ...

Các TYPE_CHECKINGhằng số luôn luôn là Falsetrong thời gian chạy, vì vậy việc nhập khẩu sẽ không được đánh giá, nhưng mypy (và các công cụ loại kiểm tra khác) sẽ đánh giá các nội dung của khối đó.

Chúng ta cũng cần tạo Mainchú thích kiểu thành một chuỗi, chuyển tiếp khai báo nó một cách hiệu quả vì Mainbiểu tượng không khả dụng trong thời gian chạy.

Nếu bạn đang sử dụng Python 3.7+, ít nhất chúng ta có thể bỏ qua việc phải cung cấp chú thích chuỗi rõ ràng bằng cách tận dụng PEP 563 :

# some_file.py

from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

class MyObject(object):
    # Hooray, cleaner annotations!
    def func2(self, some_param: Main):
        ...

Việc from __future__ import annotationsnhập sẽ làm cho tất cả các gợi ý loại là chuỗi và bỏ qua việc đánh giá chúng. Điều này có thể giúp làm cho mã của chúng tôi ở đây nhẹ nhàng hơn.

Tất cả những gì đã nói, sử dụng mixin với mypy có thể sẽ yêu cầu cấu trúc nhiều hơn một chút so với bạn hiện có. Mypy đề xuất một cách tiếp cận về cơ bản decezelà những gì đang mô tả - để tạo một ABC mà cả lớp MainMyMixinlớp của bạn đều kế thừa. Tôi sẽ không ngạc nhiên nếu cuối cùng bạn cần làm điều gì đó tương tự để khiến người kiểm tra của Pycharm hài lòng.


4
Cảm ơn vì điều đó. Python 3.4 hiện tại của tôi không có typing, nhưng PyCharm cũng khá hài lòng if False:.
velis

Vấn đề duy nhất là nó không nhận ra MyObject là một mô hình Django. Mô hình và do đó cằn nhằn về các thuộc tính đối tượng được xác định bên ngoài__init__
velis

Đây là pep tương ứng cho typing. TYPE_CHECKING : python.org/dev/peps/pep-0484/#runtime-or-type-checking
Conchylicultor

25

Đối với những người gặp khó khăn với việc nhập theo chu kỳ khi chỉ nhập lớp để kiểm tra Loại: bạn có thể sẽ muốn sử dụng Tham chiếu Chuyển tiếp (PEP 484 - Gợi ý Loại):

Khi gợi ý kiểu chứa các tên chưa được xác định, định nghĩa đó có thể được biểu thị dưới dạng chuỗi ký tự, sẽ được giải quyết sau.

Vì vậy, thay vì:

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

bạn làm:

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

Có thể là PyCharm. Bạn đang sử dụng phiên bản mới nhất? Bạn đã thử File -> Invalidate Cacheschưa?
Tomasz Bartkowiak

Cảm ơn. Xin lỗi, tôi đã xóa bình luận của mình. Nó đã đề cập rằng điều này hoạt động, nhưng PyCharm phàn nàn. Tôi đã giải quyết bằng cách sử dụng hack if False do Velis đề xuất . Không hợp lệ bộ nhớ cache đã không giải quyết được nó. Nó có thể là một vấn đề PyCharm.
Jacob Lee

1
@JacobLee Thay vì if False:bạn cũng có thể from typing import TYPE_CHECKINGif TYPE_CHECKING:.
luckydonald

11

Vấn đề lớn hơn là các loại của bạn không tốt để bắt đầu. MyMixinđưa ra một giả định được mã hóa cứng rằng nó sẽ được trộn vào Main, trong khi nó có thể được trộn vào bất kỳ số lớp nào khác, trong trường hợp đó, nó có thể sẽ bị hỏng. Nếu mixin của bạn được mã hóa cứng để trộn vào một lớp cụ thể, bạn cũng có thể viết các phương thức trực tiếp vào lớp đó thay vì tách chúng ra.

Để thực hiện điều này một cách chính xác với cách nhập thông minh, MyMixinnên được mã hóa dựa trên một giao diện hoặc lớp trừu tượng theo cách nói của Python:

import abc


class MixinDependencyInterface(abc.ABC):
    @abc.abstractmethod
    def foo(self):
        pass


class MyMixin:
    def func2(self: MixinDependencyInterface, xxx):
        self.foo()  # ← mixin only depends on the interface


class Main(MixinDependencyInterface, MyMixin):
    def foo(self):
        print('bar')

1
Tôi không nói rằng giải pháp của tôi là tuyệt vời. Đó chỉ là những gì tôi đang cố gắng làm để làm cho mã dễ quản lý hơn. Đề xuất của bạn có thể vượt qua, nhưng điều này thực sự có nghĩa là chỉ di chuyển toàn bộ lớp Chính sang giao diện trong trường hợp cụ thể của tôi .
velis

3

Hóa ra nỗ lực ban đầu của tôi cũng khá gần với giải pháp. Đây là những gì tôi hiện đang sử dụng:

# main.py
import mymixin.py

class Main(object, MyMixin):
    def func1(self, xxx):
        ...


# mymixin.py
if False:
    from main import Main

class MyMixin(object):
    def func2(self: 'Main', xxx):  # <--- note the type hint
        ...

Lưu ý if Falserằng câu lệnh import trong không bao giờ được nhập (nhưng IDE vẫn biết về nó) và sử dụng Mainlớp dưới dạng chuỗi vì nó không được biết trong thời gian chạy.


Tôi hy vọng điều này sẽ gây ra cảnh báo về mã chết.
Phil

@Phil: vâng, vào thời điểm đó tôi đang sử dụng Python 3.4. Bây giờ có thông tin
nhập.TYPE_CHECKING

-4

Tôi nghĩ rằng cách hoàn hảo nên là nhập tất cả các lớp và phần phụ thuộc vào một tệp (như __init__.py) và sau đó from __init__ import *trong tất cả các tệp khác.

Trong trường hợp này bạn là

  1. tránh nhiều tham chiếu đến các tệp và lớp đó và
  2. cũng chỉ phải thêm một dòng trong mỗi tệp khác và
  3. thứ ba sẽ là pycharm biết về tất cả các lớp mà bạn có thể sử dụng.

1
nó có nghĩa là bạn đang tải mọi thứ ở khắp mọi nơi, nếu bạn đang có một thư viện khá nặng, điều đó có nghĩa là đối với mỗi lần nhập, bạn cần tải toàn bộ thư viện. + tham chiếu sẽ hoạt động siêu chậm.
Omer Shacham

> có nghĩa là bạn đang tải mọi thứ ở khắp mọi nơi. >>>> hoàn toàn không nếu bạn có nhiều tệp " init .py" hoặc các tệp khác, và tránh import *, bạn vẫn có thể tận dụng cách tiếp cận dễ dàng này
Sławomir Lenart
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.