คำใบ้ประเภท Python โดยไม่มีการนำเข้าแบบวนรอบ


129

ฉันกำลังพยายามแบ่งชั้นเรียนขนาดใหญ่ของฉันออกเป็นสองชั้น โดยพื้นฐานแล้วจะอยู่ในคลาส "หลัก" และมิกซ์อินที่มีฟังก์ชันเพิ่มเติมดังนี้:

main.py ไฟล์:

import mymixin.py

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

mymixin.py ไฟล์:

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

ขณะนี้ใช้งานได้ดี แต่MyMixin.func2แน่นอนว่าคำใบ้ประเภทไม่สามารถใช้งานได้ ฉันไม่สามารถนำเข้าได้main.pyเนื่องจากฉันได้รับการนำเข้าแบบวนรอบและหากไม่มีคำใบ้ตัวแก้ไขของฉัน (PyCharm) ไม่สามารถบอกได้ว่าselfคืออะไร

ฉันใช้ Python 3.4 และยินดีที่จะย้ายไปที่ 3.5 หากมีวิธีแก้ปัญหา

มีวิธีใดบ้างที่ฉันสามารถแบ่งคลาสของฉันออกเป็นสองไฟล์และเก็บ "การเชื่อมต่อ" ทั้งหมดไว้เพื่อให้ IDE ของฉันยังคงเสนอการเติมข้อมูลอัตโนมัติให้ฉันและสินค้าอื่น ๆ ทั้งหมดที่มาจากคลาสนั้นรู้ประเภทหรือไม่


2
ฉันไม่คิดว่าปกติคุณควรจะต้องใส่คำอธิบายประกอบประเภทของselfเนื่องจากมันจะเป็นคลาสย่อยของคลาสปัจจุบันเสมอ (และระบบตรวจสอบประเภทใด ๆ ก็ควรจะสามารถคิดได้ด้วยตัวเอง) มีการfunc2พยายามที่จะเรียกfunc1ที่ไม่ได้กำหนดไว้ในMyMixin? บางทีมันควรจะเป็น ( abstractmethodอาจจะ)?
Blckknght

นอกจากนี้โปรดทราบว่าโดยทั่วไปคลาสที่เฉพาะเจาะจงมากขึ้น (เช่น mixin ของคุณ) ควรไปทางด้านซ้ายของคลาสพื้นฐานในการกำหนดคลาสเช่นclass Main(MyMixin, SomeBaseClass)เพื่อให้เมธอดจากคลาสที่เฉพาะเจาะจงมากขึ้นสามารถแทนที่คลาสจากคลาสพื้นฐานได้
Anentropic

4
ฉันไม่แน่ใจว่าความคิดเห็นเหล่านี้มีประโยชน์อย่างไรเนื่องจากเป็นสิ่งที่ตรงกันกับคำถามที่ถาม velis ไม่ได้ขอให้ตรวจสอบโค้ด
Jacob Lee

คำแนะนำประเภท Python พร้อมเมธอดคลาสที่นำเข้าช่วยแก้ปัญหาของคุณได้อย่างยอดเยี่ยม
Ben Mares

คำตอบ:


191

ไม่มีวิธีที่หรูหราอย่างมากในการจัดการวงจรการนำเข้าโดยทั่วไปฉันกลัว ตัวเลือกของคุณคือการออกแบบโค้ดของคุณใหม่เพื่อลบการพึ่งพาแบบวนรอบหรือหากเป็นไปไม่ได้ให้ทำสิ่งนี้:

# some_file.py

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from main import Main

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

TYPE_CHECKINGคงที่อยู่เสมอFalseที่รันไทม์ดังนั้นการนำเข้าจะไม่ได้รับการประเมิน แต่ mypy (และเครื่องมือประเภทการตรวจสอบอื่น ๆ ) จะประเมินเนื้อหาของบล็อกว่า

นอกจากนี้เรายังต้องสร้างMainคำอธิบายประกอบประเภทเป็นสตริงโดยจะประกาศไปข้างหน้าอย่างมีประสิทธิภาพเนื่องจากMainสัญลักษณ์ไม่พร้อมใช้งานในรันไทม์

หากคุณใช้ Python 3.7+ อย่างน้อยเราก็สามารถข้ามไม่ต้องใส่คำอธิบายประกอบสตริงที่ชัดเจนได้โดยใช้ประโยชน์จาก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):
        ...

from __future__ import annotationsนำเข้าจะทำให้ทุกคำแนะนำชนิดเป็นสตริงและข้ามประเมินพวกเขา ซึ่งจะช่วยให้โค้ดของเราตรงนี้เหมาะกับการทำงานมากขึ้น

ทั้งหมดที่กล่าวมาการใช้ mixins กับ mypy อาจต้องใช้โครงสร้างอีกเล็กน้อยจากนั้นคุณก็มี Mypy แนะนำวิธีการที่เป็นพื้นฐานdecezeอธิบาย - เพื่อสร้าง ABC ที่ทั้งของคุณMainและMyMixinชั้นเรียนสืบทอดมา ฉันจะไม่แปลกใจเลยถ้าคุณต้องทำอะไรคล้าย ๆ กันเพื่อให้ตัวตรวจสอบของ Pycharm มีความสุข


4
ขอบคุณสำหรับสิ่งนี้. python 3.4 ปัจจุบันของฉันไม่มีtypingแต่ PyCharm ก็ค่อนข้างพอใจif False:เช่นกัน
velis

ปัญหาเดียวคือไม่รู้จัก MyObject เป็นโมเดล Django โมเดลและทำให้เกิดปัญหาเกี่ยวกับแอตทริบิวต์อินสแตนซ์ที่ถูกกำหนดไว้ภายนอก__init__
velis

นี่คือ pep ที่สอดคล้องกันสำหรับtyping. TYPE_CHECKING : python.org/dev/peps/pep-0484/#runtime-or-type-checking
Conchylicultor

32

สำหรับผู้ที่มีปัญหากับการนำเข้าแบบวนซ้ำเมื่อนำเข้าคลาสสำหรับการตรวจสอบประเภทเท่านั้น: คุณอาจต้องการใช้การอ้างอิงไปข้างหน้า (PEP 484 - คำแนะนำประเภท):

เมื่อคำใบ้ประเภทมีชื่อที่ยังไม่ได้กำหนดนิยามนั้นอาจแสดงเป็นสตริงลิเทอรัลเพื่อแก้ไขในภายหลัง

แทนที่จะเป็น:

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

คุณทำ:

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

อาจเป็น PyCharm คุณใช้เวอร์ชันใหม่ล่าสุดหรือไม่? คุณลองแล้วFile -> Invalidate Cachesหรือยัง?
Tomasz Bartkowiak

ขอบคุณ. ขออภัยฉันลบความคิดเห็นของฉันแล้ว ได้กล่าวไว้ว่าสิ่งนี้ได้ผล แต่ PyCharm บ่น ฉันแก้ไขโดยใช้ if False hack ที่Velisแนะนำ การไม่ตรวจสอบแคชไม่สามารถแก้ไขได้ อาจเป็นปัญหาของ PyCharm
Jacob Lee

2
@JacobLee แทนคุณif False:ได้from typing import TYPE_CHECKINGและif TYPE_CHECKING:.
luckydonald

12

ปัญหาใหญ่กว่าคือประเภทของคุณไม่ได้มีเหตุผลที่จะเริ่มต้นด้วย MyMixinสร้างสมมติฐานแบบฮาร์ดโค้ดที่จะผสมลงในMainขณะที่สามารถผสมกับคลาสอื่น ๆ จำนวนเท่าใดก็ได้ซึ่งในกรณีนี้มันอาจจะแตก หากมิกซ์อินของคุณถูกเข้ารหัสแบบฮาร์ดเพื่อผสมเป็นคลาสเฉพาะคุณอาจเขียนเมธอดลงในคลาสนั้นโดยตรงแทนการแยกออก

หากต้องการทำสิ่งนี้อย่างถูกต้องด้วยการพิมพ์ที่มีสติMyMixinควรเข้ารหัสกับอินเทอร์เฟซหรือคลาสนามธรรมในภาษา 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
ฉันไม่ได้บอกว่าวิธีแก้ปัญหาของฉันดีมาก เป็นเพียงสิ่งที่ฉันพยายามทำเพื่อให้จัดการโค้ดได้ง่ายขึ้น คำแนะนำของคุณอาจผ่านไป แต่จริงๆแล้วนี่หมายถึงแค่ย้ายคลาสหลักทั้งหมดไปยังอินเทอร์เฟซในกรณีเฉพาะของฉัน
velis

3

ปรากฎว่าความพยายามเดิมของฉันค่อนข้างใกล้เคียงกับวิธีแก้ปัญหาเช่นกัน นี่คือสิ่งที่ฉันกำลังใช้:

# 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
        ...

สังเกตการนำเข้าภายในif Falseคำสั่งที่ไม่เคยถูกอิมพอร์ต (แต่ IDE รู้ดีอยู่แล้ว) และใช้Mainคลาสเป็นสตริงเนื่องจากไม่รู้จักในรันไทม์


ฉันคาดหวังว่าสิ่งนี้จะทำให้เกิดคำเตือนเกี่ยวกับรหัสที่ตายแล้ว
ฟิล

@ ฟิล: ใช่ตอนนั้นฉันใช้ Python 3.4 ตอนนี้มีการพิมพ์ TYPE_CHECKING
velis

-4

ฉันคิดว่าวิธีที่ดีที่สุดคือการนำเข้าคลาสและการอ้างอิงทั้งหมดในไฟล์ (เช่น__init__.py) จากนั้นfrom __init__ import *ในไฟล์อื่น ๆ ทั้งหมด

ในกรณีนี้คุณคือ

  1. หลีกเลี่ยงการอ้างอิงหลายไฟล์และคลาสเหล่านั้นและ
  2. นอกจากนี้ยังต้องเพิ่มหนึ่งบรรทัดในแต่ละไฟล์และ
  3. อย่างที่สามคือ pycharm ที่รู้เกี่ยวกับคลาสทั้งหมดที่คุณอาจใช้

1
หมายความว่าคุณกำลังโหลดทุกที่ทุกที่หากคุณมีไลบรารีที่ค่อนข้างหนักหมายความว่าสำหรับการนำเข้าทุกครั้งคุณต้องโหลดไลบรารีทั้งหมด + การอ้างอิงจะทำงานช้ามาก
Omer Shacham

> หมายความว่าคุณกำลังโหลดทุกที่ >>>> ไม่ใช่อย่างแน่นอนหากคุณมี " init .py" หรือไฟล์อื่น ๆ จำนวนมากและหลีกเลี่ยงimport *แต่คุณยังสามารถใช้ประโยชน์จากแนวทางง่ายๆนี้ได้
Sławomir Lenart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.