2011-12-26 13 views
8

पर इंटरसेप्ट ऑपरेटर लुकअप मेरे पास एक कक्षा है जिसे प्रत्येक ऑपरेटर के साथ कुछ जादू करने की आवश्यकता है, जैसे __add__, __sub__ और इसी तरह।मेटाक्लास

कक्षा में प्रत्येक समारोह बनाने के बजाय, मेरे पास एक मेटाक्लास है जो ऑपरेटर मॉड्यूल में प्रत्येक ऑपरेटर को परिभाषित करता है।

import operator 
class MetaFuncBuilder(type): 
    def __init__(self, *args, **kw): 
     super().__init__(*args, **kw) 
     attr = '__{0}{1}__' 
     for op in (x for x in dir(operator) if not x.startswith('__')): 
      oper = getattr(operator, op) 

      # ... I have my magic replacement functions here 
      # `func` for `__operators__` and `__ioperators__` 
      # and `rfunc` for `__roperators__` 

      setattr(self, attr.format('', op), func) 
      setattr(self, attr.format('r', op), rfunc) 

दृष्टिकोण ठीक काम करता है, लेकिन मुझे लगता है कि अगर बेहतर हो तो मैं प्रतिस्थापन ऑपरेटर उत्पन्न करता हूं तो यह बेहतर होगा।

ऑपरेटरों के लुक metaclass पर क्योंकि x + 1type(x).__add__(x,1) बजाय x.__add__(x,1) के रूप में किया जाता है, लेकिन यह __getattr__ है और न ही __getattribute__ विधियों द्वारा पकड़े नहीं है होना चाहिए।

कि काम नहीं करता:

class Meta(type): 
    def __getattr__(self, name): 
      if name in ['__add__', '__sub__', '__mul__', ...]: 
       func = lambda:... #generate magic function 
       return func 

इसके अलावा, जिसके परिणामस्वरूप "समारोह" एक विधि का इस्तेमाल किया उदाहरण के लिए बाध्य किया जाना चाहिए।

इस विचार को मैं कैसे रोक सकता हूं इस पर कोई विचार? मुझे नहीं पता कि यह स्पष्ट है कि मैं क्या करना चाहता हूं।


पूछताछ कारण है कि मैं बात इस तरह का करने की आवश्यकता है उन लोगों के लिए, पूर्ण कोड here की जाँच करें। यह फ़ंक्शन उत्पन्न करने के लिए एक उपकरण है (बस मजेदार) जो lambda एस के प्रतिस्थापन के रूप में काम कर सकता है।

उदाहरण:

>>> f = FuncBuilder() 
>>> g = f ** 2 
>>> g(10) 
100 
>>> g 
<var [('pow', 2)]> 

बस रिकार्ड के लिए, मैं नहीं चाहता कि एक और तरीका है एक ही बात (मैं वर्ग पर हर एक ऑपरेटर की घोषणा नहीं करेगा करने के लिए जानना चाहता हूँ ... कि हो जाएगा उबाऊ और दृष्टिकोण मैंने बहुत अच्छा काम किया है :)। मैं जानना चाहता हूं कि एक ऑपरेटर से विशेषता लुकअप को कैसे रोकें।

+0

"मेरे पास एक कक्षा है जिसे प्रत्येक ऑपरेटर के साथ कुछ जादू करने की आवश्यकता है" - क्यों? ऐसा लगता है कि आप एक बहुत ही जटिल पेड़ को भड़क रहे हैं ... –

+0

@LennartRegebro मैं किसी ऑब्जेक्ट पर ऑपरेटरों का उपयोग करके फ़ंक्शन जेनरेटर लिख रहा हूं। 'एफ = Funcbuilder(); जी = एफ ** 2 + 1; जी (10) == 101'। यह कुछ बहुत उपयोगी नहीं है (बहुत सारे फ़ंक्शन कॉल), लेकिन इसका उपयोग करने में कुछ मजेदार है: डी – JBernardo

+0

@LennartRegebro मैंने पूरा कोड पोस्ट किया। – JBernardo

उत्तर

5

कुछ काला जादू के जाने से आप अपने लक्ष्य को प्राप्त:

operators = ["add", "mul"] 

class OperatorHackiness(object): 
    """ 
    Use this base class if you want your object 
    to intercept __add__, __iadd__, __radd__, __mul__ etc. 
    using __getattr__. 
    __getattr__ will called at most _once_ during the 
    lifetime of the object, as the result is cached! 
    """ 

    def __init__(self): 
    # create a instance-local base class which we can 
    # manipulate to our needs 
    self.__class__ = self.meta = type('tmp', (self.__class__,), {}) 


# add operator methods dynamically, because we are damn lazy. 
# This loop is however only called once in the whole program 
# (when the module is loaded) 
def create_operator(name): 
    def dynamic_operator(self, *args): 
    # call getattr to allow interception 
    # by user 
    func = self.__getattr__(name) 
    # save the result in the temporary 
    # base class to avoid calling getattr twice 
    setattr(self.meta, name, func) 
    # use provided function to calculate result 
    return func(self, *args) 
    return dynamic_operator 

for op in operators: 
    for name in ["__%s__" % op, "__r%s__" % op, "__i%s__" % op]: 
    setattr(OperatorHackiness, name, create_operator(name)) 


# Example user class 
class Test(OperatorHackiness): 
    def __init__(self, x): 
    super(Test, self).__init__() 
    self.x = x 

    def __getattr__(self, attr): 
    print "__getattr__(%s)" % attr 
    if attr == "__add__": 
     return lambda a, b: a.x + b.x 
    elif attr == "__iadd__": 
     def iadd(self, other): 
     self.x += other.x 
     return self 
     return iadd 
    elif attr == "__mul__": 
     return lambda a, b: a.x * b.x 
    else: 
     raise AttributeError 

## Some test code: 

a = Test(3) 
b = Test(4) 

# let's test addition 
print a + b # this first call to __add__ will trigger 
      # a __getattr__ call 
print a + b # this second call will not! 

# same for multiplication 
print a * b 
print a * b 

# inplace addition (getattr is also only called once) 
a += b 
a += b 
print a.x # yay! 

आउटपुट

__getattr__(__add__) 
7 
7 
__getattr__(__mul__) 
12 
12 
__getattr__(__iadd__) 
11 

अब तुम मेरी OperatorHackiness आधार वर्ग से इनहेरिट से सचमुच अपनी दूसरी कोड नमूने का उपयोग कर सकते हैं।आपको अतिरिक्त लाभ भी मिलता है: __getattr__ केवल प्रति उदाहरण और ऑपरेटर के लिए बुलाया जाएगा और कैशिंग के लिए शामिल रिकर्सन की कोई अतिरिक्त परत नहीं है। हम विधि विधि की तुलना में धीमी गति से चलने वाली विधि कॉल की समस्या को बाधित करते हैं (जैसे पॉल हैंकिन ने सही तरीके से देखा)।

नोट: ऑपरेटर विधियों को जोड़ने के लिए लूप केवल आपके पूरे कार्यक्रम में निष्पादित किया जाता है, इसलिए तैयारी मिलीसेकंड की सीमा में निरंतर ओवरहेड लेती है।

+0

ठीक है, ऐसा लगता है कि आपके 'फॉर लूप' कक्षा में सभी ऑपरेटरों को जोड़ रहा है (मेरा कोड देखें, मैं यह भी करता हूं)। मैं ** ** ** नहीं चाहता हूं :)। बीटीडब्ल्यू, मुझे लगता है कि यह पहले से ही एक सुधार है। – JBernardo

+0

@ जेबेर्नर्डो: फिर से देखें। यह आपके कोड के लिए पूरी तरह से अलग काम करता है। जो जोड़ा गया है वह ऑपरेटर फ़ंक्शंस नहीं है बल्कि '__getattr__' कॉल के आस-पास केवल उथले रैपर हैं। यह आवश्यक है, क्योंकि, जैसा कि आपने कहा था, आप कस्टम '__getattr__' फ़ंक्शन का उपयोग करके उन विधि कॉल को रोक नहीं सकते हैं। चूंकि लूप केवल आपके पूरे प्रोग्राम_ में निष्पादित किया जाता है और ऑपरेटरों की संख्या सीमित होती है, यह मिलीसेकंड की सीमा में निरंतर ओवरहेड लेती है। असल में, यह एक हैक है जो आपको '__getattr__' का उपयोग करने के लिए ऑपरेटर को किसी भी अन्य तरीकों से अवरुद्ध करने की अनुमति देता है (जो आपने अनुरोध किया है)। –

+0

मैं आपका कोड समझता हूं (आपको इन टिप्पणियों को उत्तर में जोड़ना चाहिए), लेकिन आप क्या करते हैं: 'x + y -> x .__ add__ -> x .__ getattr __ ('__ add __')'। यह एक दिलचस्प विचार है, लेकिन ऐसा लगता है कि ऑपरेटरों को किसी भी तरह असंभव नहीं है। – JBernardo

1

ऐसा लगता है कि आप चीजों को बहुत जटिल बना रहे हैं। आप एक मिक्स्ड क्लास को परिभाषित कर सकते हैं और इससे प्राप्त कर सकते हैं। मेटाक्लास का उपयोग करने से यह दोनों सरल है और __getattr__ का उपयोग करने से तेज़ी से चलेंगे।

class OperatorMixin(object): 
    def __add__(self, other): 
     return func(self, other) 
    def __radd__(self, other): 
     return rfunc(self, other) 
    ... other operators defined too 

तब प्रत्येक कक्षा में आप इन ऑपरेटरों को रखना चाहते हैं, ऑपरेटरमिक्सिन से प्राप्त होते हैं।

class Expression(OperatorMixin): 
    ... the regular methods for your class 

जब वे आवश्यकता हो ऑपरेटर तरीकों जनरेट कर रहा है एक अच्छा विचार नहीं है: __getattr__ नियमित विधि देखने की तुलना में धीमी है, और के बाद से एक बार तरीकों (mixin वर्ग पर) जमा हो जाती है, यह लगभग कुछ भी नहीं बचाता है ।

+0

हाँ, कम से कम 10 ऑपरेटर (प्लस इनप्लेस और रिवर्स किए गए फॉर्म) हैं और मैं उन्हें हाथ से लिखना नहीं चाहता हूं और उनमें से प्रत्येक के लिए सटीक एक ही फ़ंक्शन (ऑपरेटर बदलना) नहीं लिखना चाहता हूं। – JBernardo

+0

मेरा विचार अब * केवल * को 'func' या' rfunc 'बनाते हैं जब ऑपरेटर कहलाता है। – JBernardo

+0

क्या काम आपको आलसी दे देंगे? –

3

हाथ में मुद्दा यह है कि अजगर वस्तु के वर्ग पर __xxx__ तरीकों को देखता है, वस्तु पर ही नहीं है - और अगर ऐसा नहीं पाया जाता है, यह __getattr__ है और न ही __getattribute__ करने के लिए वापस नहीं आता है।

इस तरह के कॉल अवरोधन करने का एकमात्र तरीका पहले से ही वहाँ एक विधि है। यह निकलस बाउमस्टर्क के जवाब में एक स्टब फ़ंक्शन हो सकता है, या यह पूर्ण रूप से प्रतिस्थापन कार्य हो सकता है; किसी भी तरह से, हालांकि, पहले से ही कुछ हो सकता है या आप ऐसी कॉल को रोक नहीं पाएंगे।

यदि आप बारीकी से पढ़ रहे हैं, तो आपने देखा होगा कि अंतिम विधि के लिए अंतिम विधि होने के लिए आपकी आवश्यकता एक संभावित समाधान नहीं है - आप इसे कर सकते हैं, लेकिन पाइथन इसे कभी भी कॉल नहीं करेगा क्योंकि पाइथन देख रहा है उदाहरण के वर्ग, उदाहरण के लिए, __xxx__ विधियों के लिए। प्रत्येक उदाहरण के लिए एक अद्वितीय अस्थायी वर्ग बनाने का निकलस बाउमस्टर्क का समाधान उतना करीब है जितना आप उस आवश्यकता को प्राप्त कर सकते हैं।

संबंधित मुद्दे