2017-07-25 29 views
14

我有一個自定義類,有沒有辦法在Python中返回min和max的自定義值?

class A: 
    def __init__(self, a, b): 
     self.a = a 
     self.b = b 

類是不迭代或可轉位或類似的東西。如果可能的話,我想保持這種狀態。是否有可能有以下工作?

>>> x = A(1, 2) 
>>> min(x) 
1 
>>> max(x) 
2 

什麼讓我想起了這是minmax都在docs列爲「共同序列操作」。由於range被同一文檔視爲序列類型,因此我認爲必須進行某種優化,這對range是可能的,也許我可以利用它。

也許有一種神奇的方法,我不知道這會使這個?

+0

這篇文章可能會對你有所幫助,但我並不完全確定它會使用min/max;它可能值得一試,但:https://stackoverflow.com/questions/7875911/how-to-implement-a-minimal-class-that-behaves-like-a-sequence-in-python –

+0

@depperm in這個例子我相信'a'會以'self'的形式傳遞。使用'self'只是慣例。 – cssko

+1

你的意思是讓它在沒有定義'__iter__' /'__next__'的情況下工作嗎?我不確定我瞭解你的困惑。 –

回答

20

是的。當min接受一個參數時,它假定它是一個可迭代的,迭代它並取最小值。所以,

class A: 
    def __init__(self, a, b): 
     self.a = a 
     self.b = b 
    def __iter__(self): 
     yield self.a 
     yield self.b 

應該工作。

附加說明:如果您不想使用__iter__,我不知道該如何做。你可能想要創建你自己的min函數,如果它傳遞給它的參數中有一個函數,並且調用舊的min其他函數,它會調用一些__min__方法。

oldmin = min 
def min(*args) 
    if len(args) == 1 and hasattr(args[0], '__min__'): 
    return args[0].__min__() 
    else: 
    return oldmin(*args) 
+6

[PEP 8禁止](https://www.python.org/dev/peps/pep-0008/)創建自己的dunder方法。改用'__min'。 –

+1

更正:'_min'因爲'__min'名字被破壞。 –

+4

我錯過了什麼,爲什麼不能直接調用'min'而不是任何下劃線變​​量?它似乎不是一種「神奇」的方法或任何東西,它只是一種將在標準庫之外的其他地方調用的方法。 – Mephy

6

由於range被認爲是由非常相同的文檔是一個序列類型,我在想,一定有某種優化是可能的range,那或許我可以利用它。

有沒有優化範圍的事情,也沒有專門的魔術方法爲min/max

如果你偷看the implementation for min/max,你會看到,經過一些參數解析完成,a call to iter(obj)(即obj.__iter__())由抓住一個迭代器:

it = PyObject_GetIter(v); 
if (it == NULL) { 
    return NULL; 
} 

然後calls to next(it)(即it.__next__)以執行循環搶值比較:

while ((item = PyIter_Next(it))) { 
    /* Find min/max */ 

是否有可能有類似下面的工作?

不,如果你想使用內置的min *你唯一的選擇是實現迭代器協議。


*通過修補min,你可以的,當然,讓它做你想要的任何東西。顯然是以在Pythonland中運行爲代價的。但是,如果您認爲可以利用某些優化,我建議您創建一個min方法,而不是重新定義內置的min

此外,如果你只有整數爲實例變量,你不介意不同的呼叫,您可以隨時使用varsinstance.__dict__,然後提供它.values()min

>>> x = A(20, 4) 
>>> min(vars(x).values()) 
4 
+0

這很有道理,因爲它是令人失望的。我會詢問關於如何識別調用者的另一個問題,以便如果除min或max之外的任何人嘗試獲取迭代器,則可以引發異常。我希望你不要介意我給其他人點分數。 –

+0

@MadPhysicist我不認爲你需要爲此發佈另一個問題。除非有人閱讀這裏的評論與我不同意,否則我認爲如果你做這樣的事情來檢查它應該就足夠了:https://pastebin.com/AFNGcWHw – idjaw

+1

@MadPhysicist:不能這樣做。 (事實上​​,你甚至嘗試的是一個巨人「重新考慮你的設計決策」紅旗。) – user2357112

6

有沒有__min____max__特殊方法*。這是一種遺憾,因爲range已經看到一些pretty nice optimizations in Python 3。你可以這樣做:

>>> 1000000000000 in range(1000000000000) 
False 

但除非你要等待很長一段時間不試試這個:

>>> max(range(1000000000000)) 

但是創建自己的min/max功能是一個不錯的主意,因爲建議通過Lærne

這是我該怎麼做的。 UPDATE:贊成_min刪除dunder名__min__的建議,PEP 8

決不創造這樣的名字;只有把它們作爲記錄

代碼:

from functools import wraps 

oldmin = min 

@wraps(oldmin) 
def min(*args, **kwargs) 
    try: 
     v = oldmin(*args, **kwargs) 
    except Exception as err: 
     err = err 
    try: 
     arg, = args 
     v = arg._min() 
    except (AttributeError, ValueError): 
     raise err 
    try: 
     return v 
    except NameError: 
     raise ValueError('Something weird happened.') 

我覺得這種方式也許更好一點,因爲它處理一些角落情況下,對方的回答還沒有考慮。

請注意,具有_min方法的可迭代對象按照慣例仍將被oldmin使用,但返回值被特殊方法覆蓋。然而,如果_min方法要求迭代器仍然可用於消費,則需要調整,因爲迭代器首先被oldmin消耗。

還要注意的是,如果__min方法是簡單地通過調用oldmin實現,事情會依然正常工作(即使迭代器被消耗,這是因爲oldmin提出了在這種情況下ValueError)。 *這種方法通常被稱爲「魔術」,但這不是首選的術語。

+5

'__min'被名字弄壞了,所以命名一個方法真的很尷尬。 – user2357112

+1

@ user2357112是的,這是真的。我會把它改成'_min'。 –

相關問題