# Title: module autofill
# Author: Gribouillis, for the python forum at www.daniweb.com
# Date: 2011 Nov 02
# License: Public Domain
# Use this code freely in your programs.

from __future__ import print_function
from functools import update_wrapper
import inspect

"""Module to automate class instance attributes setting at creation.

    This module offers a function autofill() which purpose is to replace
    code similar to
    
        class A(object):
            
            def __init__(self, x, y, z):
                self.x = x
                self.y = y
                self.z = z
                
    with
    
        class A(object):
            
            @ autofill()
            def __init__(self, x, y, z):
                pass
    
    See the documentation of autofill() for details.
"""

va_enum = tuple("ignore store".split())
kw_enum = tuple("ignore store merge".split())

def autofill(va='ignore', kw='ignore'):
    """Return a decorator to implement automatic attribute setting in __init__ methods.
    
        Typical use:
            
            class A(object):
                
                @ autofill()
                def __init__(self, x, y):
                    pass
                
            a = A(2, 3) # attributes a.x and a.y are automatically set.
            
        Observe that the attributes are set *before* the body of __init__() is
        executed.
        
        The parameters 'va' and 'kw' influence the way variable number of arguments
        and keywords arguments are handled in the __init__() method signature.
        Possible values are 'ignore', 'store' for va, and 'ignore', 'store', 'merge'
        for kw. For example if __init__() has the signature
                
                def __init__(self, x, y, *uuu, **vvv)
                
        then using autofill with 'ignore' for va and kw (the default) will cause uuu
        and vvv to be ignored by the autofill mechanism. Passing va = 'store' will
        create an attribute self.uuu. Similarly, passing kw = 'store' will create
        self.vvv. Passing kw = 'merge' will not create self.vvv, but instead set a
        new attribute for each pair (key, value) in vvv.
    """
    if not va in va_enum:
        raise ValueError(("Invalid va argument should be in", va_enum))
    if not kw in kw_enum:
        raise ValueError(("Invalid kw argument, should be in", kw_enum))
    def decorator(func):
        spec = inspect.getargspec(func)
        # print "argspec:", spec
        d = len(spec.args) - len(spec.defaults)
        def wrapper(*args, **kwd):
            # print "wrapper:", args, kwd
            self = args[0]
            n = len(args)
            for i, name in enumerate(spec.args[1:], 1):
                if i >= n:
                    if name in kwd:
                        value = kwd[name]
                    elif i >= d:
                        value = spec.defaults[i-d]
                    else: # ignore missing values. func() will complain
                        pass
                else:
                    value = args[i]
                setattr(self, name, value)
            if spec.varargs is not None and va == 'store':
                setattr(self, spec.varargs, args[len(spec.args):])
            if spec.keywords is not None and kw != 'ignore':
                pairs = ((k, v) for (k, v) in kwd.items() if k not in spec.args)
                if kw == 'store':
                    setattr(self, spec.keywords, dict(pairs))
                elif kw == 'merge':
                    for k, v in pairs:
                        setattr(self, k, v)
            return func(*args, **kwd)
        update_wrapper(wrapper, func)
        return wrapper
    return decorator

if __name__ == "__main__":
    
    class A(object):

        @ autofill(kw='merge')
        def __init__(self, u, v=0, *xxx, **yyy):
            pass

    a = A(2, 3, z = 5)
    print(vars(a))
1
Contributor
1
Reply
6
Views
6 Years
Discussion Span
Last Post by Gribouillis
0

Bugfix to handle properly some cases of missing arguments

# Title: module autofill
# Author: Gribouillis, for the python forum at www.daniweb.com
# Date: 2011 Nov 02
# License: Public Domain
# Use this code freely in your programs.

from __future__ import print_function
from functools import update_wrapper
import inspect

"""Module to automate class instance attributes setting at creation.

    This module offers a function autofill() which purpose is to replace
    code similar to
    
        class A(object):
            
            def __init__(self, x, y, z):
                self.x = x
                self.y = y
                self.z = z
                
    with
    
        class A(object):
            
            @ autofill()
            def __init__(self, x, y, z):
                pass
    
    See the documentation of autofill() for details.
"""

va_enum = tuple("ignore store".split())
kw_enum = tuple("ignore store merge".split())

def autofill(va='ignore', kw='ignore'):
    """Return a decorator to implement automatic attribute setting in __init__ methods.
    
        Typical use:
            
            class A(object):
                
                @ autofill()
                def __init__(self, x, y):
                    pass
                
            a = A(2, 3) # attributes a.x and a.y are automatically set.
            
        Observe that the attributes are set *before* the body of __init__() is
        executed.
        
        The parameters 'va' and 'kw' influence the way variable number of arguments
        and keywords arguments are handled in the __init__() method signature.
        Possible values are 'ignore', 'store' for va, and 'ignore', 'store', 'merge'
        for kw. For example if __init__() has the signature
                
                def __init__(self, x, y, *uuu, **vvv)
                
        then using autofill with 'ignore' for va and kw (the default) will cause uuu
        and vvv to be ignored by the autofill mechanism. Passing va = 'store' will
        create an attribute self.uuu. Similarly, passing kw = 'store' will create
        self.vvv. Passing kw = 'merge' will not create self.vvv, but instead set a
        new attribute for each pair (key, value) in vvv.
    """
    if not va in va_enum:
        raise ValueError(("Invalid va argument should be in", va_enum))
    if not kw in kw_enum:
        raise ValueError(("Invalid kw argument, should be in", kw_enum))
    def decorator(func):
        spec = inspect.getargspec(func)
        # print "argspec:", spec
        d = len(spec.args)
        if spec.defaults is not None:
            d -= len(spec.defaults)
        def wrapper(*args, **kwd):
            # print "wrapper:", args, kwd
            self = args[0]
            n = len(args)
            for i, name in enumerate(spec.args[1:], 1):
                if i >= n:
                    if name in kwd:
                        value = kwd[name]
                    elif i >= d:
                        value = spec.defaults[i-d]
                    else: # ignore missing values. func() will complain
                        break
                else:
                    value = args[i]
                setattr(self, name, value)
            if spec.varargs is not None and va == 'store':
                setattr(self, spec.varargs, args[len(spec.args):])
            if spec.keywords is not None and kw != 'ignore':
                pairs = ((k, v) for (k, v) in kwd.items() if k not in spec.args)
                if kw == 'store':
                    setattr(self, spec.keywords, dict(pairs))
                elif kw == 'merge':
                    for k, v in pairs:
                        setattr(self, k, v)
            return func(*args, **kwd)
        update_wrapper(wrapper, func)
        return wrapper
    return decorator

if __name__ == "__main__":
    
    class A(object):

        @ autofill(kw='merge')
        def __init__(self, u, v, *xxx, **yyy):
            pass

    a = A(2, 3)
    print(vars(a))

Edited by Gribouillis: n/a

Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.