How can I write updating class parameter values more concisely? - python

Simple question; any ideas how to write this more concisely? I have a class with these (already initialised) parameters that I would like to have the option for to update.
def update_parameters(self, a=None, g=None, n=None, d=None, s=None, K=None, A=None, L=None):
# If the argument is not none, change the parameter value of the class
if a:
self.a = a
if g:
self.g = g
if n:
self.n = n
if d:
self.d = d
if s:
self.s = s
if K:
self.K = K
if A:
self.A = A
if L:
self.L = L

You could just use **args instead of explicitly defining all of those parameters. You could then use setattr() to update any valid ones.
For example:
class Test():
def __init__(self):
self.a = None
self.g = None
self.n = None
def update_parameters(self, **args):
for arg, value in args.items():
if hasattr(self, arg) and value is not None:
setattr(self, arg, value)
def __str__(self):
return f"{self.a}, {self.g}, {self.n}"
t = Test()
t.update_parameters(a=21, z='rubbish', n=100)
t.update_parameters(a=42, n=None)
print(t)
This shows a and n being updated and z being ignored:
42, None, 100
Obviously you need to extend it to all of your parameters.
FYI:
setattr(x, 'foobar', 123) is equivalent to x.foobar = 123

You can use **kwargs and force the users to only pass keyword argument. Then it's easy to iterate over them and use setattr():
class A:
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if v is not None:
setattr(self, k, v)
but this has a problem, Any not-none values are going to be added to the instance's dictionary. So maybe you can add another check to see if the k is valid.
class A:
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if k in valid_attrs and v is not None:
setattr(self, k, v)
Or even use the instance's __dict__:
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if k in self.__dict__ and v is not None:
setattr(self, k, v)
Here is the complete code:
class A:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def update_parameters(self, **kwargs):
for k, v in kwargs.items():
if k in self.__dict__ and v is not None:
setattr(self, k, v)
def __repr__(self):
return f"A(a={self.a}, b={self.b}, c={self.c})"
obj = A(10, 20, 30)
print(obj)
obj.update_parameters(b=40, c=50, d=60)
print(obj)
output:
A(a=10, b=20, c=30)
A(a=10, b=40, c=50)
This ignores the d=60 value. If you want you can raise exception.

How about making a seperate method that checks for None?
def UpdateIfNotNone(To, From):
if From is not None:
To = From
Then the original method can be like
def update_parameters(self, a=None, g=None, ...):
worklist = [(self.a,a), (self.g, g) ...]
for task in worklist:
UpdateIfNotNone(task[0], task[1])

Related

python: Overloading ** dict unpacking

I would like to be able to unpack an object from a dict-similar class.
current:
f(**m.to_dict())
preferred
f(**m)
This would work if starstarprepare existed:
class M:
#... __getitem__, __setitem__
def __starstarprepare__(self):
md = self.to_dict()
return md
You can use collections.abc.Mapping.
from collections.abc import Mapping
class M(Mapping):
def __iter__(self):
return iter(self.to_dict())
def __getitem__(self, item):
return self.to_dict()[item]
def __len__(self):
return len(self.to_dict())
** works with any mapping type. One way to make M a mapping type is to subclass collections.abc.Mapping and implement __getitem__, __iter__, and __len__:
from collections.abc import Mapping
class M(Mapping):
def __init__(self):
self.a = 3
self.b = 5
def __getitem__(self, key):
return getattr(self, key)
def __iter__(self):
yield 'a'
yield 'b'
def __len__(self):
return 2
def foo(**kwargs):
for k, v in kwargs.items():
print(k, v)
m = M()
foo(**m)
If you already have a to_dict method, all three of the magic methods can be wrappers around the corresponding dict methods.
class M(Mapping):
def __init__(self):
self.a = 3
self.b = 5
def to_dict(self):
return {'a': self.a, 'b': self.b}
def __getitem__(self, key):
return self.to_dict()[key]
def __iter__(self):
return iter(self.to_dict())
def __len__(self):
return len(self.to_dict())
Solution due to #peter
class M:
# ... __getitem__ and other functions
def keys(self):
k = self.to_dict().keys()
return k

How to force calling __getitem__ using items() and values() in dict inheritance in python

I use python3.7 and for some application create a class inherited from a dict, but have a problem to implement items() and values() methods to made it work correctly. This class overrides many methods, but here I placed a very simplified example just to illustrate exact problem:
class MyFunction:
def __call__(self):
return 5
class MyDict(dict):
def __getitem__(self, key):
item = super().__getitem__(key)
if isinstance(item, MyFunction):
return item()
else:
return item
def get(self, key, default=None):
if self.__contains__(key):
return self.__getitem__(key)
if isinstance(default, MyFunction):
return default()
return default
# def __copy__(self):
# return type(self)(self)
#
# def copy(self):
# return self.__copy__()
# def __iter__(self):
# return super().__iter__()
d = MyDict(a=MyFunction(), b=3)
I want than I get a value by key instances of MyFunction be called. This works well:
for k in d:
print(k, d[k])
and prints the expected output:
a 5
b 3
But these two do not:
for v in d.values():
print(v)
for k, v in d.items():
print(k, v)
They print the function's repr.
How can I achive them to call __getitem__?
Remark: It can be some kind of dict built-in class optimization (I would like to not inherit form UserDict or Mapping). For example if I uncomment:
def __iter__(self):
return super().__iter__()
The calls:
new_d = d.copy()
new_d = dict(d)
new_d = dict(**d)
will call the __getitem__
According to the Python documentation, accessing a dict-like object with the d[k] syntax is just syntactic sugar for d.__getitem__(k).
However, as you discovered, the default implementation of values() and items() do not call __getitem__() at all. If you want them to, you'll have to implement them yourself.
Here is an implementation that hopefully does what you want:
class MyFunction:
def __call__(self):
return 5
class MyDict(dict):
def __getitem__(self, key):
item = super().__getitem__(key)
if isinstance(item, MyFunction):
return item()
else:
return item
def values(self):
for k in self:
yield(self[k])
def items(self):
for k in self:
yield(k, self[k])
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
d = MyDict(a=MyFunction(), b=3)
for k in d:
print(d.get(k))
for v in d.values():
print(v)
for k, v in d.items():
print(k, v)
This is the output:
5
3
5
3
a 5
b 3

How should I deal with this recursive behaviour?

Suppose I had a class 'A' and nested within 'A' was class 'B' and both objects were for use publicly...
class A(object):
class B(object):
def __init__(self):
self._b = dict()
self.a = A()
def __setitem__(self, key, value):
# set self._b
self.a[key] = value # recursion ??
def __init__(self):
self._a = dict()
self.b = A.B()
def __setitem__(self, key, value):
# set self._a
self.b[key] = value # recursion ??
Now when doing...
d = A()
d['sap'] = 'nin'
A.__setitem__ will call B.__setitem__, which will call A.__setitem__ and so on...
If this is the desired interface, what would be the best way to deal with this recursive behaviour?
for more context, I've posted on codereview here...
https://codereview.stackexchange.com/questions/85842/a-dictionary-that-allows-multiple-keys-for-one-value
I've tried using another nested class to pass to other internal functions so that they can determine whether it was called from the user, or internally from other functions...
class A(object):
class _Container(object):
def __init__(self, _object):
self.object = _object
class B(object):
def __init__(self, d, **kwargs):
if is instance(d, A._Container):
self.a = d.object
else:
self.a = A(A._Container(self))
def __init__(self, d, **kwargs):
if isinstance(d, A._Container):
self.b = d.object
else:
self.b = A.B(A._Container(self))
This works for init, but I'm not sure I'd like to do that with setitem and all other methods.
I've also tried delegating all or most of the work to class 'A' so essentially class 'B''s methods act as hooks...
class A(object):
class B(object):
def __setitem__(self, key, value):
# some functionality
self.a[key] = value
def __setitem__(self, key, value):
self._a[key] = value
self._b[key] = value
If it helps, here is the code I'm working on atm.
# -*- coding: utf-8 -*-
class mkdict(object):
""" A dictionary that allows multiple keys for one value """
class _Container(object):
""" This is used to wrap an object to avoid infinite
recursion when calling my own methods from the inside.
If a method sees this container, it assumes it has been
called from the inside and not the user.
"""
def __init__(self, _object):
self.object = _object
class dict(object):
""" Interface for mkdict.dict for dict-like behaviour """
def __init__(self, d={}, **kwargs):
""" Using an mkdict._Container to avoid infinite
recursion when allowing:
>>> d = mkdict({'what': 'ever'})
>>> d = mkdict.dict({'what': 'ever'})
"""
if isinstance(d, mkdict._Container):
self.mkdict = d.object
else:
self.mkdict = mkdict(mkdict._Container(self))
self.update(d, **kwargs)
def __str__(self):
return str(self.mkdict._dict)
def __repr__(self):
return str(self)
def __len__(self):
return len(self.mkdict._dict)
def __setitem__(self, key, value):
""" Desired behaviour:
>>> d = mkdict()
>>>
>>> d['what', 'ever'] = 'testing'
>>> d
{'what': 'testing', 'ever': 'testing'}
>>> d.dict
{('what', 'ever'): 'testing'}
>>> d['what'] is d['ever']
True
>>>
>>> d.dict['what'] = 'new value'
>>> d
{'what': 'new value', 'ever': 'testing'}
>>> d.dict
{'what': 'new value', 'ever': 'testing'}
>>> d['what'] is d['ever']
False
"""
if key not in self and key in self.mkdict:
self.mkdict._key_already_set(key)
self.mkdict[key] = value
def __getitem__(self, key):
return self.mkdict._dict[key]
def __contains__(self, key):
return key in self.mkdict._dict
def __delitem__(self, key):
if key not in self:
raise KeyError(key)
if isinstance(key, tuple):
key = key[0]
del self.mkdict[key]
def clear(self):
self.mkdict.clear()
def update(self, d, **kwargs):
if isinstance(d, mkdict.dict):
d = d.mkdict._dict
elif isinstance(d, mkdict):
d = d._dict
d.update(kwargs):
for k, v in d.items():
self[k] = v
class _FullKeyPtr(object):
""" Desired behaviour:
full_key_ptr1 = _FullKeyPtr()
mkdict._key_map -> {'key1', full_key_ptr1,
'key2', full_key_ptr1}
>>> d = mkdict()
>>> d['what', 'ever'] = 'testing'
>>> d._key_map
>>>
>>> # d._key_map:
>>> # {'what': full_key_ptr1, 'ever': full_key_ptr1}
>>> d._key_map
>>> {'what': ('what', 'ever'), 'ever': ('what', 'ever')}
>>>
>>> d['what']
>>> 'testing'
>>>
>>> # full_key = _key_map['ever'].full_key
>>> # i.e. full_key = ('what', 'ever')
>>> # _dict[full_key] = 'test'
>>> d['ever'] = 'test'
>>>
>>>
>>> d['what']
>>> 'test'
"""
def __init__(self, full_key):
self.full_key = full_key
def __str__(self):
return str(self.full_key)
def __repr__(self):
return str(self)
def __init__(self, d={}, **kwargs):
self._dict = dict()
self._key_map = dict()
self._dict_backup = None
self._key_map_backup = None
if isinstance(d, mkdict._Container):
self.dict = d.object
else:
self.dict = mkdict.dict(mkdict._Container(self))
self.update(d, **kwargs)
def __str__(self):
return str(dict(self.items()))
def __repr__(self):
return str(self)
def __len__(self):
return len(self._key_map)
def __iter__(self):
return iter(self.keys())
def __getitem__(self, key):
full_key = self.full_key(key)
return self.dict[full_key]
def __setitem__(self, key, value):
""" Desired behaviour:
>>> d = mkdict()
>>> d['what', 'ever'] = 'testing'
>>>
>>> d
{'what': 'testing', 'ever': 'testing'}
>>>
>>> d.dict
{('what', 'ever'): 'testing'}
>>> d['what'] is d['ever']
True
>>>
>>> d['what'] = 'new value'
>>> d
{'what': 'new value', 'ever': 'new value'}
>>>
>>> d.dict
{('what', 'ever'): 'new value'}
>>> d['what'] is d['ever']
True
"""
if key in self:
key = self.full_key(key)
if key not in self._dict:
if isinstance(key, tuple):
full_key_ptr = self._FullKeyPtr(key)
for k in key:
if k in self:
self._key_already_set(k)
self._key_map[k] = full_key_ptr
else:
self._key_map[key] = self._FullKeyPtr(key)
self._dict[key] = value
def __delitem__(self, key):
full_key = self.full_key(key)
if isinstance(full_key, tuple):
for k in full_key:
del self._key_map[k]
else:
del self._key_map[full_key]
del self._dict[full_key]
def __contains__(self, key):
return key in self._key_map
def items(self):
return [(k, self[k]) for k, v in self._key_map.items()]
def iteritems(self):
return iter(self.items())
def update(self, d={}, **kwargs):
if isinstance(d, mkdict.dict):
d = d.mkdict._dict
elif isinstance(d, mkdict):
d = d._dict
d.update(kwargs)
for k, v in d.items():
self[k] = v
def clear(self):
self._dict.clear()
self._key_map.clear()
def keys(self):
return self._key_map.keys()
def full_key(self, key):
return self._key_map[key].full_key
def has_key(self, key):
return key in self
def append(self, key, otherkey):
pass
def remove(self, key):
full_key = self.full_key(key)
if not isinstance(full_key, tuple):
del self._dict[full_key]
del self._key_map[full_key]
return
new_full_key = list(full_key)
new_full_key.remove(key)
if len(new_full_key) == 1:
new_full_key = new_full_key[0]
else:
new_full_key = tuple(new_full_key)
self._dict[new_full_key] = self.dict[full_key]
del self._dict[full_key]
self._key_map[key].full_key = new_full_key
del self._key_map[key]
def aliases(self, key):
full_key = self.full_key(key)
if isinstance(full_key, tuple):
aliases = list(full_key)
aliases.remove(key)
return aliases
return list()
def backup(self):
pass
def revert(self):
pass
def _key_already_set(self, key):
self.remove(key)
Desired behaviour for above code:
>>> d = mkdict()
>>>
>>> d['-p', '--port'] = 1234
>>> d
{'-p': 1234, '--port': 1234}
>>> d.dict
{('-p', '--port'): 1234}
>>>
>>> d['-p'] = 5678
>>> d
{'-p': 5678, '--port': 5678}
>>> d['--port'] is d['-p']
True
>>> d.aliases('-p')
['--port']
>>>
>>> d.dict['-p'] = 1234
>>> d
{'-p': 1234, '--port': 5678}
>>> d.dict
{'-p': 1234, '--port': 5678}
>>>
>>> d['-p'] is d['--port']
False
I've come up with an alternative solution, which is not so elegant (it's a hack), but should work as you've said. It overrides getitem and setitem, and for tuple like key representation, the minimal_get function is added.
from operator import itemgetter
from itertools import groupby
import logging
class mydict(dict):
def __init__(self, **kwargs):
super(mydict, self).__init__(**kwargs)
# self.d = {}
def __getitem__(self, item):
if isinstance(item, tuple):
d = {}
for an_item in item:
d[an_item] = self.__getitem__(an_item)
return d
else:
return super(mydict, self).__getitem__(item)
def __setitem__(self, keys, value, _depth=0):
if isinstance(keys, tuple) and _depth == 0:
if isinstance(value, tuple):
if len(keys) == len(value):
pass
else:
value = len(keys) * (value,)
else:
value = len(keys) * (value,)
for an_item in zip(keys, value):
self.__setitem__(an_item[0], an_item[1], _depth=1)
else:
super(mydict, self).__setitem__(keys, value)
def minimal_get(self):
x = {}
for item in groupby(sorted(self.items(), key=itemgetter(1)), key=itemgetter(1)):
keys = []
try:
while item[1]:
keys.append(item[1].next()[0])
except StopIteration:
logging.info("StopIteration")
x[tuple(keys)] = item[0]
return x
dic = mydict()
dic["one"] = 1
dic["seven"] = 2
print "dic is", dic
dic["two", "three", "four"] = [1, 2, 3]
print "dic.minimal_get() is ", dic.minimal_get()
print "dic is", dic
dic["two", "ten"] = "lol"
print "dic[\"one\", \"two\"] is ", dic["one", "two"]
print "dic[\"three\"] is", dic["three"]
print "dic.minimal_get() is ", dic.minimal_get()
dic[("x", "z",), "h"] = (1, 2, 3)
print "dic is", dic
print "dic.minimal_get() is ", dic.minimal_get()
Result is,

python dictmixin object with property decorator

I was instructed to use more pythonish way of setter and getters #property. So we have something like this:
from UserDict import DictMixin
class A(dict):
def __init__(self, a, b):
self.a = a
self.b = b
#property
def a(self):
return self._a
#a.setter
def a(self, value):
self._a = value
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
setattr(self, key, value)
def keys(self):
return [k for k in self.__dict__.keys() if not k.startswith('_')]
def do_whatever(self):
pass
a = A(1,2)
print a.keys()
output is ['b'] and at first I wasn't expecting that, but it actually makes sense.
Question is how to get all properties names but not names of methods. Any ideas?
Properties are implemented as descriptors, and so belong to the class, not the instance:
>>> class Question(object):
... #property
... def answer(self):
... return 7 * 6
...
>>> q = Question()
>>> q.answer
42
>>> q.__dict__
{}
>>> for key, value in Question.__dict__.iteritems():
... if isinstance(value, property):
... print key
...
answer

Pythonic alias for instance variable?

I have a class in which I store data in a list for inheritance reasons. I would like to know, and I have done my share of googling, is there a cleaner way other than creating getter/setter functions and properties to give a alias to the element in this list?
For example...
class Serializable(object):
"""Adds serialization to from binary string"""
def encode(self):
"""Pack into struct"""
return self.encoder.pack(*self)
def decode(self, data_str):
"""Unpack from struct"""
self.data = self.encoder.unpack(data_str)
return self.data
class Ping(Serializable):
encoder = Struct("!16sBBBL")
def __init__(self, ident=create_id(), ttl=TTL, hops=0, length=0):
self.data = [ident, 1, ttl, hops, length]
self.ident = property(self.data[0])
def __getitem__(self, index):
return self.data[index]
#property
def ident(self):
return self.data[0]
#ident.setter
def ident(self, value):
self.data[0] = value
#property
def protocol(self):
return self.data[1]
#protocol.setter
def protocol(self, protocol):
self.data[1]
I would prefer a more compact solution to reference object.ident while maintaining the ability to pack and unpack as above.
If you store your values/properties in a dictionary instead:
def __init__(self, ident=create_id(), ttl=TTL, hops=0, length=0):
self.data = {
'ident': ident,
'protocol': 1,
'ttl': hops,
'length': length,
}
And then override __getattr__ and __setattr__:
def __getattr__(self, attr):
return self.data[attr]
def __setattr__(self, attr, value):
if attr == 'data':
object.__setattr__(self, attr, value)
else:
self.data[attr] = value
Now you can do this:
>>> ping = Ping()
>>> ping.protocol
1
>>> ping.protocol = 2
>>> ping.protocol
2
If self.data absolutely has to be a list, you can do this instead:
class Ping(Serializable):
mapping = ('ident', 'protocol', 'ttl', 'hops', 'length')
encoder = Struct("!16sBBBL")
def __init__(self, ident=create_id(), ttl=TTL, hops=0, length=0):
self.data = [ident, 1, ttl, hops, length]
def __getitem__(self, index):
return self.data[index]
def __getattr__(self, attr):
index = self.mapping.index(attr)
return self.data[index]
def __setattr__(self, attr, value):
if attr == 'data':
object.__setattr__(self, attr, value)
else:
index = self.mapping.index(attr)
self.data[index] = value
def alias_property(key):
return property(
lambda self: getattr(self, key),
lambda self, val: setattr(self, key, val),
lambda self: delattr(self, key))
class A(object):
def __init__(self, prop):
self.prop = prop
prop_alias = alias_property('prop')
If your problem is just shorten the code to access ident, you may just use "property" in the "old style" - that is, you pass to it, as parameters, the getter and setter functions, instead of using it as a decorator.
In this case, the functions are so small, they can be lambda functions, without affecting code readbility.
class Ping(Serializable):
encoder = Struct("!16sBBBL")
def __init__(self, ident=None, ttl=TTL, hops=0, length=0):
if ident is None:
ident = create_id()
self.data = [ident, 1, ttl, hops, length]
# The line bellow looks like garbage -
# it does not even make sense as a call to `property`
# should have a callable as first parameter
# returns an object that is designed to work as a class attribute
# self.ident = property(self.data[0])
# rather:
self.ident = ident
# this will use the property defined bellow
def __getitem__(self, index):
return self.data[index]
ident = property(lambda s: s.data[0], lambda s, v: s.data[0].__setitem__(0, v)
protocol = property(lambda s: s.data[1], lambda s, v: s.data[1].__setitem__(1, v)

Categories