2012-01-25 24 views
34

Kitaplığın bir parçası olan bir python sınıfını yeniden adlandırdım. Bir süre önce bir önceki adı kullanma olasılığını bırakmaya istekliyim, ancak kullanıcı tarafından onaylanmadığını ve gelecekte kaldırılacağını bildirmek isterim. Sınıf (isim) kullanımdan kaldırmayla ilgili uyarılma

böyle olsun bir takma ad kullanmak için yeterli olacağını geriye dönük uyumluluk sağlamak için düşünüyorum:

class NewClsName: 
    pass 

OldClsName = NewClsName 

nasıl zarif bir şekilde kullanımdan kaldırılmış olarak OldClsName işaretlemek için hiçbir fikrim yok. Belki de NewClsName nesnesini (*args ve **kvargs kullanarak) bir uyarı veren (günlük) ve OldClsName yapabilen bir işlev yapabilir, ancak yeterince zarif görünmüyor (veya belki de?). Ancak, Python standart kitaplık kullanımdan kaldırma uyarılarının nasıl çalıştığını bilmiyorum. İhmal ile başa çıkmak için güzel bir sihir olabileceğini hayal ediyorum, ör. Bazı tercümanların komut satırı seçeneklerine bağlı olarak hata veya yanlışlık olarak ele alınmasını sağlar.

Sorun şudur: Kullanıcıları, eski bir sınıf takma adı (veya genel olarak eski bir sınıf) kullanma konusunda uyarma.

DÜZENLEME: fonksiyon yaklaşımı benim için çalışmıyor sınıf OldClsName bir fonksiyonu olarak tanımlanır çağrılacak edilemez bazı sınıf yöntemleri (fabrika yöntemleri) sahip olduğundan (Zaten bir deneyin verdi) . Aşağıdaki kod çalışmaz:

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(...) 

Çünkü:

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

benim tek seçenek miras mı? Dürüst olmak gerekirse, bana çok temiz görünmüyor - gereksiz hiyerarşinin getirilmesiyle sınıf hiyerarşisini etkiliyor. Ayrıca, çoğu durumda bir sorun değildir, ancak kitaplığı kullanan kötü yazılmış kodun kullanılması durumunda sorun olabilir.

Ayrıca, bir kukla, ilişkisiz bir OldClsName sınıfı oluşturabilir ve bir kurucu yanı sıra tüm sınıf yöntemleri için sarmalayıcılar uygulayabilirim, ancak bence daha da kötü bir çözümdür.

cevap

26

Belki ( günlüklerine) bir uyarı yayan bir işlev OldClsName yapabilir ve NewClsName nesnesi oluşturur onun parametreleri ( * args ve ** kvargs kullanarak) ama yeterince zarif görünmüyor (ya da belki?).

Evet, ben bu oldukça standart bir uygulama olduğunu düşünüyorum: Eğer OldClsName gelen alt sınıf şeyler varsa

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

tek aldatıcı bir şeydir - o zaman akıllı almak zorunda.Sadece sınıf yöntemlerine erişimini tutmak gerekiyorsa, bunu yapmak gerekir: Ben test etmedim

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) 

, ama bu sana fikir vermelidir - __call__ normal instantation rotayı idare edecek, __getattr__ yakalayacaktır Sınıf hiyerarşisi ile uğraşmadan, & sınıf yöntemlerine erişme yine de uyarı üretir.

+1

kolay yanı olmalı miras Destekleyici - sadece bir 'sınıf OldClsName (NewClsName bilgileri): # ve aşırı yük __new__'. Bu çözümün kullanıldığını ve 'uyarı' modülünden bahsetmek için – delnan

+1

+1. Ne yazık ki, işlev çözümü benim için çalışmıyor (bkz. Düzenlenmiş soru). Belki başka bazı temiz çözümler var? : D –

+0

Yeni sınıfa hem çağrıları hem de öznitelik erişimini proxy gönderebilen bir sarmalayıcı nesnesi kullanma örneği ile güncellendi. – AdamKG

12

Lütfen warnings.warn'a bir göz atın.

Göreceğiniz gibi, belgelerinde örnek bir kullanımdan kaldırılması uyarıdır:

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

Neden alt sınıf değilsiniz? Bu şekilde kullanıcı kodu kırılmamalıdır.

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

'isinstance()' kontrolleri ... – dAnjou

+0

'isinstance' da alt sınıfları kontrol eder, bu yüzden eğer ima etmeye çalıştığınız şey bu bir şeyi kırmamalı. –

+0

'isinstance' kontrolleri gerçekten başarısız olacaktır. Düşünün, 'isinstance (obj, OldClsName)' ve 'object = NewClsName() 'ı, sonra' isinstance (obj, OldClsName) == False' ve kodlarınızın koptuğu yeni kodu içeren eski kodunuz var: oops. – DylanYoung

0

Kullanım inspect modül sonra OldClsName is NewClsName çek geçecek OldClass için yer tutucu eklemek ve PyLint gibi bir linter hatası olarak bu bilgilendirecektir.

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()) 

sonra python -W all test.py çalıştırın:

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> 
İlgili konular