timeislove
timeislove

Reputation: 1105

replace class method with simple function

as an example here, i want to make a function to temporarily direct the stdout to a log file.

the tricky thing is that the codes have to keep the file handler and std sav for restoration after the redirect, i wrote it in class type to keep these two variables.

here below the full code:

class STDOUT2file:
    def __init__(self,prefix='report@'):

        now=dt.date.today()
        repname=repnameprefix=prefix+now.strftime("%Y%m%d")+'.txt'
        count=0
        while os.path.isfile(repname):
            count+=1
            repname=repnameprefix+(".%02d" %(count))
        self.sav=sys.stdout
        f=open(repname,'w')
        sys.stdout=f
        self.fname=repname
        self.fhr=f


    def off(self,msg=False):

        sys.stdout=self.sav
        self.fhr.close()
        if msg: 
            print('output to:'+self.fname)
        return

here is the code to apply it:

outbuf=STDOUT2file()

#codes to print out to stdout

outbuf.off(msg=True)

i want to make it more clean, read about 'closure' but it returns a function at the first call, kind of assigment type as similar as class.

i want it to be like:

STDOUT2file('on')

STDout2file('off',msg=True)

note: redirecting to stdout is an example i encountered just now.. what i am wondering is, any way other than class type to make simple functionality like those on/off type, which involve store/retrieval of state variables that should be better made invisible to outside.

Upvotes: 2

Views: 173

Answers (2)

John1024
John1024

Reputation: 113864

Yes, you can save state information in a function. Just name the variable functionname.something and it will be saved. For example:

def stdout2file(status, prefix='pre', msg=False):
    import datetime as dt
    import os
    import sys
    if not hasattr(stdout2file, 'sav'):
        stdout2file.sav = None
    if status == 'on':
        if stdout2file.sav:
            print('You have already triggered this once  Ignoring this request.')
        else:
            now = dt.date.today()
            repname = repnameprefix = prefix + now.strftime("%Y%m%d") + '.txt'
            count = 0
            while os.path.isfile(repname):
                count += 1
                repname = repnameprefix + (".%02d" %(count))
            stdout2file.sav = sys.stdout
            f = open(repname,'w')
            sys.stdout = f
            stdout2file.fhr = f
    elif status == 'off':
        if not stdout2file.sav:
            print('Redirect is "off" already.  Ignoring this request')
        else:
            sys.stdout = stdout2file.sav
            stdout2file.fhr.close()
            if msg:
                print('output to:' + stdout2file.fhr.name)
            stdout2file.sav = None
    else:
        print('Unrecognized request')

It is also possible to keep status information in mutable keyword parameters like so:

def stdout_toggle(prefix='pre', msg=False, _s=[None, None]):
    import datetime as dt
    import os
    import sys
    if _s[0] is None:
        now = dt.date.today()
        repname = repnameprefix = prefix + now.strftime("%Y%m%d") + '.txt'
        count = 0
        while os.path.isfile(repname):
            count += 1
            repname = repnameprefix + (".%02d" %(count))
        f = open(repname,'w')
        _s[:] = [sys.stdout, f]
        sys.stdout = f
    else:
        sys.stdout = _s[0]
        _s[1].close()
        if msg:
            print('output to:' + _s[1].name)
        _s[:] = [None, None]

The user can call the above without any arguments and it will toggle between the redirect between on and off. The function remembers the current status through the keyword parameter _s which is a mutable list.

Although some consider the fact that mutable keyword parameters are preserved between function calls to be a language flaw, it is consistent with python philosophy. It works because the default values for keyword parameters are assigned when the function is first defined, that is when the def statement is executed, and not when the function is called. Consequently, _s=[None, None] is assigned once at definition and is free to vary thereafter.

Upvotes: 1

roippi
roippi

Reputation: 25954

Try using a context manager instead. This idiom is common enough that it was included in the PEP that introduced context managers (slightly modified here):

from contextlib import contextmanager

@contextmanager
def redirect_stdout(new_stdout):
    import sys
    save_stdout = sys.stdout
    sys.stdout = new_stdout
    try:
        yield
    finally:
        sys.stdout = save_stdout

Or, if you like, the class-based version with __enter__ and __exit__:

class redirect_stdout:
    """Context manager for temporarily redirecting stdout to another file

    docstring truncated
    """

    def __init__(self, new_target):
        self.new_target = new_target

    def __enter__(self):
        self.old_target = sys.stdout
        sys.stdout = self.new_target
        return self.new_target

    def __exit__(self, exctype, excinst, exctb):
        sys.stdout = self.old_target

Raymond Hettinger actually committed this to contextlib, it will be included in python 3.4 as contextlib.redirect_stdout().

Basic usage:

with open('somelogfile','a') as f:
    with stdout_redirected(f):
        print(something)

Upvotes: 3

Related Questions