Döngüsel içe aktarmalar olmadan Python türü ipuçları


129

Büyük sınıfımı ikiye ayırmaya çalışıyorum; temelde "ana" sınıfa ve bunun gibi ek işlevler içeren bir karışıma:

main.py dosya:

import mymixin.py

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

mymixin.py dosya:

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

Şimdi, bu gayet iyi çalışsa da, tip ipucu MyMixin.func2elbette işe yaramaz. İçe main.pyaktaramıyorum, çünkü döngüsel bir içe aktarma elde ederim ve ipucu olmadan editörüm (PyCharm) ne selfolduğunu söyleyemez .

Python 3.4 kullanıyorum, orada bir çözüm varsa 3.5'e geçmek istiyorum.

Sınıfımı iki dosyaya bölüp tüm "bağlantıları" tutmamın bir yolu var mı, böylece IDE'm hala otomatik tamamlama ve türleri bilerek ondan gelen diğer tüm güzellikleri sunmaya devam ediyor?


2
Normalde türüne açıklama eklemeniz gerektiğini düşünmüyorum self, çünkü her zaman mevcut sınıfın bir alt sınıfı olacaktır (ve herhangi bir tür kontrol sistemi bunu kendi başına çözebilmelidir). Is func2çağrı çalışırken func1tanımlı değil, MyMixin? Belki de olmalı (bir abstractmethod, belki olarak)?
Blckknght

ayrıca, genel olarak daha spesifik sınıfların (örneğin sizin karışımınız), sınıf tanımında temel sınıfların soluna gitmesi gerektiğini unutmayın, yani class Main(MyMixin, SomeBaseClass), daha spesifik sınıftan yöntemler, temel sınıftakileri geçersiz kılabilir
Anentropic

4
Sorulan soruya teğet oldukları için bu yorumların ne kadar yararlı olduğundan emin değilim. velis bir kod incelemesi istemiyordu.
Jacob Lee

Yanıtlar:


191

Korkarım, genel olarak ithalat döngülerini halletmenin çok zarif bir yolu yok. Seçimleriniz, döngüsel bağımlılığı ortadan kaldırmak için kodunuzu yeniden tasarlamak ya da mümkün değilse, bunun gibi bir şey yapmaktır:

# 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_CHECKINGSabiti daimaFalse ithalat değerlendirmeye alınmayacaktır, ancak mypy (ve diğer tip denetimi araçları) o bloğun içeriğini değerlendirecek böylece, çalışma zamanında.

Ayrıca Main, Mainsembol çalışma zamanında mevcut olmadığından , tür açıklamasını bir dizge haline getirmemiz ve bunu etkili bir şekilde ileri doğru bildirmemiz gerekir .

Python 3.7+ kullanıyorsanız, en azından yararlanarak açık bir dize ek açıklama sağlamak zorunda atlayabilirsiniz 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İthalat yapacak tüm dizeleri be tipi ipuçları ve bunları değerlendirerek atlayın. Bu, kodumuzu biraz daha ergonomik hale getirmeye yardımcı olabilir.

Tüm söylenenler, mypy ile mixins kullanmak muhtemelen şu anda sahip olduğunuzdan biraz daha fazla yapı gerektirecektir. Mypy bir yaklaşım önerir temelde ne decezehem dair bir ABC yaratmak - anlatıyor Mainve MyMixinsınıfları devralır. Pycharm'ın pulunu mutlu etmek için benzer bir şey yapma ihtiyacı duyarsan şaşırmam.


4
Bunun için teşekkürler. Mevcut python typing3.4'ümde yok , ancak PyCharm da oldukça memnun kaldı if False:.
velis

Tek sorun MyObject'i bir Django modeli olarak tanımamasıdır.Model ve bu nedenle__init__
velis

İşte karşılık gelen moral typing. TYPE_CHECKING : python.org/dev/peps/pep-0484/#runtime-or-type-checking
Conchylicultor

32

Sınıfı yalnızca Tür denetimi için içe aktarırken döngüsel içe aktarmayla mücadele eden kişiler için: Büyük olasılıkla bir İleri Referans (PEP 484 - Tür İpuçları) kullanmak isteyeceksiniz :

Bir tür ipucu henüz tanımlanmamış isimler içerdiğinde, bu tanım daha sonra çözülecek bir dize değişmezi olarak ifade edilebilir.

Yani bunun yerine:

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

Yapmalısın:

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

PyCharm olabilir. En yeni sürümü mü kullanıyorsunuz? Denedin File -> Invalidate Cachesmi
Tomasz Bartkowiak

Teşekkürler. Üzgünüm, yorumumu sildim. Bunun işe yaradığından bahsetmişti, ancak PyCharm şikayet ediyor. Velis'in önerdiği if False hack'i kullanarak çözdüm . Önbelleğin geçersiz kılınması sorunu çözmedi. Muhtemelen bir PyCharm sorunudur.
Jacob Lee

2
Yerine @JacobLee if False:şunları da yapabilirsiniz from typing import TYPE_CHECKINGve if TYPE_CHECKING:.
luckydonald

12

Daha büyük sorun, tiplerinizin başlangıçta aklı başında olmamasıdır. MyMixiniçine karıştırılacağına dair sabit kodlanmış bir varsayım yapar Main, oysa herhangi bir sayıda başka sınıfa karıştırılabilir, bu durumda muhtemelen kırılır. Karışımınız belirli bir sınıfa karıştırılacak şekilde kodlanmışsa, yöntemleri ayırmak yerine doğrudan bu sınıfa yazabilirsiniz.

Bunu aklı başında yazımla düzgün bir şekilde yapmak için, MyMixinbir arayüze veya Python dilinde soyut bir sınıfa göre kodlanmalıdır :

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
Pekala, çözümümün harika olduğunu söylemiyorum. Kodu daha yönetilebilir hale getirmek için yapmaya çalıştığım şey bu. Öneriniz geçebilir, ancak bu aslında benim özel durumumda tüm Main sınıfını arayüze taşımak anlamına gelir .
velis

3

İlk denememin de çözüme oldukça yakın olduğu ortaya çıktı. Şu anda kullandığım şey bu:

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

İçe if Falseaktarılmayan (ancak IDE bunu zaten bilir) ve Mainçalışma zamanında bilinmediği için sınıfı dize olarak kullandığına dikkat edin .


Bunun ölü kod hakkında bir uyarıya neden olmasını bekliyorum.
Phil

@Phil: evet, o sırada Python 3.4 kullanıyordum. Şimdi
typing var.TYPE_CHECKING

-4

Bence mükemmel yol, tüm sınıfları ve bağımlılıkları bir dosyaya (gibi __init__.py) ve sonra from __init__ import *diğer tüm dosyalarda içe aktarmak olmalıdır .

Bu durumda sen

  1. bu dosyalara ve sınıflara birden fazla referanstan kaçınmak ve
  2. ayrıca diğer dosyaların her birine yalnızca bir satır eklemelisiniz ve
  3. üçüncüsü, kullanabileceğiniz tüm sınıfları bilen pycharm olacaktır.

1
her yerde yüklediğiniz anlamına gelir, oldukça ağır bir kitaplığınız varsa bu, her içe aktarma için tüm kitaplığı yüklemeniz gerektiği anlamına gelir. + referans süper yavaş çalışacaktır.
Omer Shacham

> her yerde yükleme yaptığınız anlamına gelir. >>>> " init .py" veya başka dosyalarınız varsa ve kaçınmanız durumunda kesinlikle hayır import *ve yine de bu kolay yaklaşımdan yararlanabilirsiniz
Sławomir Lenart
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.