2012-01-25 12 views
34

मैंने एक पाइथन कक्षा का नाम बदल दिया है जो लाइब्रेरी का हिस्सा है। मैं कुछ समय के लिए अपने पिछले नाम का उपयोग करने की संभावना छोड़ने के लिए तैयार हूं लेकिन उपयोगकर्ता को चेतावनी देना चाहता हूं कि इसे बहिष्कृत किया गया है और भविष्य में हटा दिया जाएगा।वर्ग (नाम) बहिष्करण के बारे में चेतावनी कैसे दें

मुझे लगता है कि पिछली संगतता प्रदान करने के लिए ऐसा एक अन्य नाम का उपयोग करने के लिए पर्याप्त होगा:

class NewClsName: 
    pass 

OldClsName = NewClsName 

मैं पता नहीं कैसे OldClsName चिह्नित करने के लिए के रूप में एक सुरुचिपूर्ण ढंग से बहिष्कृत कर दिया गया है। हो सकता है कि मैं OldClsName एक ऐसा फ़ंक्शन बना सकता हूं जो एक चेतावनी (लॉग करने के लिए) उत्सर्जित करता है और NewClsName ऑब्जेक्ट को इसके पैरामीटर से बना देता है (*args और **kvargs का उपयोग करके) लेकिन यह पर्याप्त सुरुचिपूर्ण प्रतीत नहीं होता है (या शायद यह है?)।

हालांकि, मुझे नहीं पता कि पाइथन मानक लाइब्रेरी बहिष्करण चेतावनियां कैसे काम करती हैं। मुझे लगता है कि बहिष्कार से निपटने के लिए कुछ अच्छा जादू हो सकता है, उदा। कुछ दुभाषिया के कमांड लाइन विकल्प के आधार पर इसे त्रुटियों या सिलेंसिंग के रूप में इलाज करने की अनुमति देता है।

सवाल यह है: उपयोगकर्ताओं को एक अप्रचलित वर्ग उपनाम (या सामान्य रूप से अप्रचलित कक्षा) का उपयोग करने के बारे में चेतावनी कैसे दें।

संपादित: समारोह दृष्टिकोण मेरे लिए काम नहीं करता है क्योंकि वर्ग कुछ वर्ग विधियों (कारखाना विधि) जो जब OldClsName एक समारोह के रूप में परिभाषित किया गया नहीं कहा जा सकता है (मैं पहले से ही यह एक कोशिश दे दी है) । निम्नलिखित कोड काम नहीं करेगा:

class NewClsName(object): 
    @classmethod 
    def CreateVariant1(cls, ...): 
     pass 

    @classmethod 
    def CreateVariant2(cls, ...): 
     pass 

def OldClsName(*args, **kwargs): 
    warnings.warn("The 'OldClsName' class was renamed [...]", 
        DeprecationWarning) 
    return NewClsName(*args, **kwargs) 

OldClsName.CreateVariant1(...) 

के कारण:

AttributeError: 'function' object has no attribute 'CreateVariant1' 

मेरी एकमात्र विकल्प विरासत है? ईमानदार होने के लिए, यह मेरे लिए बहुत साफ नहीं दिखता है - यह अनावश्यक व्युत्पन्न के परिचय के माध्यम से कक्षा पदानुक्रम को प्रभावित करता है। इसके अतिरिक्त, OldClsName is not NewClsName अधिकांश मामलों में कोई समस्या नहीं है लेकिन लाइब्रेरी का उपयोग करके खराब लिखित कोड के मामले में समस्या हो सकती है।

मैं एक डमी, असंबद्ध OldClsName कक्षा भी बना सकता हूं और इसमें सभी वर्ग विधियों के लिए एक कन्स्ट्रक्टर के साथ ही रैपर लागू कर सकता हूं, लेकिन यह मेरी राय में भी खराब समाधान है।

उत्तर

26

शायद मैं एक समारोह जो एक चेतावनी ( लॉग के लिए) का उत्सर्जन करता है OldClsName कर सकता है और NewClsName वस्तु का निर्माण इसके पैरामीटर से ( * args और ** kvargs का उपयोग करके) लेकिन यह पर्याप्त सुरुचिपूर्ण प्रतीत नहीं होता है (या शायद यह है?)।

हाँ, मुझे लगता है कि सुंदर मानक अभ्यास है: यदि आप चीजें हैं जो OldClsName से उपवर्ग है

def OldClsName(*args, **kwargs): 
    from warnings import warn 
    warn("get with the program!") 
    return NewClsName(*args, **kwargs) 

केवल मुश्किल बात है - तो हम चालाक निकलना है।तुम सिर्फ वर्ग के तरीकों के लिए उपयोग रखने की जरूरत है, तो यह यह करना चाहिए:

class DeprecationHelper(object): 
    def __init__(self, new_target): 
     self.new_target = new_target 

    def _warn(self): 
     from warnings import warn 
     warn("Get with the program!") 

    def __call__(self, *args, **kwargs): 
     self._warn() 
     return self.new_target(*args, **kwargs) 

    def __getattr__(self, attr): 
     self._warn() 
     return getattr(self.new_target, attr) 

OldClsName = DeprecationHelper(NewClsName) 

मैं इसे परीक्षण नहीं किया है, लेकिन है कि आप अंदाज़ा हो जाना चाहिए - __call__ सामान्य instantation मार्ग संभाल लेंगे, __getattr__ कब्जा क्लास विधियों तक पहुंच & अभी भी आपकी कक्षा विरासत के साथ गड़बड़ किए बिना चेतावनी उत्पन्न करती है।

+1

विरासत सहायक आसान रूप में अच्छी तरह से किया जाना चाहिए - बस एक 'वर्ग OldClsName (NewClsName बारे में): # और अधिभार __new__'। – delnan

+1

+1 यह पुष्टि करने के लिए कि इस समाधान का उपयोग किया गया है और 'चेतावनियों' मॉड्यूल का उल्लेख करने के लिए। दुर्भाग्यवश फ़ंक्शन समाधान मेरे लिए काम नहीं करता है (संपादित प्रश्न देखें)। शायद आपके पास कुछ और साफ समाधान हैं? : डी –

+0

एक रैपर ऑब्जेक्ट का उपयोग करने के उदाहरण के साथ अपडेट किया गया है जो कॉल और विशेषता दोनों को प्रॉक्सी कर सकता है नई कक्षा तक पहुंच। – AdamKG

12

कृपया warnings.warn पर एक नज़र डालें।

जैसा कि आप देखेंगे, दस्तावेज में उदाहरण के एक प्रतिवाद चेतावनी है:

def deprecation(message): 
    warnings.warn(message, DeprecationWarning, stacklevel=2) 
5

आप केवल उप-वर्ग क्यों नहीं हैं? इस तरह कोई उपयोगकर्ता कोड टूटा नहीं जाना चाहिए।

class OldClsName(NewClsName): 
    def __init__(self, *args, **kwargs): 
     warnings.warn("The 'OldClsName' class was renamed [...]", 
         DeprecationWarning) 
     NewClsName.__init__(*args, **kwargs) 
+1

'isinstance()' चेक ... – dAnjou

+0

'isinstance' subclasses के लिए भी जांचता है, इसलिए यदि आप ऐसा करने का प्रयास करते हैं तो यह कुछ तोड़ना नहीं चाहिए। –

+0

'इंस्टेंसेंस 'चेक वास्तव में असफल हो जाएंगे। विचार करें, आपके पास विरासत कोड है 'आईइंस्टेंस (ओबीजे, ओल्डसीएलएसनाम)' और नया कोड जो 'obj = newClsName()' को तत्काल करता है, फिर 'isinstance (obj, OldClsName) == गलत 'और आपका कोड ब्रेक: ओह। – DylanYoung

0

उपयोग inspect मॉड्यूल OldClass के लिए प्लेसहोल्डर जोड़ने के लिए है, तो OldClsName is NewClsName जाँच में सफल होगा, और pylint की तरह एक लिंटर त्रुटि के रूप में इस बारे में सूचित करेंगे।

deprecate.py

import inspect 
import warnings 
from functools import wraps 

def renamed(old_name): 
    """Return decorator for renamed callable. 

    Args: 
     old_name (str): This name will still accessible, 
      but call it will result a warn. 

    Returns: 
     decorator: this will do the setting about `old_name` 
      in the caller's module namespace. 
    """ 

    def _wrap(obj): 
     assert callable(obj) 

     def _warn(): 
      warnings.warn('Renamed: {} -> {}' 
         .format(old_name, obj.__name__), 
         DeprecationWarning, stacklevel=3) 

     def _wrap_with_warn(func, is_inspect): 
      @wraps(func) 
      def _func(*args, **kwargs): 
       if is_inspect: 
        # XXX: If use another name to call, 
        # you will not get the warning. 
        frame = inspect.currentframe().f_back 
        code = inspect.getframeinfo(frame).code_context 
        if [line for line in code 
          if old_name in line]: 
         _warn() 
       else: 
        _warn() 
       return func(*args, **kwargs) 
      return _func 

     # Make old name available. 
     frame = inspect.currentframe().f_back 
     assert old_name not in frame.f_globals, (
      'Name already in use.', old_name) 

     if inspect.isclass(obj): 
      obj.__init__ = _wrap_with_warn(obj.__init__, True) 
      placeholder = obj 
     else: 
      placeholder = _wrap_with_warn(obj, False) 

     frame.f_globals[old_name] = placeholder 

     return obj 

    return _wrap 

test.py

from __future__ import print_function 

from deprecate import renamed 


@renamed('test1_old') 
def test1(): 
    return 'test1' 


@renamed('Test2_old') 
class Test2(object): 
    pass 

    def __init__(self): 
     self.data = 'test2_data' 

    def method(self): 
     return self.data 

# pylint: disable=undefined-variable 
# If not use this inline pylint option, 
# there will be E0602 for each old name. 
assert(test1() == test1_old()) 
assert(Test2_old is Test2) 
print('# Call new name') 
print(Test2()) 
print('# Call old name') 
print(Test2_old()) 

तो python -W all test.py चलाएँ:

test.py:22: DeprecationWarning: Renamed: test1_old -> test1 
assert(test1() == test1_old()) 
# Call new name 
<__main__.Test2 object at 0x0000000007A147B8> 
# Call old name 
test.py:27: DeprecationWarning: Renamed: Test2_old -> Test2 
print(Test2_old()) 
<__main__.Test2 object at 0x0000000007A147B8> 
संबंधित मुद्दे