1 puan yazan GN⁺ 1 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • PEP 661, None geçerli bir değer olduğunda ondan ayrı biçimde ayırt edilebilen sentinel değerleri oluşturmak için Python yerleşik çağrılabilir nesnesi sentinel() ile C API PySentinel_New() işlevini önerir
  • Mevcut _sentinel = object() deyimi, fonksiyon imzalarında repr çıktısının uzun ve belirsiz olmasına yol açar; ayrıca açık tip imzası, kopyalama ve pickle işlemlerinde sorun çıkarabilir
  • sentinel('MISSING') çağrısı, kısa bir repre sahip yeni ve benzersiz bir nesne oluşturur; aynı sentinelin paylaşılması gerekiyorsa MISSING = sentinel('MISSING') örneğindeki gibi bir değişkene atanıp açıkça yeniden kullanılmalıdır
  • Sentinel değerlerinin is ile karşılaştırılması önerilir ve bunlar truthy olarak değerlendirilir; copy.copy() ile copy.deepcopy() aynı nesneyi döndürür ve bir modülden adıyla import edilebildiğinde pickle sonrası da kimliğini korur
  • Tip sistemi, int | MISSING gibi ifadelerde sentinelin kendisinin tip ifadesi olarak kullanılmasına izin verir; en güncel resmi belge ise Python 3.15’teki [sentinel](<https://docs.python.org/3.15/library/functions.html#sentinel "(in Python v3.15>)") dokümantasyonudur

Ortaya çıkış arka planı

  • Benzersiz bir yer tutucu olan sentinel değeri (sentinel value), fonksiyon argümanı verilmediğinde kullanılacak varsayılan değer, arama başarısızlığını gösteren dönüş değeri veya eksik veriyi gösteren değer gibi amaçlarla kullanılır
  • Python’da bu tür kullanım için genellikle özel değer None bulunur; ancak None’ın kendisinin de geçerli bir değer olduğu bağlamlarda, Nonedan ayrılan ayrı bir sentinel değerine ihtiyaç duyulur
  • Mayıs 2021’de python-dev posta listesinde, traceback.print_exception içinde kullanılan sentinel değerini daha iyi uygulamanın yolları tartışıldı
  • Mevcut uygulama, yaygın bir deyim olan _sentinel = object() yaklaşımını kullanıyordu; ancak repr çıktısı aşırı uzun ve yetersiz bilgi verdiği için fonksiyon imzasını okumayı zorlaştırıyordu
    &gt;&gt;&gt; help(traceback.print_exception)  
    Help on function print_exception in module traceback:  
    
    print_exception(exc, /, value=&lt;object object at  
    0x000002825DF09650&gt;, tb=&lt;object object at 0x000002825DF09650&gt;,  
    limit=None, file=None, chain=True)  
    
  • Tartışma sırasında mevcut sentinel uygulamalarındaki başka sorunlar da ortaya çıktı
    • Bazı sentinellerin kendine özgü bir tipi yoktur; bu yüzden sentinelin varsayılan değer olarak kullanıldığı fonksiyonlar için açık tip imzası tanımlamak zordur
    • Kopyalama sonrası ayrı bir örnek oluştuğu için is karşılaştırması başarısız olabilir ve beklenmedik davranışlar görülebilir
    • Bazı yaygın deyimlerde, pickle sonrası unpickle edildiğinde de benzer sorunlar yaşanır
  • Victor Stinner, Python standart kütüphanesinde kullanılan sentinel değerlerinin bir listesini sundu; bunun sonucunda standart kütüphanenin içinde bile farklı uygulama yöntemlerinin kullanıldığı ve birçoğunun yukarıdaki sorunlardan en az birine sahip olduğu görüldü
  • discuss.python.org üzerindeki oylama, 39 oyla net bir sonuca ulaşamadı
    • %40, “mevcut durum yeterince iyi ve tutarlılık gerekli değil” seçeneğini işaretledi
    • Çoğunluk, bir veya daha fazla standartlaştırılmış çözümü seçti
    • %37, “yeni bir özel sentinel factory/class/metaclass yapısının tutarlı biçimde kullanılması ve standart kütüphanede açıkça sunulması” seçeneğini tercih etti
  • Bu karışık sonuçlar nedeniyle PEP yazıldı ve basit, iyi bir standart kütüphane uygulamasının hem standart kütüphane içinde hem de dışında yararlı olacağı sonucuna varıldı
  • Standart kütüphanedeki mevcut tüm sentinellerin bu yöntemle değiştirilmesi zorunlu değildir; karar ilgili bakımcıların takdirine bırakılmıştır
  • PEP belgesi tarihsel bir belgedir; en güncel resmi belge Python 3.15’teki [sentinel](<https://docs.python.org/3.15/library/functions.html#sentinel "(in Python v3.15>)") dokümantasyonudur

Tasarım ölçütleri

  • Sentinel nesnesi, is işleciyle karşılaştırıldığında her zaman kendisiyle aynı, diğer tüm nesnelerden farklı olmalıdır
  • Sentinel nesnesi oluşturma işlemi basit ve sezgisel bir tek satırlık kod olmalıdır
  • Gerektiği kadar çok farklı sentinel değer kolayca tanımlanabilmelidir
  • Sentinel nesnesi kısa ve açık bir repre sahip olmalıdır
  • Sentinel için açık bir tip imzası kullanılabilmelidir
  • Kopyalama sonrasında da doğru çalışmalı, pickle ve unpickle sırasında öngörülebilir davranış sergilemelidir
  • CPython 3.x ve PyPy3 üzerinde çalışmalı, mümkünse diğer Python uygulamalarında da çalışmalıdır
  • Hem uygulama hem kullanım olabildiğince basit ve sezgisel olmalı; Python öğrenirken ek bir özel kavram yükü oluşturmamalıdır
  • Standart kütüphane sentinels veya sentinel gibi PyPI paketlerinin uygulamalarına bağımlı olamayacağından, standart kütüphane içinde kullanılabilecek bir uygulamaya ihtiyaç vardır

sentinel() özellikleri

  • Yeni yerleşik çağrılabilir nesne sentinel eklendi
    >>> MISSING = sentinel('MISSING')  
    >>> MISSING  
    MISSING  
    
  • sentinel() yalnızca konumsal olan tek bir name argümanı alır ve name mutlaka str olmalıdır
  • Dize olmayan bir değer verilirse TypeError oluşur
  • name, sentinel'in adı ve repr değeri olarak kullanılır
  • Sentinel nesnesinin iki herkese açık niteliği vardır
    • __name__: sentinel adı
    • __module__: sentinel() çağrısının yapıldığı modül adı
  • sentinel alt sınıflanamaz
  • sentinel(name) her çağrıldığında yeni bir sentinel nesnesi döndürür
  • Aynı sentinel birden fazla yerde kullanılacaksa, mevcut MISSING = object() kalıbında olduğu gibi bir değişkene atanmalı ve aynı nesne açıkça yeniden kullanılmalıdır
    MISSING = sentinel('MISSING')  
    
    def read_value(default=MISSING):  
        ...  
    
  • Belirli bir değerin sentinel olup olmadığını denetlerken, None için olduğu gibi is operatörünün kullanılması önerilir
  • == karşılaştırması da yalnızca kendisiyle karşılaştırıldığında True döndürecek şekilde beklendiği gibi çalışır
  • if value is MISSING: gibi kimlik denetimleri, genellikle if value: veya if not value: gibi boolean denetimlerinden daha uygundur
  • Sentinel nesneleri truthy'dir; yani boolean değerlendirmesinin sonucu True olur
    • Bu, rastgele bir sınıfın varsayılan davranışıyla ve Ellipsis'in boolean değeriyle aynıdır
    • Falsy olan None'dan farklıdır
  • Bir sentinel nesnesi copy.copy() veya copy.deepcopy() ile kopyalanırsa aynı nesne döndürülür
  • Tanımlandığı modülden adıyla içe aktarılabilen sentineller, standart pickle mekanizmasına göre pickle ve unpickle sonrasında da kimliğini korur
    MISSING = sentinel('MISSING')  
    assert pickle.loads(pickle.dumps(MISSING)) is MISSING  
    
  • sentinel(), sentinel oluşturulurken çağrıldığı modülü __module__ niteliğine kaydeder
  • Pickle işlemi sentinel'i modül ve ad ile kaydeder; unpickle ise modülü içe aktardıktan sonra sentinel'i adıyla alır
  • Modül ve ad üzerinden içe aktarılamayan sentineller, örneğin yerel kapsamda oluşturulup modül geneline veya sınıf niteliğindeki eşleşen bir ada atanmamış sentineller, pickle edilemez
  • Sentinel nesnesinin repr değeri, sentinel() fonksiyonuna verilen name olur; örtük bir modül niteleyicisi eklenmez
  • Nitelikli bir repr gerekiyorsa bu, ada açıkça eklenmelidir
    >>> MyClass_NotGiven = sentinel('MyClass.NotGiven')  
    >>> MyClass_NotGiven  
    MyClass.NotGiven  
    
  • Sentinel nesnelerinin sıralama karşılaştırmaları tanımlı değildir
  • Sentinel'ler weakref desteği sunmaz

Tip belirtimi

  • Tip belirtilmiş Python kodunda sentinel kullanımını açık ve basit hale getirmek için, tip sistemine sentinel nesneleri için özel işlem eklenir
  • Sentinel nesneleri, tip ifadeleri içinde kendilerini temsil eden bir değer olarak kullanılabilir
  • Bu, mevcut tip sisteminde None'ın ele alınış biçimine benzerdir
    MISSING = sentinel('MISSING')  
    
    def foo(value: int | MISSING = MISSING) -> int:  
        ...  
    
  • Tip denetleyicileri, NAME = sentinel('NAME') biçimindeki sentinel oluşturmayı yeni bir sentinel nesnesi oluşturma olarak tanımalıdır
  • sentinel() fonksiyonuna verilen ad, atamanın sol tarafındaki adla eşleşmiyorsa tip denetleyicisi hata vermelidir
  • Bu söz dizimiyle tanımlanan sentineller tip ifadelerinde kullanılabilir
  • İlgili sentinel tipi, üye olarak yalnızca sentinel nesnesinin kendisini içeren bir tam statik tipi temsil eder
  • Tip denetleyicileri, is ve is not operatörlerini kullanarak sentinel içeren union tiplerini daraltmayı desteklemelidir
    from typing import assert_type  
    
    MISSING = sentinel('MISSING')  
    
    def foo(value: int | MISSING) -> None:  
        if value is MISSING:  
            assert_type(value, MISSING)  
        else:  
            assert_type(value, int)  
    
  • Çalışma zamanı uygulaması, tip ifadelerinde kullanımı desteklemek için __or__ ve __ror__ metotlarına sahip olmalı; bu metotlar typing.Union nesnesi döndürmelidir
  • Typing Council, bu önerinin tiple ilgili kısmını destekliyor

C API

  • Sentinel'ler C uzantılarında da yararlı olabileceğinden, iki yeni C API fonksiyonu öneriliyor
  • PyObject *PySentinel_New(const char *name, const char *module_name) yeni bir sentinel nesnesi oluşturur
  • bool PySentinel_Check(PyObject *obj) bir nesnenin sentinel olup olmadığını denetler
  • C kodu, belirli bir sentinel olup olmadığını denetlerken == operatörünü kullanabilir

Uyumluluk ve güvenlik

  • Yeni bir yerleşik ad eklendiğinde, çıplak sentinel adının şu anda NameError verdiğini varsayan kod artık aynı sonucu görmez
  • Bu, yeni yerleşik adlar eklenirken genel olarak ortaya çıkan bir uyumluluk değerlendirmesidir
  • Zaten var olan yerel, global veya içe aktarılmış sentinel adları etkilenmez
  • Halihazırda sentinel adını kullanan kodların yeni yerleşik nesneyi kullanacak şekilde uyarlanması gerekebilir ve yerleşik adlarla çakışmaları uyaran linter'lar yeni uyarılar verebilir
  • Yeni yerleşik işlevsellik için genel dokümantasyon yöntemleri olan docstring'ler, kütüphane belgeleri ve “What’s New” bölümü yeterli kabul edilir
  • Bu önerinin güvenlik açısından bir etkisi olmadığı değerlendiriliyor

Referans uygulama ve backport

  • Referans uygulama, CPython pull request'i [10] ile sunuluyor
  • Önceki referans uygulama, ayrı bir GitHub deposunda [7] yer alıyor
  • Amaçlanan davranışın taslağı şu şekilde
    class sentinel:
        """Unique sentinel values."""
    
        __slots__ = ("__name__", "_module_name")
    
        def __init_subclass__(cls):
            raise TypeError("type 'sentinel' is not an acceptable base type")
    
        def __init__(self, name, /):
            if not isinstance(name, str):
                raise TypeError("sentinel name must be a string")
            self.__name__ = name
            self._module_name = sys._getframemodulename(1)
    
        @property
        def __module__(self):
            return self._module_name
    
        def __repr__(self):
            return self.__name__
    
        def __reduce__(self):
            return self.__name__
    
        def __copy__(self):
            return self
    
        def __deepcopy__(self, memo):
            return self
    
        def __or__(self, other):
            return typing.Union[self, other]
    
        def __ror__(self, other):
            return typing.Union[other, self]
    
    • typing-extensions modülünde bir backport bulunuyor, ancak bu şu anda PEP'in yinelemeli sürümünün davranışıyla tam olarak eşleşmiyor

Reddedilen alternatifler

  • NotGiven = object() kullanımı

    • Bu yaklaşım, PEP’in tasarım ölçütlerinde ele alınan tüm dezavantajlara sahiptir
    • repr uzun ve net değildir, tip imzasını açık hale getirmek zordur ve kopyalama veya pickling ile ilgili sorunlar ortaya çıkabilir
  • MISSING veya Sentinel gibi tek bir yeni sentinel değeri eklemek

    • Tek bir değer birden çok yerde birden çok amaçla kullanılırsa, bazı kullanım senaryolarında bu değerin kendisinin geçerli bir değer olmayacağından her zaman emin olmak zordur
    • Amaca özel, birbirinden farklı sentinel değerleri, olası edge case’ler hesaba katılmadan daha güvenle kullanılabilir
    • Sentinel değerleri, kullanım bağlamına uygun anlamlı bir ad ve repr sağlayabilmelidir
    • Bu seçenek oylamada yalnızca %12 seçilerek çok düşük popülerlikte kaldı
  • Mevcut Ellipsis sentinel değerini kullanmak

    • Ellipsis başlangıçta böyle bir amaç için tasarlanmış bir değer değildir
    • pass yerine boş sınıf veya fonksiyon blokları tanımlamak için daha sık kullanılmaya başlansa da, amaca özel farklı sentinel değerleri kadar her durumda güvenle kullanılamaz
  • Tek değerli Enum kullanmak

    • Önerilen deyim şu şekildedir
    class NotGivenType(Enum):  
      NotGiven = &#039;NotGiven&#039;  
      NotGiven = NotGivenType.NotGiven  
    
  • Tekrar gereksiz derecede fazladır ve repr, &lt;NotGivenType.NotGiven: &#039;NotGiven&#039;&gt; gibi aşırı uzundur
  • Daha kısa bir repr tanımlanabilir, ancak bu da kodu ve tekrarı daha da artırır
  • Oylamadaki 9 seçenek arasında oy alamayan tek seçenekti ve en az popüler olanıydı
  • Sentinel sınıfı dekoratörü

    • Önerilen deyim şu şekildedir
      @sentinel  
      class NotGivenType: pass  
      NotGiven = NotGivenType()  
      
    • Dekoratörün uygulanması kendi başına basit ve açık olabilir, ancak deyim fazla ayrıntılı, tekrarlı ve hatırlaması zordur
  • Sınıf nesnesi kullanmak

    • Sınıflar özünde singleton olduğundan, sentinel değer olarak kullanılmaları fikri mümkündür
    • En basit biçim şu şekildedir
      class NotGiven: pass  
      
      • Net bir repr elde etmek için bir metaclass veya sınıf dekoratörü gerekir
      class NotGiven(metaclass=SentinelMeta): pass  
      
      @Sentinel  
      class NotGiven: pass  
      
    • Sınıfları bu şekilde kullanmak alışılmadık olduğundan kafa karıştırıcı olabilir
    • Yorum olmadan kodun niyetini anlamak zordur ve sentinel’in callable hale gelmesi gibi beklenmedik, istenmeyen davranışlar ortaya çıkar
  • Uygulama olmadan yalnızca önerilen standart deyimi tanımlamak

    • Yaygın mevcut deyimlerin çoğu önemli dezavantajlara sahiptir
    • Şimdiye kadar bu dezavantajlardan kaçınırken açık ve kısa bir deyim bulunamamıştır
    • İlgili oylamada deyim önerisi seçeneği düşük popülerlikte kaldı ve en çok oy alan seçenek bile %25’te kaldı
  • Yeni bir standart kütüphane modülü kullanmak

    • İlk taslak, yeni bir sentinels veya sentinellib modülüne Sentinel sınıfı eklenmesini öneriyordu
    • Tek bir herkese açık callable nesne için yeni bir modül eklemek gereksizdir
    • Modül kullanımı, mevcut object() deyimine göre özelliğin kullanımını daha zahmetli hale getirir
    • Steering Council de bunun object() kadar kolay kullanılabilmesi için özellikle built-in bir özellik olmasını tavsiye etti
    • sentinels adı, zaten aktif olarak kullanılan bir PyPI paketiyle çakışır; bunu built-in yapmak ad sorununu önler
  • Modül bazlı sentinel ad kayıt defteri kullanmak

    • İlk taslak, sentinel adlarının modül içinde benzersiz olmasını önermişti
    • Bu tasarımda, aynı modülde sentinel(&quot;MISSING&quot;) tekrar tekrar çağrıldığında, modül adı ve sentinel adını anahtar olarak kullanan süreç genelindeki bir kayıt defteri üzerinden aynı nesne döndürülür
    • Bu davranış fazla örtük bulunduğu için reddedildi
    • Paylaşılan bir sentinel gerekiyorsa, mevcut MISSING = object() örneğinde olduğu gibi bir tane açıkça tanımlanıp adıyla yeniden kullanılabilir
    • Yerel scope’ta, her çağrıda veya her tekrarda yeni bir sentinel istenebilir; bu nedenle sentinel(name) tekrar çağrıları, object() tekrar çağrıları gibi birbirinden farklı nesneler üretmelidir
    • Kayıt defteri kaldırıldığında uygulama ve zihinsel model daha basit hale gelir ve geriye yalnızca sentinel(name) ifadesinin repr değeri name olan yeni, benzersiz bir nesne oluşturduğu kuralı kalır
  • Modül adını otomatik keşfetmek veya iletmek

    • İlk taslak, kayıt defteri tabanlı tasarımı desteklemek için isteğe bağlı bir module_name argümanı öneriyordu
    • Kayıt defteri kaldırılınca, herkese açık module_name argümanı artık temel öneri için gerekli değildir
    • Uygulama, TypeVar benzeri şekilde, pickle’ın import edilebilir sentinel’leri modül ve ad üzerinden serileştirebilmesi için çağıran modülü dahili olarak kaydeder
    • Dahili modül adı, sentinel’in repr değerini etkilemez
    • Modül adı veya sınıf adını içeren bir repr istenirse, sentinel(&quot;mymodule.MISSING&quot;) örneğinde olduğu gibi tek name argümanı içinde açıkça belirtilebilir
  • repr özelleştirmesine izin vermek

    • Bunun bir avantajı, mevcut sentinel değerlerin repr değişmeden bu yaklaşıma taşınabilmesiydi
    • Ancak ek karmaşıklığa değmediği düşünülerek dışarıda bırakıldı
  • Boolean değerlendirmesinin özelleştirilmesine izin vermek

    • Tartışmalarda, sentinel’in açıkça truthy, falsy veya bool dönüşümüne kapalı hale getirilebilmesi seçeneği ele alındı
    • Bazı üçüncü taraf sentinel’ler, falsy davranışı herkese açık API’nin bir parçası olarak sunar
    • Birçok katılımcı, boolean bağlamında istisna fırlatmanın kimlik denetimini daha iyi zorladığını düşündü
    • PEP, sıradan nesnelerin varsayılan truthy davranışını koruyup kimlik denetimini önermesi bakımından ilk öneriyi daha basit tuttu
    • Özelleştirilebilir boolean davranışı, ek API ve tür belirtme karmaşıklığına değdiği düşünülürse daha sonra değerlendirilebilir
  • Tip anotasyonlarında typing.Literal kullanmak

    • Tartışmalarda birçok kişi bunu önerdi ve PEP de başlangıçta bu yaklaşımı benimsedi
    • Ancak Literal[&quot;MISSING&quot;], sentinel değeri MISSING için ileri referans değil, string değeri &quot;MISSING&quot; anlamına geldiğinden kafa karışıklığı yaratabilir
    • Çıplak ad kullanımı da tartışmalarda sıkça önerildi
    • Çıplak ad yaklaşımı, None’ın oluşturduğu emsali ve iyi bilinen deseni izler; import gerektirmez ve çok daha kısadır

Ek kullanım yönergeleri

  • Sentinel’i sınıf scope’unda tanımlarken, ad çakışmasını önlemek isterken veya nitelikli bir repr daha açık olacaksa, istenen nitelikli adı açıkça iletmek gerekir
    &gt;&gt;&gt; class MyClass:  
    ...    NotGiven = sentinel(&#039;MyClass.NotGiven&#039;)  
    &gt;&gt;&gt; MyClass.NotGiven  
    MyClass.NotGiven  
    
  • Fonksiyon veya metot içinde sentinel oluşturulmasına izin verilir
  • Her sentinel() çağrısı farklı bir nesne oluşturduğundan, yerel scope’ta oluşturulan sentinel’ler o scope içinde object() çağrısıyla oluşturulan değerler gibi davranır
  • NotImplemented’ın boolean değeri True’dur, ancak Python 3.9’dan beri bunun kullanımı kullanımdan kaldırılma sürecindedir ve deprecation warning üretir
  • Bu kullanımdan kaldırma, bpo-35712 [8] içinde açıklanan NotImplemented’a özgü sorunlardan kaynaklanır
  • Birden fazla ilişkili sentinel değeri tanımlamanız veya bunlar arasında bir sıralama tanımlamanız gerekiyorsa, Enum ya da benzeri bir yaklaşım kullanılmalıdır
  • Bu tür sentinel’lerin type annotation’ı hakkında, typing-sig posta listesinde [9] çeşitli seçenekler tartışılmıştır

1 yorum

 
GN⁺ 1 시간 전
Lobste.rs görüşleri
  • Seçilen adın anlam olarak fazla dar olması tuhaf hissettiriyor
    Sadece isme bakınca benzersiz sembol gibi bir şeyin daha esnek bir temel yapı taşı olması beklenirdi. Pratikte neredeyse sembol gibi davranacakları için o şekilde kullanılabilirler, ama buna “Sentinels” denmesi kulağa garip geliyor. Belki de Lisp’e alışık olduğum için böyle hissediyorum

    • Amaç, SENTINEL_A ile SENTINEL_B'nin farklı tipler olmasını sağlamak gibi görünüyor; böylece bir değerin is_a SENTINEL_A olup olmadığı sorulabiliyor
      Ruby sembolleri böyle çalışmaz: :beef.is_a? :droog.class #=> true
    • Lisp tarzı düşünce burada doğru. Geniş amaçlı kullanımın arzu edilir olduğu ve çözülmesi gereken bir sorun olduğu varsayılıyor, ama Python’da Lisp sembollerinin çoğu kullanım senaryosu için zaten Literal ve literal string’ler var
      Bunlara adlandırılmış sentinel denmesinin nedeni, sentinel values'ın Python’da yaygın bir kavram ve kalıp olması ve sentinel’lerin bu kalıbın kullanımından doğan bazı sorunları dar kapsamda çözmeye çalışması. “Motivation” ve “Rationale” bölümlerinde anlatıldığı gibi
      Ayrıca sentinel’lerin değer semantiği yoktur; bu yüzden aynı adlı iki sentinel bile farklı değerlerdir ve birbirine eşit değildir. Dolayısıyla semboller gibi davranmazlar ve o şekilde kullanılmamalılar
  • Adlandırılmış argümanların varsayılan değeri sorununda, Typst’te none yanında sadece auto değeri eklenmesi bile istenen adlandırılmış argüman arayüzlerinin neredeyse tamamını ifade etmeye yeter
    Yalnızca none, çoğu adlandırılmış argüman varsayılanı için anlamsal olarak pek uygun düşmüyor. none varsayılan dönüş değeri olarak iyi olabilir, ama fonksiyon argümanı olduğunda çoğu zaman bir isim olarak doğru anlamı taşımıyor. matrix(axes=None) eksenleri kaldırmak mı demek, yoksa her zamanki gibi korumak mı, belirsiz. none geçirmekle hiçbir şey geçirmemek arasında fark olup olmadığı da net değil. Parametrenin verilmiş olup olmadığını ayırt etmek için çoklu dispatch’e gidilirse, o parametrenin davranışını belgelemek için merkezi bir yer de kayboluyor
    auto, “eldeki bilgiyle uygun olanı yap” anlamını doğrudan taşıyan harika bir varsayılan değer. auto | none imzası daha açık bir boolean gibi kullanılabilir ve T | auto | none, fonksiyonun değeri nasıl kullanacağı hakkında epey bilgi verir. Örneğin T bir color ise, auto muhtemelen beyaz/siyah gibi bir varsayılan seçmek veya üst öğeden miras almak anlamına gelir; T rengi açıkça ayarlar; none ise bağlama göre ya rengi hiç ayarlamamak ya da saydam olarak ele almak anlamına gelebilir

  • İlginç; bazı paketlerin semantiğinin nasıl değişeceğini merak ediyorum. Örneğin Item | None döndürmek yerine şöyle yazılabilir

    NOT_FOUND = sentinel("NOT_FOUND")  
    def get_item(iid: str) -> Item | NOT_FOUND: ...  
    

    Elbette birden fazla sentinel ile ek anlam da taşınabilir. Bu zaten mümkündü, ama belgelerde “resmen önerilen” bir yol yoktu. Bu, paket yazarlarını farklı bir yöne çekebilir

    MISSING_ID = sentinel("MISSING_ID")  
    MISSING_VALUE = sentinel("MISSING_VALUE")
    
    def get_item(iid: str) -> Item | MISSING_ID | MISSING_VALUE: ...  
    

    Biraz zorlama bir örnek ama bu durumda, mevcut bir ID olup bağlı bir değerin bulunmaması ile böyle bir ID’nin hiç olmaması nedeniyle başarısız olunması ayırt edilebilir. “Pythonic” yaklaşım muhtemelen istisna kullanmak olurdu, ama bu normalde Python yazarken görülenden daha çok işlevsel bir yaklaşım gibi duruyor

    • Eskiden boş bir sınıf oluşturup modül başına örnekleyerek yapılan singleton kullanımının daha temiz bir biçimi gibi görünüyor
      class _MissingId: ...
      
      MISSING_ID = _MissingId()
      
      # elsewhere  
      from ... import MISSING_ID  
      
      Symbols'u hatırlatıyor
    • PEP, birbiriyle ilişkili birden çok sentinel değer tanımlamak veya aralarında sıralama düzeni vermek istiyorsanız bunun yerine Enum ya da benzeri bir şey kullanmanız gerektiğini söylüyor
  • Bence doğrudan JavaScript’in Symbol API’sini eklemek daha iyi olurdu. Genel kullanım açısından da faydalı olurdu ve burada çözülmeye çalışılan sorunu da birlikte çözerdi