Bir yöntemin dönüş türünün sınıfın kendisiyle aynı olduğunu nasıl belirleyebilirim?


410

Ben python 3 aşağıdaki kodu var:

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

Ancak editörüm (PyCharm) referans Pozisyonunun çözülemediğini söylüyor ( __add__yöntemde). Dönüş türünün tür olmasını beklediğimi nasıl belirtmeliyim Position?

Düzenleme: Bu aslında bir PyCharm sorunu olduğunu düşünüyorum. Bilgileri uyarılarında ve kod tamamlamada kullanır

Ama yanılıyorsam beni düzeltin ve başka bir sözdizimi kullanmanız gerekiyor.

Yanıtlar:


574

TL; DR : Python 4.0 kullanıyorsanız işe yarıyor. Bugün (2019) 3.7 ve sonraki sürümlerinde bu özelliği gelecekteki bir ifadeyi ( from __future__ import annotations) kullanarak açmalısınız - Python 3.6 veya daha düşük sürümler için bir dize kullanın.

Sanırım şu istisnayı yakaladınız:

NameError: name 'Position' is not defined

Bunun nedeni Position, Python 4'ü kullanmadığınız sürece bir ek açıklamada kullanabilmeniz için tanımlanması gerektiğidir.

Python 3.7+: from __future__ import annotations

Python 3.7, PEP 563'ü sunar: ek açıklamaların ertelenmesi . Future ifadesini kullanan bir modül from __future__ import annotationsek açıklamaları otomatik olarak dize olarak saklar:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

Bunun Python 4.0'da varsayılan olması planlanmıştır. Python hala dinamik olarak yazılan bir dil olduğundan, çalışma zamanında tür denetimi yapılmadığından, ek açıklamaların performans etkisi olmaması gerekir, değil mi? Yanlış! Piton 3.7 eskiden yazarak modülü önce çekirdekte en yavaş piton modüllerinden biri böylece eğer import typinggöreceksiniz kez performansında artış 7'ye kadar sen 3.7 yükselttiğinizde.

Python <3.7: bir dize kullanın

PEP 484'e göre , sınıfın kendisi yerine bir dize kullanmalısınız:

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...

Django çerçevesini kullanırsanız, Django modelleri ayrıca ileri başvurular için dizeler kullandığından (yabancı modelin selfhenüz bildirilmediği veya henüz bildirilmediği yabancı anahtar tanımları) tanıdık gelebilir . Bu Pycharm ve diğer araçlarla çalışmalıdır.

Kaynaklar

PEP 484 ve PEP 563'ün ilgili bölümleri , seyahatinizi yedeklemek için:

İleri referanslar

Bir tür ipucu henüz tanımlanmamış adlar içerdiğinde, bu tanım daha sonra çözümlenecek bir dize hazır bilgisi olarak ifade edilebilir.

Bunun yaygın olarak meydana geldiği bir durum, tanımlanmış sınıfın bazı yöntemlerin imzasında gerçekleştiği bir konteyner sınıfının tanımıdır. Örneğin, aşağıdaki kod (basit bir ikili ağaç uygulamasının başlangıcı) çalışmaz:

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

Bunu ele almak için şunu yazıyoruz:

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

Dize değişmez değeri geçerli bir Python ifadesi içermelidir (yani, derleme (lit, '', 'eval') geçerli bir kod nesnesi olmalıdır) ve modül tamamen yüklendikten sonra hatasız olarak değerlendirilmelidir. Değerlendirildiği yerel ve global ad alanı, aynı işleve ait varsayılan bağımsız değişkenlerin değerlendirileceği ad alanları ile aynı olmalıdır.

ve PEP 563:

Python 4.0'da, işlev ve değişken ek açıklamaları artık tanımlama zamanında değerlendirilmez. Bunun yerine, ilgili __annotations__sözlükte bir dize formu korunur . Statik tip denetleyicilerin davranışlarında hiçbir fark görülmezken, çalışma zamanında ek açıklamaları kullanan araçların ertelenmiş değerlendirme yapması gerekir.

...

Yukarıda açıklanan işlevsellik, aşağıdaki özel içe aktarma kullanılarak Python 3.7'den başlayarak etkinleştirilebilir:

from __future__ import annotations

Bunun yerine yapmak isteyebileceğiniz şeyler

A. Bir kukla tanımlayın Position

Sınıf tanımından önce kukla bir tanım yerleştirin:

class Position(object):
    pass


class Position(object):
    ...

Bu kurtulur NameErrorve hatta Tamam görünebilir:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

Ama öyle mi?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

Ek açıklama eklemek için maymun yaması:

Ek açıklamalar eklemek için bazı Python meta programlama sihirlerini denemek ve sınıf tanımını maymun yaması için bir dekoratör yazmak isteyebilirsiniz:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

Dekoratör, bunun eşdeğerinden sorumlu olmalıdır:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

En azından doğru görünüyor:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

Muhtemelen çok fazla sorun var.

Sonuç

3.6 veya daha düşük bir değer kullanıyorsanız, 3.7 adını kullanarak sınıf adını içeren bir dize değişmez değeri kullanın from __future__ import annotationsve bu sadece işe yarayacaktır.


2
Doğru, bu daha az bir PyCharm sorunu ve daha çok bir Python 3.5 PEP 484 sorunu. Aynı uyarıyı mypy type aracından çalıştırırsanız alacağınızdan şüpheleniyorum.
Paul Everitt

23
> Python 4.0 kullanıyorsanız bu arada çalışıyor, Sarah Connor'ı gördün mü? :)
scrutari

@JoelBerkeley Sadece test ettim ve parametreler 3.6 üzerinde benim için çalıştı, sadece typingdize değerlendirildiğinde kullandığınız herhangi bir tür kapsamda olması gerektiği gibi içe aktarmayı unutmayın .
Paulo Scardine

ah, benim hatam, sadece ''sınıf yuvarlak koymak , tip parametreleri değil
joelb

5
Kullanan herkes için önemli not from __future__ import annotations- bu, diğer tüm içe aktarmalardan önce içe aktarılmalıdır.
Artur

16

Türü dize olarak belirtmek iyidir, ancak her zaman bana temelde ayrıştırıcıyı atlattığımızdan biraz rendeler. Yani bu değişmez dizelerden herhangi birini yanlış yazmamanız iyi olur:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Küçük bir varyasyon, bağlı bir typevar kullanmaktır, en azından typevar'ı bildirirken dizeyi yalnızca bir kez yazmanız gerekir:

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: T) -> T:
        return Position(self.x + other.x, self.y + other.y)

8
Keşke Python typing.Selfbu açıkça belirtmek için vardı .
Alexander Huszagh

2
Buraya senin gibi bir şey olup olmadığını görmek için geldim typing.Self. Sabit kodlanmış bir dizenin döndürülmesi, polimorfizmden yararlanırken doğru türü döndürmüyor. Benim durumumda serileştirilmiş bir sınıf yöntemi uygulamak istedim . Bir dict (kwargs) iade edip aramaya karar verdim some_class(**some_class.deserialize(raw_data)).
Scott P.

Burada kullanılan tür açıklamaları, bunu alt sınıfları kullanmak için doğru şekilde uygularken uygundur. Ancak, uygulama Positionsınıfı değil, döndürür , bu nedenle yukarıdaki örnek teknik olarak yanlıştır. Uygulama, bunun Position(gibi bir şeyle değiştirilmelidir self.__class__(.
Sam Bull

Ek olarak, ek açıklamalar dönüş türünün bağlı olduğunu other, ancak büyük olasılıkla aslında bağlı olduğunu söylüyor self. Bu nedenle, selfdoğru davranışı tanımlamak için ek açıklamayı koymanız gerekir (ve belki otherde yalnızca Positiondönüş türüne bağlı olmadığını göstermek için olmalıdır ). Bu aynı zamanda yalnızca üzerinde çalıştığınız durumlarda da kullanılabilir self. ör.def __aenter__(self: T) -> T:
Sam Bull

15

Sınıf gövdesinin kendisi ayrıştırılırken 'Konum' adı kullanılamaz. Tip bildirimlerini nasıl kullandığınızı bilmiyorum, ancak Python'un PEP 484 - bu yazım ipuçlarını kullanarak en çok kullanmanız gereken şey bu adı basitçe bu noktada bir dize olarak koyabileceğinizi söylüyor:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Https://www.python.org/dev/peps/pep-0484/#forward-references - buna uygun araçları kontrol edin - sınıf adını oradan açmayı ve bundan faydalanmayı bilecek. (Her zaman önemlidir Python dilinin kendisinin bu ek açıklamalardan hiçbir şey yapmadığını aklınızda bulundurun - genellikle statik kod analizi içindir veya çalışma zamanında tür denetimi için bir kütüphane / çerçeve olabilir - ancak bunu açıkça ayarlamanız gerekir).

güncelleme Ayrıca, Python 3.8'den itibaren, pep-563'ü kontrol edin - Python 3.8'den itibaren yazmak mümkündürfrom __future__ import annotations 3.8'den itibaren, ek açıklamaların değerlendirmesini ertelemek - ileri referans sınıfları doğrudan çalışmalıdır.


9

Dize tabanlı bir tür ipucu kabul edilebilir olduğunda, __qualname__öğe de kullanılabilir. Sınıfın adını tutar ve sınıf tanımının gövdesinde bulunur.

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()

Bunu yaparak, sınıfın yeniden adlandırılması, tür ipuçlarının değiştirilmesini gerektirmez. Ama şahsen akıllı kod editörlerinin bu formu iyi işlemesini beklemem.


1
Bu özellikle yararlıdır, çünkü sınıf adını sabit kodlamaz, bu nedenle alt sınıflarda çalışmaya devam eder.
Florian Brucker

Bunun ek açıklamaların ertelenmiş değerlendirmesi ile çalışıp çalışmayacağından emin değilim (PEP 563), bu yüzden bunun için bir soru sordum .
Florian Brucker
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.