I have a class with many instance variables with default values, which optionally can be overridden in instantiantion (note: no mutable default arguments).
Since it's quite redundant to write self.x = x etc. many times, I initialise the variables programmatically.
To illustrate, consider this example (which has, for the sake of brevity, only 5 instance variables and any methods omitted):
Example:
# The "painful" way
class A:
def __init__(self, a, b=2, c=3, d=4.5, e=5):
self.a = a
self.b = b
self.c = c
self.d = d
self.e = e
# The "lazy" way
class B:
def __init__(self, a, b=2, c=3, d=4.5, e=5):
self.__dict__.update({k: v for k, v in locals().items() if k!='self'})
# The "better lazy" way suggested
class C:
def __init__(self, a, b=2, c=3, d=4.5, e=5):
for k, v in locals().items():
if k != 'self':
setattr(self, k, v)
x = A(1, c=7)
y = B(1, c=7)
z = C(1, c=7)
print(x.__dict__) # {'d': 4.5, 'c': 7, 'a': 1, 'b': 2, 'e': 5}
print(y.__dict__) # {'d': 4.5, 'c': 7, 'a': 1, 'b': 2, 'e': 5}
print(z.__dict__) # {'d': 4.5, 'c': 7, 'a': 1, 'b': 2, 'e': 5}
So to make my life easier, I use the idiom shown in class B, which yields the same result as A.
Is this bad practice? Are there any pitfalls?
Addendum:
Another reason to use this idiom was to save some space - I intended to use it in MicroPython. For whatever reason Because locals work differently there, only the way shown in class A works in it.
I would actually suggest using the code shown in class A. What you have is repetitive code, not redundant code, and repetitive isn't always bad. You only have to write __init__ once, and keeping one assignment per instance variable is good documentation (explicit and clear) for what instance variables your class expects.
One thing to keep in mind, though, is that too many variables that you can initialize as distinct parameters may be a sign that your class needs to be redesigned. Would some of the individual parameters make more sense being grouped into separate lists, dicts, or even additional classes?
Try a more pythonic approach:
class C:
def __init__(self,a,b=2,c=3,d=4.5,e=5):
for k,v in locals().iteritems():
setattr(self,k,v)
c = C(1)
print c.a, c.b
1 2
This approach may be a line or two longer, but the line lengths are shorter, and your intent less convoluted. In addition, anyone who may try to reuse your code will be able to access your objects' attributes as expected.
Hope this helps.
Edit: removed second approach using kwargs bc it does not address default variable requirement.
the important take-away here is that a user of your code would not be able to access your object's attributes as expected if done like your example class B shows.
Related
I would like to know whether there is a more efficient way of replacing a particular value in a dictionary. By "more efficient" I mean to avoid looking up the same key twice. I don't expect it would make a big difference, but I find myself doing the following alot:
foo = {'a': 0, 'b': 1}
foo['b'] = bar(foo['b'])
Update
I think the assignment above is "looking up the same key twice" because the following prints "Hashing b" three times.
class LoudKey:
def __init__(self, key):
self.key = key
def __hash__(self):
print(f'Hashing {self.key}')
return self.key.__hash__()
b = LoudKey('b')
foo = {'a': 0, b: 1}
# first "Hashing b"
foo[b] = float(foo[b])
# next two "Hashing b"s
If dict.__getitem__ and dict.__setitem__ are really not duplicating effort somehow, an elaboration on that would also be accepted as an answer.
You can do it by making the dictionary items a mutable type, then mutating it. The simplest is a list with a single element.
>>> b = LoudKey('b')
>>> foo = {'a': [0], b: [1]}
Hashing b
>>> ref = foo[b]
Hashing b
>>> ref[0] = float(ref[0])
>>> foo
{'a': [0], <__main__.LoudKey object at 0x0000000014C66F40>: [1.0]}
You're right that it wouldn't make much difference in practice, though.
Let's say we have a function that takes some arguments and a dict that is a superset of values required to invoke the function:
d = {"a": 1, "b": 2, "c": 3}
def foo(a, b):
print(a, b)
# this won't work
foo(**d)
# this works but is tedious the more values there are
foo(**{k: v for k,v in d.items() if k not in ("c")})
Is there a more elegant solution?
Instead of doing this:
def foo(a, b):
print(a, b)
You could do this:
def foo(a, b, **kwargs):
print(a, b)
Then the function would just ignore all the unneeded arguments.
However, in my view, there are some problems with this approach.
Sometimes you don't own the source code of the function. Of course, you can make lots of wrapper functions. If you do that manually, you'll have to change the signatures of your helper functions every time the library authors modify the function signature.
This solution can make some bugs harder to find. Maybe it's better to explicitly state that you're only using some of the elements from the dictionary.
You can still make it easier using the inspect module of the standard library.
a) Make a decorator that makes the function filter its keyword arguments.
For instance:
import inspect
def filter_kwargs(f):
arg_names = inspect.getargspec(f).args
def _f(*args, **kwargs):
return f(*args, **{k: v for k, v in kwargs.items() if k in arg_names})
return _f
# Use it like this:
filter_kwargs(foo)(a_dict)
b) You can create a function that transforms the argument to fit the function
def fit_for(f, kwargs):
arg_names = inspect.getargspec(f).args
return {k: v for k, v in kwargs.items() if k in arg_names}
foo(fit_for(foo, a_dict))
Optionally, you can expand this function to also take into account the *args and **kwargs in the original function.
If this is a recurring pattern in your code, you can just pass the dict as a single argument.
Also, as #chepner pointed out in the comments, consider creating a class.
you could use keyword arguments:
def foo(**kwarg):
print(kwarg['a'], kwarg['b'])
foo(**d)
output:
1 2
Assuming you do not want to modify foo's function signature, you can use foo.__code__.co_varnames to get the argument names of foo if you are using cpython
foo(**{k: d[k] for k in foo.__code__.co_varnames})
You can do something like:
d = {"a": 1, "b": 2, "c": 3}
def foo(a, b, *arg, **kwarg):
print(a, b)
foo(**d)
Consider the code
a = 2
b = 3
mylist = {'a' : a, 'b' : b, 'product' : a * b}
This produces a dictionary of three fields, of which the third is calculated using the values of the first and second. I am looking for a more compact definition of mylist. I have tried (1)
mylist = {'a' : 2, 'b' : 3, 'product' : a * b}
which gives the error
NameError: name 'a' is not defined
and (2)
mylist = {'a' : 2, 'b' : 3, 'product' : mylist['a'] * mylist['b']}
which gives the error
NameError: name 'mylist' is not defined
I would like to find a shorter command of the form (1) because you do not need to need to mention the name of the dictionary. Maybe there exists something like currentdictionary['a']?
I'd use something like a computed property in this case. It'll lazily evaluate the property when you need it; at call time. Much more robust than actively managing the product as a key-value pair.
class Pair(object):
def __init__(self, a, b):
self.a = a
self.b = b
#property
def product(self):
return self.a * self.b
Sample Output:
>>> Pair(2, 3).product
6
Using a dictionary here is possible but strikes me as a shoehorned solution, (1) you'd need to contend with checking if key(s) exist on lookup and (2) also maintaining a synchronized product should a or b change.
You could use a function to specify the desired keys of the dictionary and use inspect to view the signature at run time:
import inspect
a = 2
b = 3
def get_list(a, b, product):
pass
mylist = inspect.getcallargs(get_list, a, b, a*b)
Output:
{'a': 2, 'product': 6, 'b': 3}
The benefit of using a function in this case is that you can built a solution to find mylist around other potential objects in your code.
I can not think of a one liner to do that.
from functools import reduce
mylist = {'a' : 2, 'b' : 3}
mylist["product"] = reduce(lambda x,y: x*y, mylist.values())
How can one define the initial contents of the keyword arguments dictionary? Here's an ugly solution:
def f(**kwargs):
ps = {'c': 2}
ps.update(kwargs)
print(str(ps))
Expected behaviour:
f(a=1, b=2) => {'c': 2, 'a': 1, 'b': 2}
f(a=1, b=2, c=3) => {'c': 3, 'a': 1, 'b': 2}
Yet, I would like to do something a little bit more in the lines of:
def f(**kwargs = {'c': 2}):
print(str(kwargs))
Or even:
def f(c=2, **kwargs):
print(str(???))
Ideas?
First to address the issues with your current solution:
def f(**kwargs):
ps = {'c': 2}
ps.update(kwargs)
print(str(ps))
This creates a new dictionary and then has to take the time to update it with all the values from kwargs. If kwargs is large that can be fairly inefficient and as you pointed out is a bit ugly.
Obviously your second isn't valid.
As for the third option, an implementation of that was already given by Austin Hastings
If you are using kwargs and not keyword arguments for default values there's probably a reason (or at least there should be; for example an interface that defines c without explicitly requiring a and b might not be desired even though the implementation may require a value for c).
A simple implementation would take advantage of dict.setdefault to update the values of kwargs if and only if the key is not already present:
def f(**kwargs):
kwargs.setdefault('c', 2)
print(str(kwargs))
Now as mentioned by the OP in a previous comment, the list of default values may be quite long, in that case you can have a loop set the default values:
def f(**kwargs):
defaults = {
'c': 2,
...
}
for k, v in defaults.items():
kwargs.setdefault(k, v)
print(str(kwargs))
A couple of performance issues here as well. First the defaults dict literal gets created on every call of the function. This can be improved upon by moving the defaults outside of the function like so:
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
for k, v in DEFAULTS.items():
kwargs.setdefault(k, v)
print(str(kwargs))
Secondly in Python 2, dict.items returns a copy of the (key, value) pairs so instead dict.iteritems or dict.viewitems allows you to iterate over the contents and thus is more efficient. In Python 3, 'dict.items` is a view so there's no issue there.
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
for k, v in DEFAULTS.iteritems(): # Python 2 optimization
kwargs.setdefault(k, v)
print(str(kwargs))
If efficiency and compatibility are both concerns, you can use the six library for compatibility as follows:
from six import iteritems
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
for k, v in iteritems(DEFAULTS):
kwargs.setdefault(k, v)
print(str(kwargs))
Additionally, on every iteration of the for loop, a lookup of the setdefault method of kwargs needs to be performed. If you truly have a really large number of default values a micro-optimization is to assign the method to a variable to avoid repeated lookup:
from six import iteritems
DEFAULTS = {
'c': 2,
...
}
def f(**kwargs):
setdefault = kwargs.setdefault
for k, v in iteritems(DEFAULTS):
setdefault(k, v)
print(str(kwargs))
Lastly if the number of default values is instead expected to be larger than the number of kwargs, it would likely be more efficient to update the default with the kwargs. To do this, you can't use the global default or it would update the defaults with every run of the function, so the defaults need to be moved back into the function. This would leave us with the following:
def f(**kwargs):
defaults = {
'c': 2,
...
}
defaults.update(kwargs)
print(str(defaults))
Enjoy :D
A variation possible on your first approach on Python 3.5+ is to define the default and expand the provided arguments on a single line, which also lets you replace kwargs on the same line, e.g.:
def f(**kwargs):
# Start with defaults, then expand kwargs which will overwrite defaults if
# it has them
kwargs = {'c': 2, **kwargs}
print(str(kwargs))
Another approach (that won't produce an identical string) that creates a mapping that behaves the same way using collections.ChainMap (3.3+):
from collections import ChainMap
def f(**kwargs):
# Chain overrides over defaults
# {'c': 2} could be defined outside f to avoid recreating it each call
ps = ChainMap(kwargs, {'c': 2})
print(str(ps))
Like I said, that won't produce the same string output, but unlike the other solutions, it won't become more and more costly as the number of passed keyword arguments increases (it doesn't have to copy them at all).
Have you tried:
def f(c=2, **kwargs):
kwargs['c'] = c
print(kwargs)
Update
Barring that, you can use inspect to access the code object, and get the keyword-only args from that, or even all the args:
import inspect
def f(a,b,c=2,*,d=1,**kwargs):
code_obj = inspect.currentframe().f_code
nposargs = code_obj.co_argcount
nkwargs = code_obj.co_kwonlyargcount
localvars = locals()
kwargs.update({k:localvars[k] for k in code_obj.co_varnames[:nposargs+nkwargs]})
print(kwargs)
g=f
g('a', 'b')
In order to optimize a code in one single line, I am trying to write a determinate statement in my code without calling any function or method. While I was thinking about this I wondered if this is even possible in my case. I was searching some information about this but it seems to be very rarely, but in my current work I must be able to keep the code intact except that optimize section.
Hope you could give me a hand. Any help is welcome.
This is my current progress.
def count_chars(s):
'''(str) -> dict of {str: int}
Return a dictionary where the keys are the characters in s and the values
are how many times those characters appear in s.
>>> count_chars('abracadabra')
{'a': 5, 'r': 2, 'b': 2, 'c': 1, 'd': 1}
'''
d = {}
for c in s:
if not (c in d):
# This is the line it is assumed to be modified without calling function or method
else:
d[c] = d[c] + 1
return d
How about this, as mentioned in the comments, it does implicitly use functions, but I think it may be the sort of thing you are looking for?
s='abcab'
chars={}
for char in s:
if char not in chars:
chars[char]=0
chars[char]+=1
Result
{'a': 2, 'b': 2, 'c': 1}