Printing message when class variable is called

Viewed 415

I'm using a 2 python class as configuration file. One of them contain old parameters (deprecated) and I would like to display a message if a deprecated param is used.

Here is how I used the different class:

config_backup.py

class _ConfigBackup:
    PARAM1 = 'a'
    PARAM2 = 'b'

config_new.py

class Config(_ConfigBackup):
    PARAM3 = 'c'
    PARAM4 = 'd'

cfg = Config

Then I can call cfg and have result like this:

>>> cfg.PARAM3
'c'
>>> cfg.PARAM1
Parameter PARAM1 is deprecated.
'a'

The function or method would look like this I think:

def warning(param):
    print(f"Parameter {param.__name__} is deprecated.")
    return param

I am not exactly sure if this is possible, maybe by using decorator or with statement, any idea ?

5 Answers

One method you could use with the @property decorator

class Config(_ConfigBackup):
    PARAM3 = 'c'
    PARAM4 = 'd'
    __PARAM1 = _ConfigBackup.PARAM1

    @property
    def PARAM1(self):
        print(f"Parameter PARAM1 is deprecated.")
        return Config.__PARAM1

cfg = Config()

print(cfg.PARAM1)
print(cfg.PARAM2)
print(cfg.PARAM3)
print(cfg.PARAM4)

Output:

Parameter PARAM1 is deprecated.
a
b
c
d

EDIT:

Another option is modifying __getattribute__:

class Config(_ConfigBackup):
    PARAM3 = 'c'
    PARAM4 = 'd'

    DEPRECATED = ['PARAM1', 'PARAM2']

    def __getattribute__(self, item):
        if not item == 'DEPRECATED' and item in Config.DEPRECATED:
            print(f"Parameter {item} is deprecated.")
            return object.__getattribute__(self,item)

Here is a proof-of-concept solution that meets the objection of having too many properties to be workable.

class Config:
    def __init__(self):
        self.deprecated = {'PARAM1': 'a', 'PARAM2': 'b'}
        self.nondeprecated = {'PARAM3': 'c', 'PARAM4': 'd'}
    def __getattr__(self, parmname):
        if parmname in self.__dict__["deprecated"]:
            print(f"{parmname} is deprecated")
            return self.__dict__["deprecated"][parmname]
        return self.__dict__["nondeprecated"][parmname]

>>> c = Config()
>>> c.PARAM1
PARAM1 is deprecated
'a'
>>> c.PARAM2
PARAM2 is deprecated
'b'
>>> c.PARAM3
'c'

I didn't put the deprecated parameters in a separate class because that would complicate the example unnecessarily. And real-world code would need to be able to cope with attempts to name a nonexistent parameter, and not do this:

>>> c.PARAM5
Traceback (most recent call last):
  File "<pyshell#105>", line 1, in <module>
    c.PARAM5
  File "<pyshell#100>", line 9, in __getattr__
    return self.__dict__["nondeprecated"][parmname]
KeyError: 'PARAM5'

When you use a value you are not allowed to really execute something unless you are using the property function.

for example in the code below we use the property to make it dynamically produced when a function tries to get it

class _ConfigBackup:
    _PARAM1 = 'a'
    
    @property
    def PARAM1(self):  # This method is get method in which the user tries to get its value
        print("Parameter PARAM1 is depreciated")
        return self._PARAM1

    # You can also make a setter by
    @PARAM1.setter
    def PARAM1(self, value_to_set):
        self._PARAM1 = value_to_set

    PARAM2 = 'b'

in the above code you require a little bit of extra work but it works as you expect it to. You can use this function prepared by me to make a depreciated property

def depreciated(property_to_use: str):
    def internal_set_property(self, value):
        setattr(self, propert_to_use, value)
    def internal_get_property(self):
        print(f"Parameter {property_to_use} is Depreciated")
        return property(internal_set_property, internal_get_property, lambda self: None)


class _ConfigBackup:
    _PARAM1 = 'a'
    PARAM1 = depreciated('_PARAM1')
    
    _PARAM2 = 'b'
    PARAM2 = depreciated('_PARAM2')

See this for reference:

Something like this avoids the need to create the .deprecated dicts like in the accepted answer. Just add a __getattribute__() method to class Config:

class _ConfigBackup:
    PARAM1 = 'a'
    PARAM2 = 'b'
    
class Config(_ConfigBackup):
    PARAM3 = 'c'
    PARAM4 = 'd'
    
    def __getattribute__(self, name):
        if not name.startswith('__') and name in _ConfigBackup.__dict__:
            print(f"'{name}' is deprecated.")
        return object.__getattribute__(self, name)

Usage:

cfg = Config()
print(cfg.PARAM1, cfg.PARAM3)

Output:

'PARAM1' is deprecated.
a c

The accepted answer does the job is if you can put the variables in a dictionary. However, in my case I wanted to keep those class. It can be done using the same 2 config class and a class wrapping all deprecated param using _ConfigBackup.__dict__:

class _ConfigBackup(object):
    PARAM1 = 'a'
    PARAM1 = 'b'

class ConfigBackup:
    def __init__(self):
        self.deprecated = _ConfigBackup.__dict__

    def __getattr__(self, parmname):
        if parmname in self.__dict__["deprecated"]:
            print(f"{parmname} is deprecated")
            return self.__dict__["deprecated"][parmname]

class Config(ConfigBackup):
    PARAM3 = 'c'
    PARAM4 = 'd'

>>> cfg = Config()
>>> cfg.PARAM1
PARAM1 is deprecated
'a'
>>> cfg.PARAM3
'c'
Related