Automatically assign method arguments to instance variables - python

Is there a way in Python to declare instance variables from the methods arguments without the need for boilerplate writing?
For example, is there a way for self.foo, self.bar and all other arguments to be automatically declared?
def __init__(self, foo, bar, ..., last):
self.foo = foo
self.bar = bar
...
self.last = last

There actually is, though I think it's a very ugly way
class Foo:
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
Foo(name='foo', baz=123, bar=True, last=None)
As Tim Roberts suggested in the comments, using a namedtuple is a much better and cleaner way (you may also take a look at dataclasses.dataclass)

You can use a keyword argument in __init__. For example:
class MyClass:
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
Then you can create a class using whatever keyword arguments you want:
>>> mc = MyClass(foo=123, bar=456, baz=789)
>>>
Those arguments are assigned as attributes of the class instance:
>>> mc.foo
123
>>> mc.bar
456
>>> mc.baz
789
>>>
You may want to add some checks if you do this, e.g. restrict them to some known set of names. Otherwise an undesired name could override a method name for instance.
Or, you could save the keyword dictionary directly in the class instance, as a single attribute. That would insulate the class from the names. For example:
class MyClass:
def __init__(self, **kwargs):
self.args = kwargs
You could then access them through self.args.

Solution 1
As #Tim Roberts suggested this is how it can be accomplished with a namedtuple.
from collections import namedtuple
class Foo(namedtuple("Foo", ["param1", "param2"])):
pass # Rest of code goes here
my_class = Foo(1, 2)
my_class.param1 # returns 1
You can even create default arguments
namedtuple("Foo", ["param1", "param2"], defaults=[1, 2])
Solution 2
Alternatively, you can use **kwargs to set the instance variables (which doesn't use the collections library and is arguably easier) if you are fine with the user passing any random argument.
Here is an example
class Foo:
def __init__(self, **kwargs):
super().__dict__.update(kwargs)
my_class = Foo(param1=1)
my_class.param1 # returns 1
# Doesn't force you to pass the correct parameters
random_param_class = Foo(a_made_up_parameter=2)
random_param_class.param1 # Results in an error
Bear in mind that none of this is standard and most often people just declare the instance variables from within __init__.

To be honest, if you have a fixed sets of inputs, you can use this:
from inspect import getargvalues, stack
def arguments():
args = getargvalues(stack()[1][0])[-1]
del args['self']
if 'kwargs' in args:
args.update(args['kwargs'])
del args['kwargs']
return args
class myClass():
def __init__(self, foo, bar, ..., last):
# Auto update all arguments into object dictionary
self.__dict__.update(arguments())
This should do it, if you don't specify *kwargs
object = myClass(1,2,3,'foo','random')
# all the right instances will be created
# object.foo =1
# object.bar = 2

Related

how to ignore extra kwargs in python-attrs class

For example:
#attrs
class Foo:
a = attrib()
f = Foo(a=1, b=2)
Code above will throw an error because class Foo doesn't have b attr. But I want to discard passed b value as if I just called f = Foo(a=1). In my use case I have dynamic dict (which I want to transform into attr-class) and I simply do not need some of the keys.
I think I figured out a more elegant solution which allows you to take advantage of the features of attrs while also tweaking the __init__ logic. See attrs documentation for more info.
#attr.s(auto_attribs=True, auto_detect=True)
class Foo():
a: int
optional: int = 3
def __init__(self,**kwargs):
filtered = {
attribute.name: kwargs[attribute.name]
for attribute in self.__attrs_attrs__
if attribute.name in kwargs
}
self.__attrs_init__(**filtered)
The code above allows you to specify extraneous keyword args. It also allows for optional args.
>>> Foo(a = 1, b = 2)
Foo(a=1, optional=3)
attrs detects the explicit init method (due to auto_detect=True) and still creates the init function, but calls it __attrs_init__. This allows you do define your own init function to do preprocessing and then call __attrs_init__ when you are done.
>>> import inspect
>>> print(inspect.getsource(Foo.__attrs_init__))
def __attrs_init__(self, a, optional=attr_dict['optional'].default):
self.a = a
self.optional = optional
class FromDictMixin:
#classmethod
def from_dict(cls, data: dict):
return cls(**{
a.name: data[a.name]
for a in cls.__attrs_attrs__
})
#attrs
class Foo(FromDictMixin):
a = attrib()
It works, but it looks kinda ugly. I was hopping that attrs lib had out of the box solution.
This seems to be more of a question of serialization/deserialization/validation and attrs is quite strict on its argument for multiple reasons. One of them is typing (as in types, not pressing keys :)) and the other is robustness/debugabiity. Ignoring arguments that you might have just misspelt can lead to very frustrating moments. It's better to move this kind of stuff into a separate layer.
You can find some possible tools for that in https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs.
I had to do something similar but I didn't want to write a custom __init__ method for every class. So I created a decorator where it would attach an __init__ method to the class before instantiation then wrap in attrs.define decorator.
This is just an example but does what you want.
import attrs
def define(cls):
def __init__(cls, **kwargs):
filtered = {}
for attr in cls.__attrs_attrs__:
if attr.name in kwargs:
filtered[attr.name] = kwargs[attr.name]
cls.__attrs_init__(**filtered)
def wrapper(*args, **kwargs):
nonlocal cls
cls.__init__ = __init__
cls = attrs.define(cls)
return cls(*args, **kwargs)
return wrapper
#define
class Booking:
id: int
id_hash: str
booking = {"id": 1, "id_hash": "a3H33lk", "foo": "bar"}
b = Booking(**booking)
print(b)
# Booking(id=1, id_hash='a3H33lk')

More efficient way of setting default method argument to instance attribute [duplicate]

I want to pass a default argument to an instance method using the value of an attribute of the instance:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
When trying that, I get the following error message:
NameError: name 'self' is not defined
I want the method to behave like this:
C("abc").process() # prints "abc"
C("abc").process("xyz") # prints "xyz"
What is the problem here, why does this not work? And how could I make this work?
You can't really define this as the default value, since the default value is evaluated when the method is defined which is before any instances exist. The usual pattern is to do something like this instead:
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=None):
if formatting is None:
formatting = self.format
print(formatting)
self.format will only be used if formatting is None.
To demonstrate the point of how default values work, see this example:
def mk_default():
print("mk_default has been called!")
def myfun(foo=mk_default()):
print("myfun has been called.")
print("about to test functions")
myfun("testing")
myfun("testing again")
And the output here:
mk_default has been called!
about to test functions
myfun has been called.
myfun has been called.
Notice how mk_default was called only once, and that happened before the function was ever called!
In Python, the name self is not special. It's just a convention for the parameter name, which is why there is a self parameter in __init__. (Actually, __init__ is not very special either, and in particular it does not actually create the object... that's a longer story)
C("abc").process() creates a C instance, looks up the process method in the C class, and calls that method with the C instance as the first parameter. So it will end up in the self parameter if you provided it.
Even if you had that parameter, though, you would not be allowed to write something like def process(self, formatting = self.formatting), because self is not in scope yet at the point where you set the default value. In Python, the default value for a parameter is calculated when the function is compiled, and "stuck" to the function. (This is the same reason why, if you use a default like [], that list will remember changes between calls to the function.)
How could I make this work?
The traditional way is to use None as a default, and check for that value and replace it inside the function. You may find it is a little safer to make a special value for the purpose (an object instance is all you need, as long as you hide it so that the calling code does not use the same instance) instead of None. Either way, you should check for this value with is, not ==.
Since you want to use self.format as a default argument this implies that the method needs to be instance specific (i.e. there is no way to define this at class level). Instead you can define the specific method during the class' __init__ for example. This is where you have access to instance specific attributes.
One approach is to use functools.partial in order to obtain an updated (specific) version of the method:
from functools import partial
class C:
def __init__(self, format):
self.format = format
self.process = partial(self.process, formatting=self.format)
def process(self, formatting):
print(formatting)
c = C('default')
c.process()
# c.process('custom') # Doesn't work!
c.process(formatting='custom')
Note that with this approach you can only pass the corresponding argument by keyword, since if you provided it by position, this would create a conflict in partial.
Another approach is to define and set the method in __init__:
from types import MethodType
class C:
def __init__(self, format):
self.format = format
def process(self, formatting=self.format):
print(formatting)
self.process = MethodType(process, self)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
This allows also passing the argument by position, however the method resolution order becomes less apparent (which can affect the IDE inspection for example, but I suppose there are IDE specific workarounds for that).
Another approach would be to create a custom type for these kind of "instance attribute defaults" together with a special decorator that performs the corresponding getattr argument filling:
import inspect
class Attribute:
def __init__(self, name):
self.name = name
def decorator(method):
signature = inspect.signature(method)
def wrapper(self, *args, **kwargs):
bound = signature.bind(*((self,) + args), **kwargs)
bound.apply_defaults()
bound.arguments.update({k: getattr(self, v.name) for k, v in bound.arguments.items()
if isinstance(v, Attribute)})
return method(*bound.args, **bound.kwargs)
return wrapper
class C:
def __init__(self, format):
self.format = format
#decorator
def process(self, formatting=Attribute('format')):
print(formatting)
c = C('test')
c.process()
c.process('custom')
c.process(formatting='custom')
You can't access self in the method definition. My workaround is this -
class Test:
def __init__(self):
self.default_v = 20
def test(self, v=None):
v = v or self.default_v
print(v)
Test().test()
> 20
Test().test(10)
> 10
"self" need to be pass as the first argument to any class functions if you want them to behave as non-static methods.
it refers to the object itself. You could not pass "self" as default argument as it's position is fix as first argument.
In your case instead of "formatting=self.format" use "formatting=None" and then assign value from code as below:
[EDIT]
class c:
def __init__(self, cformat):
self.cformat = cformat
def process(self, formatting=None):
print "Formating---",formatting
if formatting == None:
formatting = self.cformat
print formatting
return formatting
else:
print formatting
return formatting
c("abc").process() # prints "abc"
c("abc").process("xyz") # prints "xyz"
Note : do not use "format" as variable name, 'cause it is built-in function in python
Instead of creating a list of if-thens that span your default arguements, one can make use of a 'defaults' dictionary and create new instances of a class by using eval():
class foo():
def __init__(self,arg):
self.arg = arg
class bar():
def __init__(self,*args,**kwargs):
#default values are given in a dictionary
defaults = {'foo1':'foo()','foo2':'foo()'}
for key in defaults.keys():
#if key is passed through kwargs, use that value of that key
if key in kwargs: setattr(self,key,kwargs[key])
#if no key is not passed through kwargs
#create a new instance of the default value
else: setattr(self,key, eval(defaults[key]))
I throw this at the beginning of every class that instantiates another class as a default argument. It avoids python evaluating the default at compile... I would love a cleaner pythonic approach, but lo'.

How to automatically add attributes to a class in python?

I have a class with quite a few attributes that are provided when instanciating (init).
Looks like this, but with about 30 more attr:
class SomeObject:
def __init__(self,
first,
second):
self.first = first
self.second = second
Works fine, but gets very long an repetitive and hard to update.
Before I used a much smaller class:
class SomeObject:
def __init__(self, **attr):
self.__dict__.update(attr)
Works fine, much easier, but hard to keep track. When using an IDE (I use PyCharm) I will have no hints when writing an object and autocomplete hints are usually not working.
I would look for some mix of both classes, like:
class SomeObject:
def __init__(self,
first,
second):
self.__dict__.update(???) # I would like to do it in a single line, so I dont have to write a new attr two (three...) times.
Anybody knows if / how this is possible? Thanks a lot
Python 3.4 +
PyCharm 2016.1 CommunityEdition
... Edit / Additional ...
The problem seems to be primarily to "preserve" the code inspection in the IDE, so that the autocompletion etc. is still available on the object.
you can simply get a dictionary of the local variables with the locals builtin, then remove self and update:
class test:
def __init__(self,
a,b,c,d,e,f,g,h,i,j,k,l,m,
n,o,p,q,r,s,t,u,v,w,x,y,z):
args = locals()
del args["self"]
if "args" in args:
del args["args"]
self.__dict__.update(args)
args = list(range(26))
x = test(*args)
from string import ascii_lowercase as letters
assert args == [getattr(x,c) for c in letters]
print("worked")
Although if you really want to make your IDE happy it needs to see the explicit attribute assignment, so you could instead write a code to write the code:
def write_init_code(func):
self,*args = func.__code__.co_varnames
return "\n".join(["{0}.{1} = {1}".format(self,a)
for a in args])
class test:
def __init__(self,a,b,c,d,e,f,*v,**kw):
pass #just a moment...
print(write_init_code(test.__init__))
then just copy-paste and indent the result from write_init_code into the actual function and you are done.
The module inspect allows you to query the running program. The below example demonstrates its use:
import inspect
class Test:
def __init__(self, a, b, c, d):
argInfo = inspect.getargvalues(inspect.currentframe())
print(argInfo)
# Print argument values in order of parameters
print([ argInfo.locals[paramName] for paramName in argInfo.args[1:] ])
t = Test(1,2,3,4)
output:
ArgInfo(args=['self', 'a', 'b', 'c', 'd'], varargs=None, keywords=None, locals={'c': 3, 'self': <__main__.Test object at 0x7fb2d9c0ce10>, 'b': 2, 'd': 4, 'a': 1})
[1, 2, 3, 4]
As you can see, you can access a dictionary of the arguments and their corresponding parameter name through ArgInfo.locals.
The second print out is an example of how to put the argument values in the order of the parameter declaration.
You can use this to populate your instance variables, or you can simply copy the ArgInfo.locals dictionary.
You can use locals() to achieve what you want:
class SomeObject:
def __init__(self, first, second):
kwargs = locals()
kwargs.pop('self') # remove `self` keywork from the dict
self.__dict__.update(kwargs)
With dataclasses this is now easy and fully supported by PyCharm. By adding typing annotations and using decorator #dataclasses.dataclass we make class members act as instance members.
import dataclasses
import typing
#dataclasses.dataclass
class SomeObject:
first: str
second: str
class_member1 = None
class_member2: typing.ClassVar[str] = None
SomeObject(first="first", second="second")
Note that class fields with an annotation like first and second work like instance members. While unannotated fields like class_member1 remain as class members.
But if you want some field as a class member but still want to have typing information then use annotation typing.ClassVar. This allows you to have class_member2 which is a class member and also has typing information.
*** Made this function to automates attributes from args ***
def autoargs(self: object, locals: object) -> dict:
''' Put this line in the __init__ method of a class:
self.__dict__ = autoargs(self, locals())
to auto-attributes args to class '''
kwargs = locals
kwargs.pop('self') # remove `self` keywork from the dict
self.__dict__.update(kwargs)
return self.__dict__
class SomeObject:
def __init__(self, first, second):
# function to auto make the attributes from args (see the function doc)
self.__dict__ = autoargs(self, locals())
a = SomeObject(1,2)
print(a.first)
print(a.second)
output:
1
2
EDIT: Now that python 3.7 is out, the #dataclass class decorator solves this very elegantly.
ORIGINAL ANSWER:
With multiple cursors in PyCharm, you can easily convert
self.first = first
self.second = second
to a very long line like:
self.first, self.second = first, second
This is what I've done to keep PyCharm smart

How to pass a class constructor to a python function

Suppose I have a class Foo, I want to define a function that receives the class constructor as a parameter:
def bar(class_name):
local_class = None
# TODO: if I call bar(Foo()), I want to get local_class = Foo()
How can I implement the function?
The following bar function will work. Note, the first parameter will be a class itself and not the name of a class, so "class_name", which implies that it's a str, is misleading. args will be a tuple of args to initialize klass objects with, *-unpacked in the calls to klass. You said in a later comment that you wanted to "create multiple independent objects", all of the same class and initialized with the same args, so I've revised my answer to reflect that:
def bar(klass, *args):
# Now you can create multiple independent objects of type klass,
# all initialized with the same args
obj1 = klass(*args)
obj2 = klass(*args)
# ...
# do whatever you have in mind with the objs
Your "local_class" isn't a class at all, but rather an instance of klass, so that's a bad name; and anyway you want several of them.
Assuming Foo objects are initialized with three int arguments, and Baz objects with two strings, you can call bar like so:
bar(Foo, 1, 2, 3)
bar(Baz, 'Yo', 'bro')
etc.
Especially in a dynamically-typed language like Python, reasoning about code is more difficult when variables have misleading names.
When can pass the classname as an argument to your function, and then call class_name(). E.g., if you also want to pass arguments.
class Foo:
def __init__(self, arg1, arg2):
pass
def bar1(class_name):
args = ("val1", "val2")
local_class = class_name(*args)
or
def bar2(class_name):
kwargs = {'arg1':'val1','arg2':'val2'}
local_class = class_name(**kwargs)
You can call the functions like:
one = bar1(Foo)
two = bar2(Foo)
If you really want to call the class from a string read this post. I would suggest you use #Evan Fosmark's solution because use of eval and globals should be avoided

Is it possible get a dictionary of passed in parameters similar to kwargs(python)?

I'm currently creating an object like this:
class Obj(object):
def __init__(self,**kwargs):
params = ['val1','val2','val3','val4',...]
for p in params:
setattr(self,p,kwargs.get(p,None))
I'm doing this so I don't have to do this:
class Obj(object):
def __init__(self,val1=None,val2=None,val3=None,val4=None,...):
self.val1=val1
self.val2=val2
self.val3=val3
self.val4=val4
...
My question is, can you do a mix of the two? Where I can define the expected parameters yet still loop the parameters to set the attributes? I like the idea of defining the expected parameters because it is self documenting and other developers don't have to search for what kwargs are used.
I know it seems pretty petty but I'm creating an object from some XML so I'll be passing in many parameters, it just clutters the code and bugs me.
I did google this but couldn't find anything, probably because dictionary and kwargs together point to kwarg examples.
UPDATE: To be more specific, is it possible to get a dictionary of passed in parameters so I don't have to use kwargs at all?
Sudo code:
class Obj(object):
def __init__(self,val1=None,val2=None,val3=None,val4=None,...):
for k,v in dictionary_of_paramters.iteritems():
setattr(self,k,v)
You can use the inspect module:
import inspect
def myargs(val1, val2, val3=None, val4=5):
print inspect.currentframe().f_locals
it shows all the locals available on the current stack frame.
myargs('a','b')
==> {'val3': None, 'val2': 'b', 'val1': 'a', 'val4': 5}
(note: it's not guaranteed to be implemented on all Python interpreters)
edit: i concur that it's not a pretty solution. what i would do is more like:
def _yourargs(*names):
"returns a dict with your named local vars"
alllocs = inspect.stack()[1][0].f_locals
return {n:alllocs[n] for n in names}
def askformine(val1, val2, val3=None, val4=5):
"example to show just those args i'm interested in"
print _yourargs('val1','val2','val3','val4')
class Obj(object):
"example inserting some named args as instance attributes"
def __init__(self, arg1, arg2=4):
self.__dict__.update(_yourargs('arg1','arg2'))
edit2 slightly better:
def pickdict(d,*names):
"picks some values from a dict"
return {n:d[n] for n in names}
class Obj(object):
"example inserting some named args as instance attributes"
def __init__(self, arg1, arg2=4):
self.__dict__.update(pickdict(locals(),'arg1','arg2'))
There is no nice way to get a dictionary of all the arguments to a function. The **kwargs syntax only collects up the extra keyword arguments, not the ones that match explicit parameters in the function definition.
Although you won't be able to get the parameters without using kwargs or the inspect module (see other answers), you can do something like this...
class Obj(object):
def __init__(self, **kwargs):
self.__dict__.update(**kwargs)
Every object has a dictionary that stores all of the attributes, which you can access via self.__dict__. Then you're just using update to set all of the attributes in that object's internal dictionary.
See this question on some discussion of this method.
If you want to obtain the args dict at the very top of your method, before you define any locals, this is as simple as:
class Obj(object):
def __init__(self,val1=None,val2=None,val3=None,val4=None):
kwargs = dict(locals())
To read this dict later on, some introspection magic is required:
class Obj(object):
def __init__(self,val1=None,val2=None,val3=None,val4=None):
# feel free to add more locals
loc = dict(locals())
fun = sys._getframe().f_code
kwargs = {x:loc[x] for x in fun.co_varnames[:fun.co_argcount]}
You can also make the latter reusable by adding a function like this:
def getargs():
f = sys._getframe(1)
return {x:f.f_locals[x] for x in f.f_code.co_varnames[:f.f_code.co_argcount]}
and then:
class Obj(object):
def __init__(self,val1=None,val2=None,val3=None,val4=None):
# feel free to add more locals
kwargs = getargs()
This is cpython-specific, I guess.
Yes you can mix the two.
See below:
def method(a, b=1, *args, **kwargs):
'''some code'''
This is valid. Here:
'a' is a required argument
'b' is a default argument
'args' will have all the non-keyword arguments and
'kwargs' will have all the keyword arguments.
Example:
method(1,2,3,4,5,test=6,test1=7)
This call will have:
a=1
b=2
args=(3,4,5)
kwargs={'test':6,'test1':7}
A kind of an ugly workaround: Inject extra arguments into kwargs and use it where you want to loop over all keyword arguments (PS this is an example usage of the inspect module, but not recommended for production use):
#!/usr/bin/env python
import inspect
def inject_defaults(func):
""" injects '__defaults' key into into kwargs,
so it can be merged with kwargs in the decorated method """
args, varargs, varkwargs, defaults = inspect.getargspec(func)
have_defaults = args[-len(defaults):]
defaults_dict = dict(zip(have_defaults, defaults))
def fun(*args, **kwargs):
kwargs['__defaults'] = defaults_dict
return func(*args, **kwargs)
return fun
#inject_defaults
def f1(a,b,c, x=1, **kwargs):
kwargs.update(kwargs['__defaults'])
del kwargs['__defaults']
for k, v in kwargs.items():
# here, x, y and z will appear
print(k, v)
f1(1, 2, 3, y=3, z=2)
# prints
# ('y', 3)
# ('x', 1)
# ('z', 2)

Categories