Python: Iterating through constructor's arguments

前端 未结 9 1858
囚心锁ツ
囚心锁ツ 2021-02-05 15:07

I often find myself writing class constructors like this:

class foo:
    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
         


        
相关标签:
9条回答
  • 2021-02-05 15:33

    What about iterating over the explicit variable names?

    I.e.

    class foo:
        def __init__(self, arg1, arg2, arg3):
            for arg_name in 'arg1,arg2,arg3'.split(','):
                setattr(self, arg_name, locals()[arg_name])
    
    f = foo(5,'six', 7)
    

    Resulting with

    print vars(f)
    
    {'arg1': 5, 'arg2': 'six', 'arg3': 7}
    

    My suggestion is similar to @peepsalot, except it's more explicit and doesn't use dir(), which according to the documentation

    its detailed behavior may change across releases

    0 讨论(0)
  • 2021-02-05 15:36

    the *args is a sequence so you can access the items using indexing:

    def __init__(self, *args):
            if args:       
                self.arg1 = args[0]    
                self.arg2 = args[1]    
                self.arg3 = args[2]    
                ...  
    

    or you can loop through all of them

    for arg in args:
       #do assignments
    
    0 讨论(0)
  • 2021-02-05 15:44

    Provided answers rely on *vargs and **kargs arguments, which might not be convenient at all if you want to restrict to a specific set of arguments with specific names: you'll have to do all the checking by hand.

    Here's a decorator that stores the provided arguments of a method in its bound instance as attributes with their respective names.

    import inspect
    import functools
    
    def store_args(method):
        """Stores provided method args as instance attributes."""
        argspec = inspect.getargspec(method)
        defaults = dict(zip( argspec.args[-len(argspec.defaults):], argspec.defaults ))
        arg_names = argspec.args[1:]
        @functools.wraps(method)
        def wrapper(*positional_args, **keyword_args):
            self = positional_args[0]
            # Get default arg values
            args = defaults.copy()
            # Add provided arg values
            list(map( args.update, ( zip(arg_names, positional_args[1:]), keyword_args.items() ) ))
            # Store values in instance as attributes
            self.__dict__.update(args)
            return method(*positional_args, **keyword_args)
    
        return wrapper
    

    You can then use it like this:

    class A:
        @store_args
        def __init__(self, a, b, c=3, d=4, e=5):
            pass
    
    a = A(1,2)
    print(a.a, a.b, a.c, a.d, a.e)
    

    Result will be 1 2 3 4 5 on Python3.x or (1, 2, 3, 4, 5) on Python2.x

    0 讨论(0)
  • 2021-02-05 15:51

    The most Pythonic way is what you've already written. If you are happy to require named arguments, you could do this:

    class foo:
        def __init__(self, **kwargs):
            vars(self).update(kwargs)
    
    0 讨论(0)
  • 2021-02-05 15:53
    class foo:
        def __init__(self, **kwargs):
            for arg_name, arg_value in kwargs.items():
                setattr(self, arg_name, arg_value)
    

    This requires arguments to be named:

    obj = foo(arg1 = 1, arg2 = 2)
    
    0 讨论(0)
  • 2021-02-05 15:54

    For python >= 3.7 take a look at the @dataclass class decorator. Among other things, it handles your __init__ boilerplate coding problem.

    From the doc:

    @dataclass
    class InventoryItem:
        name: str
        unit_price: float
        quantity_on_hand: int = 0
    

    Will automatically add an __init__ like that:

    def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
        self.name = name
        self.unit_price = unit_price
        self.quantity_on_hand = quantity_on_hand
    
    0 讨论(0)
提交回复
热议问题