__cmp__ yerine __lt__


100

Python 2.x, karşılaştırma operatörlerini __cmp__veya "zengin karşılaştırma operatörlerini" aşırı yüklemenin iki yolu vardır __lt__. Zengin karşılaştırma aşırı yüklemelerinin tercih edildiği söyleniyor, ama bu neden böyle?

Zengin karşılaştırma operatörlerinin her birini uygulamak daha kolaydır, ancak birkaçını neredeyse aynı mantıkla uygulamalısınız. Bununla birlikte, yerleşik cmpve tuple sıralamayı kullanabilirseniz, __cmp__oldukça basitleşir ve tüm karşılaştırmaları yerine getirir:

class A(object):
  def __init__(self, name, age, other):
    self.name = name
    self.age = age
    self.other = other
  def __cmp__(self, other):
    assert isinstance(other, A) # assumption for this example
    return cmp((self.name, self.age, self.other),
               (other.name, other.age, other.other))

Bu basitlik, ihtiyaçlarımı zengin karşılaştırmaların 6'sını (!) Aşırı yüklemekten çok daha iyi karşılıyor gibi görünüyor. (Bununla birlikte, "değiş tokuş edilen argümana" / yansıyan davranışa güvenirseniz, bunu "sadece" 4'e indirebilirsin, ancak bu mütevazi görüşüme göre net bir karmaşıklık artışı ile sonuçlanır.)

Sadece aşırı yükleme yaparsam farkına varmam gereken öngörülemeyen tuzaklar var __cmp__mı?

Anlıyorum <, <=, ==vb operatörler başka amaçlar için aşırı yüklenebilir ve benzeri herhangi bir nesneyi onlar dönebilir. Bu yaklaşımın yararlarını sormuyorum, sadece bu operatörleri karşılaştırmalar için kullanırken aynı anlamıyla sayılar için kullandıkları farklılıklar hakkında soruyorum.

Güncelleme: Christopher olarak işaret , cmp3.x yok oluyor Karşılaştırma yapmayı yukarıdakiler kadar kolaylaştıran alternatifler var __cmp__mı?


5
Cevabımın son sorunuz olduğunu görün, ancak aslında sizinki de dahil olmak üzere birçok sınıf için işleri daha da kolaylaştıracak bir tasarım var (şu anda onu uygulamak için bir miksine, meta sınıfa veya sınıf dekoratörüne ihtiyacınız var): önemli bir özel yöntem varsa , bir değer demeti döndürmelidir ve tüm karşılaştırıcılar VE hash , bu demet açısından tanımlanır. Guido fikrimi ona açıkladığımda beğendi, ama sonra başka şeylerle meşgul oldum ve bir PEP yazmaya hiç vakit ayıramadım ... belki 3.2 ;-). Bu arada bunun için mixin'imi kullanmaya devam ediyorum! -)
Alex Martelli

Yanıtlar:


90

Evet, her şeyi, örneğin __lt__bir mixin sınıfı (veya bir metasınıf veya zevkiniz bu şekilde çalışıyorsa bir sınıf dekoratörü ) açısından uygulamak kolaydır .

Örneğin:

class ComparableMixin:
  def __eq__(self, other):
    return not self<other and not other<self
  def __ne__(self, other):
    return self<other or other<self
  def __gt__(self, other):
    return other<self
  def __ge__(self, other):
    return not self<other
  def __le__(self, other):
    return not other<self

Artık sınıfınız, __lt__ComparableMixin'den devralmayı sadece tanımlayabilir ve çoğaltabilir (eğer varsa, diğer temellerden sonra). Bir sınıf dekoratörü, süslediği yeni sınıfın niteliklerine benzer işlevler ekleyerek oldukça benzer olacaktır (sonuç, çalışma zamanında mikroskobik olarak daha hızlı, bellek açısından eşit derecede dakika maliyetinde olabilir).

Elbette, sınıfınızın özellikle hızlı bir uygulama yolu varsa (örneğin) __eq__ve __ne__bunları doğrudan mixin sürümlerinin kullanılmaması için tanımlaması gerekir (örneğin, durum böyledir dict) - aslında __ne__kolaylaştırmak için iyi tanımlanabilir bunun gibi:

def __ne__(self, other):
  return not self == other

ancak yukarıdaki kodda sadece <;-) kullanmanın hoş simetrisini korumak istedim . Neden olarak __cmp__biz bu yana, gitmek zorunda yaptılar var __lt__ve arkadaşlar, niçin, etrafında tam olarak aynı şeyi yapmak için farklı bir yol başka tutmak? Her Python çalışma zamanında (Classic, Jython, IronPython, PyPy, ...) çok fazla ağırlık var. Bu kod kesinlikle hata olmaz yoktur koddur - Orada bir görevi gerçekleştirmek için ideal bir açık yolu olması gerektiğini Python'un ilkesi (C "Ruh C" bölümünde aynı ilke edinmiştir nereden ISO standardı, btw).

Bu bir şeyleri yasaklamak için yolumuza çıkmak anlamına gelmez (örneğin Mixins ve bazı kullanımlar için sınıf dekoratörler arasındaki yakın denklik), ama kesinlikle yok biz derleyici ve kod taşımak sevmiyorum demek oluyor / veya tamamen aynı görevi gerçekleştirmek için birden çok eşdeğer yaklaşımı desteklemek için fazladan var olan çalışma zamanları.

Daha fazla düzenleme: Aslında soruda bulunanlar da dahil olmak üzere birçok sınıf için karşılaştırma VE karma sağlamanın daha iyi bir yolu var - soruya __key__yaptığım yorumda bahsettiğim bir yöntem. PEP'i hiçbir zaman onun için yazmaya fırsat bulamadığım için, şu anda onu bir Mixin (& c) ile uygulamalısınız, eğer isterseniz:

class KeyedMixin:
  def __lt__(self, other):
    return self.__key__() < other.__key__()
  # and so on for other comparators, as above, plus:
  def __hash__(self):
    return hash(self.__key__())

Bir örneğin diğer örneklerle karşılaştırmalarının, her biri için birkaç alanı olan bir demeti karşılaştırmaya indirgenmesi çok yaygın bir durumdur - ve daha sonra, hashing tamamen aynı temelde uygulanmalıdır. __key__Doğrudan gerek özel yöntem adresleri.


@R gecikmesi için özür dilerim. Pate, yine de düzenleme yapmak zorunda olduğum için acele etmektense verebileceğim en kapsamlı cevabı vermem gerektiğine karar verdim (ve PEPping'e hiç ulaşamadığım eski anahtar fikrimi önermek için yeniden düzenledim ve nasıl bir mixin ile uygulamak için).
Alex Martelli

Kullanacağım ve nasıl hissettirdiğini göreceğim bu anahtar fikri gerçekten beğendim . (Ayrılmış bir ad yerine cmp_key veya _cmp_key olarak adlandırılmış olsa da)

TypeError: Cannot create a consistent method resolution order (MRO) for bases object, ComparableMixinbunu Python 3'te denediğimde tam kodu gist.github.com/2696496
Adam Parkin

2
Python 2.7 + / 3.2 + 'da kendinizinkini oluşturmak functools.total_orderingyerine kullanabilirsiniz ComparableMixim. Önerildiği üzere jmagnusson cevabı
Gündüz

4
Python 3'te <uygulamak için kullanmak __eq__oldukça kötü bir fikir çünkü TypeError: unorderable types.
Antti Haapala

49

Bu durumu basitleştirmek için , Alex'in önerdiği şeyi uygulamak için kullanılabilecek Python 2.7 + / 3.2 +, functools.total_ordering'de bir sınıf dekoratörü vardır . Dokümanlardan örnek:

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

9
total_ordering__ne__yine de uygulanmıyor , bu yüzden dikkatli olun!
Flimm

3
@Flimm, öyle değil, ama __ne__. ancak bunun nedeni __ne__, yetkilendiren varsayılan uygulamaya sahip olmasıdır __eq__. Yani burada dikkat edilecek bir şey yok.
Jan Hudec

en az bir sipariş işlemi tanımlamalıdır: <> <=> = .... eq, toplam sipariş olarak gerekli değildir, a <b ve b <a sonra a = b
Xanlantos

9

PEP, NumPy kullanıcılarının A <B'nin bir dizi döndürmesini istediği şekilde, neden zengin karşılaştırmalara ihtiyaç duyulduğuyla ilgilenir.

Kesinlikle uzaklaştığını fark etmemiştim, bu beni üzüyor. (Ama bunu belirttiğiniz için teşekkürler.)

KEP ayrıca "neden" tercih edildiklerini de tartışır. Esasen verimlilikle ilgilidir: 1. Nesneniz için anlamlı olmayan işlemleri uygulamaya gerek yoktur (sırasız koleksiyonlar gibi) 2. Bazı koleksiyonların bazı karşılaştırmalar üzerinde çok verimli işlemleri vardır. Zengin karşılaştırmalar, tercümanın, onları siz tanımlarsanız bundan yararlanmasına izin verir.
Christopher

1
Re 1, Eğer mantıklı değillerse, cmp'yi uygulamayın . Re 2, her iki seçeneğe sahip olmak, hızlı bir şekilde prototip oluşturma ve test ederken gerektiğinde optimize etmenize izin verebilir. Neden kaldırıldığını kimse bana söylemiyor. (Esasen benim için geliştirici verimliliğine dönüşüyor.) Zengin karşılaştırmaların cmp geri dönüşü ile daha az verimli olması mümkün mü ? Bu bana mantıklı gelmez.

1
@R. Pate, cevabımda açıklamaya çalıştığım gibi, genellikte gerçek bir kayıp yok (çünkü bir mixin, dekoratör veya metasınıf, her şeyi sadece <dilerseniz, <dilerseniz, kolayca tanımlamanıza izin verir) ve böylece tüm Python uygulamalarının taşınması için sonsuza kadar cmp'ye geri düşen yedek kod - sadece Python kullanıcılarının bir şeyleri iki eşdeğer şekilde ifade etmesine izin vermek için - Python tanesine karşı% 100 çalışır.
Alex Martelli

2

(Yorumları dikkate almak için 17.06.2017 tarihinde düzenlendi.)

Yukarıdaki karşılaştırılabilir mixin cevabını denedim. "Yok" ile başım belaya girdi. Burada, "Yok" ile eşitlik karşılaştırmalarını ele alan değiştirilmiş bir sürüm bulunmaktadır. (Anlambilimden yoksun olarak Yok ile eşitsizlik karşılaştırmalarıyla uğraşmak için hiçbir neden görmedim):


class ComparableMixin(object):

    def __eq__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other and not other<self

    def __ne__(self, other):
        return not __eq__(self, other)

    def __gt__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return other<self

    def __ge__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other

    def __le__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not other<self    

Nasıl öyle düşünüyorsun selftekil olabilir Noneait NoneTypesenin uygulamak aynı zamanda ve en ComparableMixin? Ve gerçekten de bu tarif Python 3 için kötü.
Antti Haapala

3
selfolacaktır asla olmak Noneo dal tamamen gidebilir. Kullanmayın type(other) == type(None); basitçe kullanın other is None. Daha çok özel mahfaza dışında Nonebaşka tip türüne bir örnek ise, testi self, ve geri dönüş NotImplementedSingleton değilse: if not isinstance(other, type(self)): return NotImplemented. Bunu tüm yöntemler için yapın. Python daha sonra diğer işlenene bunun yerine bir cevap verme şansı verebilir.
Martijn Pieters

1

Alex Martelli'nin ComparableMixinve KeyedMixinyanıtlarından esinlenerek , aşağıdaki karışımı oluşturdum. _compare_to()Benzer anahtar tabanlı karşılaştırmalar kullanan tek bir yöntem uygulamanıza KeyedMixinizin verir, ancak sınıfınızın türüne göre en verimli karşılaştırma anahtarını seçmesine olanak tanır other. (Bu karışımın, eşitlik açısından test edilebilen ancak düzen için test edilemeyen nesneler için fazla yardımcı olmadığını unutmayın).

class ComparableMixin(object):
    """mixin which implements rich comparison operators in terms of a single _compare_to() helper"""

    def _compare_to(self, other):
        """return keys to compare self to other.

        if self and other are comparable, this function 
        should return ``(self key, other key)``.
        if they aren't, it should return ``None`` instead.
        """
        raise NotImplementedError("_compare_to() must be implemented by subclass")

    def __eq__(self, other):
        keys = self._compare_to(other)
        return keys[0] == keys[1] if keys else NotImplemented

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        keys = self._compare_to(other)
        return keys[0] < keys[1] if keys else NotImplemented

    def __le__(self, other):
        keys = self._compare_to(other)
        return keys[0] <= keys[1] if keys else NotImplemented

    def __gt__(self, other):
        keys = self._compare_to(other)
        return keys[0] > keys[1] if keys else NotImplemented

    def __ge__(self, other):
        keys = self._compare_to(other)
        return keys[0] >= keys[1] if keys else NotImplemented
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.