简体   繁体   中英

Avoiding namespace pollution in python by using Classes

A bit of background

I'm writing a python module for my own use, and I'm using Python's logging module. There are handlers and formatters and even a pair of functions I create that (for the most part) won't be used anywhere else. However, I still want to be able to access and modify these variables elsewhere (for instance, other closely-coupled modules or scripts)

A simple namespace

What I'm currently doing is using a class definition to group all of my variables together, like this:

class _Logging:
    '''A little namespace for our logging facilities. Don't try to instantiate
    it: all it does is group together some logging objects and keep them out of
    the global namespace'''
    global logger

    def __init__(self):
        raise TypeError("that's not how this works...")

    def gz_log_rotator(source, dest):
        '''accept a source filename and a destination filename. copy source to
        dest and add gzip compression. for use with
        logging.handlers.RotatingFileHandler.rotator.'''
        with gzip.open(dest, 'wb', 1) as ofile, open(source, 'rb') as ifile:
            ofile.write(ifile.read())
        os.remove(source)

    def gz_log_namer(name):
        '''accept a filename, and return it with ".gz" appended. for use with
        logging.handlers.RotatingFileHandler.namer.'''
        return name + ".gz"

    fmtr = logging.Formatter(
        '[%(asctime)s:%(name)s:%(thread)05d:%(levelname)-8s] %(message)s')

    gz_rotfile_loghandler = logging.handlers.RotatingFileHandler(
        '%s.log' % __name__, mode='a', maxBytes=(1024**2 * 20), backupCount=3)
    gz_rotfile_loghandler.setLevel(5)
    gz_rotfile_loghandler.setFormatter(fmtr)
    gz_rotfile_loghandler.rotator = gz_log_rotator
    gz_rotfile_loghandler.namer = gz_log_namer

    simplefile_loghandler = logging.FileHandler(
        '%s.simple.log' % __name__, mode='w')
    simplefile_loghandler.setLevel(15)
    simplefile_loghandler.setFormatter(fmtr)

    stream_loghandler = logging.StreamHandler()
    stream_loghandler.setLevel(25)
    stream_loghandler.setFormatter(fmtr)

    logger = logging.getLogger(__name__)
    logger.setLevel(5)
    logger.addHandler(gz_rotfile_loghandler)
    logger.addHandler(simplefile_loghandler)
    logger.addHandler(stream_loghandler)

However, pylint complains (and i agree) that methods defined in a class should either be static methods, or follow the naming conventions for first parameters (eg gz_log_rotator(self, dest) ), which is not how the function is used, and would be much more confusing.

Fun Fact

During this process i've also discovered that instances of classmethod and staticmethod are not in and of themselves callable (???). While a method defined in a class namespace is callable both within and without, classmethods and staticmethods are only callable when accessed through their class (at which point they refer to the underlying function, not the classmethod / staticmethod object)

>>> class Thing:
...   global one_, two_, three_
...   def one(self):
...      print('one')
...   @classmethod
...   def two(cls):
...     print('two')
...   @staticmethod
...   def three():
...     print('three')
...   one_, two_, three_ = one, two, three
...
>>> Thing.one()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: one() missing 1 required positional argument: 'self'
>>> Thing.two()
two
>>> Thing.three()
three
>>> # all as expected
>>> one_()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: one() missing 1 required positional argument: 'self'
>>> # so far so good
>>> two_()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'classmethod' object is not callable
>>> # what?
>>> three_()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'staticmethod' object is not callable
>>> # ???

My Question

Is there a better way to hold these variables without polluting my namespace?

The code I have works correctly, but it makes me feel a little unclean. I could define a function that would only be called once and then immediately call it, but then I either lose references to everything I don't return, or i'm back to polluting the global namespace. I could just make everything _hidden , but I feel like they should be logically grouped. I could make _Logging a bona fide class, put all of my stuff in an __init__ function and tack all my little variables onto self , but that also feels inelegant. I could create another file for this, but so far I've gotten by with everything held in the same file. The only other option that seemed palatable is to make the two functions staticmethods and only refer to them through our class (ie _Logging.gz_log_namer ), but it would seem that is also impossible.

>>> class Thing:
...   @staticmethod
...   def say_hello():
...     print('hello!')
...   Thing.say_hello()
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in Thing
AttributeError: type object 'Thing' has no attribute 'say_hello'
>>>

As it stands, the best option I see is to use the selfless methods.

Sorry for answering 2 years later, but this could help someone.

You could make your methods static, and create another static method (ex. init ), calling it right after initializing the class. Then use setattr to keep the references to your variables.

For setting multiple class variables, you can use

[setattr(Class, name, value) for name,value in locals().items()]

inside the method.


Full code:

class _Logging:
    '''A little namespace for our logging facilities. Don't try to instantiate
    it: all it does is group together some logging objects and keep them out of
    the global namespace'''
    def __init__(self):
        raise TypeError("that's not how this works...")

    @staticmethod
    def gz_log_rotator(source, dest):
        '''accept a source filename and a destination filename. copy source to
        dest and add gzip compression. for use with
        logging.handlers.RotatingFileHandler.rotator.'''
        with gzip.open(dest, 'wb', 1) as ofile, open(source, 'rb') as ifile:
            ofile.write(ifile.read())
        os.remove(source)

    @staticmethod
    def gz_log_namer(name):
        '''accept a filename, and return it with ".gz" appended. for use with
        logging.handlers.RotatingFileHandler.namer.'''
        return name + ".gz"

    @staticmethod
    def init():
        global logger

        fmtr = logging.Formatter(
            '[%(asctime)s:%(name)s:%(thread)05d:%(levelname)-8s] %(message)s')

        gz_rotfile_loghandler = logging.handlers.RotatingFileHandler(
            '%s.log' % __name__, mode='a', maxBytes=(1024**2 * 20), backupCount=3)
        gz_rotfile_loghandler.setLevel(5)
        gz_rotfile_loghandler.setFormatter(fmtr)
        gz_rotfile_loghandler.rotator = _Logging.gz_log_rotator
        gz_rotfile_loghandler.namer = _Logging.gz_log_namer

        simplefile_loghandler = logging.FileHandler(
            '%s.simple.log' % __name__, mode='w')
        simplefile_loghandler.setLevel(15)
        simplefile_loghandler.setFormatter(fmtr)

        stream_loghandler = logging.StreamHandler()
        stream_loghandler.setLevel(25)
        stream_loghandler.setFormatter(fmtr)

        logger = logging.getLogger(__name__)
        logger.setLevel(5)
        logger.addHandler(gz_rotfile_loghandler)
        logger.addHandler(simplefile_loghandler)
        logger.addHandler(stream_loghandler)

        [setattr(_Logging, name, value) for name,value in locals().items()]

_Logging.init()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM