2014-04-09 66 views
0

我正在寫一個測試系統,使用,除其他外,數據源。運行時,它會讀取一堆儀器,但爲了測試和開發後端,我希望它讀取文件或返回隨機數。將來,我知道需要創建新的數據源,但其操作仍未知。我試圖設置這個系統,以便我可以進入一個新的部門,而不必回過頭來支持它,所以想成爲Pythonic並儘可能少地做出讓步。主要的要求是爲源代碼提供一致的API,而ABC似乎是這裏的明顯選擇。源之間沒有足夠的共同點來在基類中有任何有價值的塊被繼承。鴨子班,ABC,繼承,__new__

我不想要一個大型的源代碼模塊來選擇要做什麼,我需要可以從中選擇的小型獨立源代碼,因此可以單獨使用那些可以工作的舊代碼。問題是,我希望能夠選擇使用哪個參數,所以我可以輕鬆運行相同的測試腳本並切換源代碼。我完全忘記了我是如何碰到__new__的,但它並不明顯,其他人也很少聽說過它。有用。但是,顯而易見的或Pythonic的方式來做我想做的事情?有沒有辦法讓我的同事們更熟悉?我應該指出,我現在的工作時間略高於元編程的舒適水平,所以任何更復雜的東西都可能會讓我頭暈目眩。

from abc import ABCMeta, abstractmethod 
import random 

class BaseSource: 
    __metaclass__ = ABCMeta 

    @abstractmethod 
    def get(self): 
     pass  

class ManualSrc(BaseSource): 
    def get(self): 
     return float(raw_input('gimme a number - ')) 

class RandomSrc(BaseSource): 
    def get(self): 
     return random.random() 

class Source(BaseSource): 
    """generic Source choice""" 
    def __new__(BaseSource, choice): 
     if choice == 0: 
      return ManualSrc() 
     elif choice == 1: 
      return RandomSrc() 
     else: 
      raise ValueError('source choice parameter {} not valid'.format(choice)) 

if __name__ == '__main__': 
    for use_src in range(4): 
     print 'using source choice {}'.format(use_src) 
     src = Source(use_src) 
     print src.get() 

回答

1

這不是一個很好的答案......更多的是代碼審查,所以我可能會等待不同的意見。

我有(個人而言......不客觀的確認這裏)看到__new__通常使用的,當你用自己的__metaclass__(ES)(檢查SO this answerthis great thread了Python的元類)

創造 class實例

在你的榜樣,因爲如果你添加你要需要編輯Source類的__new__方法反正新源(新WhateverSrc()啄),它看起來就像是一個有點矯枉過正使用一個類從BaseSource繼承創造其他來源。此外,問題是:Source類真的是BaseSource據我瞭解,不是真的... Source是一個工廠的來源,對不對?如果是這樣的話,你可以試試this implementation,如果你願意的話(鏈接是我在第二段中提到的答案,所以我沒有太多的功勞來「發現」它),儘管工廠對我來說聽起來很Java。再次,這裏只是一個個人的觀點。

取而代之的是Source(BaseSource)類,你有它存在的方式,我會用一個簡單的方法create_source走:

## [ . . . ] 

class RandomSrc(BaseSource): 
    def get(self): 
     return random.random() 

def create_source(choice): 
    if choice == 0: 
     return ManualSrc() 
    elif choice == 1: 
     return RandomSrc() 
    else: 
     raise ValueError('source choice parameter {} not valid'.format(choice)) 

if __name__ == '__main__': 
    for use_src in range(4): 
     print 'using source choice {}'.format(use_src) 
     src = create_source(use_src) 
     print src.get() 

如果你需要一個新的來源,你會編輯create_source方法,如:

## [ . . . ] 

class RandomSrc(BaseSource): 
    def get(self): 
     return random.random() 

class WhateverSrc(BaseSource): 
    def get(self): 
     return "Foo Bar??" 

def create_source(choice): 
    if choice == 0: 
     return ManualSrc() 
    elif choice == 1: 
     return RandomSrc() 
    elif choice == 2: 
     return WhateverSrc() 
    else: 
     raise ValueError('source choice parameter {} not valid'.format(choice)) 

甚至更​​多...忘了關於@abstractmethod完全,只是得到一堆或定期的具體類。如果有人創建了一個新的*Src類,但沒有實現get方法,那麼該人將會看到一個很好的描述性失敗......

import random 

class ManualSrc(object): 
    def get(self): 
     return float(raw_input('gimme a number - ')) 

class RandomSrc(object): 
    def get(self): 
     return random.random() 

class BreakingSrc(object): 
    pass 

def create_source(choice): 
    if choice == 0: 
     return ManualSrc() 
    elif choice == 1: 
     return RandomSrc() 
    elif choice == 2: 
     return BreakingSrc() 
    else: 
     raise ValueError('source choice parameter {} not valid'.format(choice)) 

if __name__ == '__main__': 
    for use_src in range(4): 
     print 'using source choice {}'.format(use_src) 
     src = create_source(use_src) 
     print src.get() 

輸出:

using source choice 0 
gimme a number - 1 
1.0 
using source choice 1 
0.702223268052 
using source choice 2 
Traceback (most recent call last): 
    File "./stack26.py", line 28, in <module> 
    print src.get() 
AttributeError: 'BreakingSrc' object has no attribute 'get' 

所有這一切說......使用元類,當你定義class Whatever可以註冊在某種列表或字典的類(見this answer),這也可能給你一些想法:-)

在你的情況,下面的註冊通過元類一類的想法,下面的代碼片段工作,但你可以看到,代碼變得越來越撲朔迷離:

from abc import ABCMeta, abstractmethod 
import random 
import inspect 

available_srcs = [] 

def register(newclass): 
    if inspect.isabstract(newclass): 
     print ("newclass %s is abstract, and has abstract" 
       " methods: %s. Refusing to register" 
       % (newclass, newclass.__abstractmethods__)) 
     return 
    if newclass not in available_srcs: 
     available_srcs.append(newclass) 
     print "Registered %s as available source" % newclass 

class MyMetaClass(ABCMeta): 
    def __new__(cls, clsname, bases, attrs): 
     newclass = super(MyMetaClass, cls).__new__(cls, clsname, bases, attrs) 
     register(newclass) # here is your register function 
     return newclass 

class BaseSource(object): 
    __metaclass__ = MyMetaClass 

    @abstractmethod 
    def get(self): 
     pass  

class ManualSrc(BaseSource): 
    def get(self): 
     return float(raw_input('gimme a number - ')) 

class RandomSrc(BaseSource): 
    def get(self): 
     return random.random() 

if __name__ == '__main__': 
    for use_src in range(4): 
     print 'using source choice {}'.format(use_src) 
     src = available_srcs[use_src]() 
     print src.get() 

編輯1

Neil_UK)要求在評論這個答案這將是更加令人困惑的OP ,大寫一些不是類的東西,或者調用一個非大寫的名字來實例化一個特定的對象?

開始之前,以下示例充分利用內置的typevars函數。在繼續之前,你應該確保你熟悉他們的工作。

對我來說(這只是我的看法,因爲在Python中大寫或非大寫函數名在語法上都是可以的),它會以大寫字母形式顯示函數會更混亂。請記住,你實際上並沒有返回(儘管你可以,因爲class(es)也是type類型的實例)你要返回的是實例,並且函數沒有錯誤(根據PEP8 naming convention的小寫)返回一個實例。這是記錄模塊做什麼,例如:

>>> import logging 
>>> log = logging.getLogger('hello') 
>>> vars(log) 
{'name': 'hello', 'parent': <logging.RootLogger object at 0x17ce850>, 'handlers': [], 'level': 0, 'disabled': 0, 'manager': <logging.Manager object at 0x17ce910>, 'propagate': 1, 'filters': []} 
>>> type(log) 
<class 'logging.Logger'> 

再回到您的具體情況:如果我不知道你的代碼什麼(如果我只是進口CreateSource的地方),我知道我必須使用CreateSource是這樣的: src = CreateSource(use_src)我會自動想到的是,srcCreateSource類的一個實例,並且我在use_src參數中傳遞的整數將存儲在屬性的某處。用上面複製的logging檢查示例... 'hello'字符串恰好是通過getLogger函數創建的log實例的name屬性。好的... getLogger函數沒有什麼奇怪的。

讓我們來看一個極端的例子。我知道你不是在做我想要做的事情,(我認爲你的確是一個有效的擔憂),但也許它會幫助證明我的意思。

考慮下面的代碼:

a = A() 
a.x = 5 
print "a.x is %s" % a.x 

我你剛纔看到了,你會覺得這是怎麼回事呢?你會認爲你正在創建一個A類的空實例,並將它的x屬性設置爲5,所以你期望print輸出a.x is 5,對吧?

錯誤。這裏是發生了什麼事情(完全正確的Python):

class B(object): 
    def __init__(self): 
     self.x = 10 
    @property 
    def x(self): 
     return "I ain't returning x but something weird, and x is %s... FYI"\ 
       % self._x 
    @x.setter 
    def x(self, x): 
     self._x = int(self._x if hasattr(self, '_x') else 0 + 2 * x) 

def A(): 
    return B() 

所以a實際上是class B一個實例,並因了Python通過properties提供了「面具」 getter和setter的能力,我創建了一個可怕的混亂,根本不直觀。在處理Python時你會聽到很多次,事實上你可以做些什麼並不意味着你應該這樣做。我個人總是給本叔叔:能力越大責任重大(以及...或Voltaire,但咩,我發現引用本大叔冷卻器,whaddup!:-D?)

這說,你可能想創建一個用戶https://codereview.stackexchange.com/我敢肯定,有很多有知識的人可以比我更好地回答這類問題。

呵呵,之前我提過class也是一個實例。等等,wootoot?是的。功能也是實例!!檢查了這一點:

>>> class C(object): 
...  pass 
... 
>>> vars(C) 
dict_proxy({'__dict__': <attribute '__dict__' of 'C' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'C' objects>, '__doc__': None}) 
>>> type(C) 
<type 'type'> 
>>> def get_me_a_c_class(): 
...  return C 
... 
>>> my_class = get_me_a_c_class() 
>>> my_instance = my_class() 
>>> type(my_instance) 
<class '__main__.C'> 
>>> type(get_me_a_c_class) 
<type 'function'> 
>>> vars(get_me_a_c_class) 
{} 
>>> get_me_a_c_class.random_attribute = 5 
>>> print "Did I just put an attribute to a FUNCTION??: %s" % get_me_a_c_class.random_attribute 
Did I just put an attribute to a FUNCTION??: 5 

在幾年裏,我一直在處理與Python,我發現,它在很大程度上依賴於程序員的常識。雖然我最初猶豫不決,相信這種範例不會導致可怕的混亂,但事實證明它不(在大多數情況下,;-))。

+1

Doh,一個函數可以返回一個對象,所以我認爲def create_source(choice):就是我以前的樣子。它不需要'__new__',它顯然不是一個BaseSource。這是我關於我的實現的另一個擔憂,你指出,我的Source並不是真正的BaseSource,儘管我發現ABC機制在其中發揮了很大的作用,所以我印象深刻。我認爲發生的事情是Source從其中一個子源發展而來,所以它開始作爲一個真正的源泉,而我陷入了錯誤的思維模式。 –

+0

很高興我幫助**: - )**我喜歡元類......你可以用它們做很棒的事情......但是它們很棘手,可以很容易地導致*殺死蒼蠅與炮彈*情況 – BorrajaX

+0

好吧,現在我還有一個問題,create_source()不是一個類,但它唯一的功能就是當被調用時,它返回一個特定類型的對象,就像一個類一樣,就像xxxSrc類一樣。我傾向於利用它,以便在其他地方調用它時,它看起來像一個類。哪一個會更混亂,大寫一些不是類的東西,或者調用一個非大寫的名字來實例化一個特定的對象? –