Een klasse heeft een constructor die één parameter nodig heeft:
class C(object):
def __init__(self, v):
self.v = v
...
Ergens in de code is het handig voor waarden in een dict om hun sleutels te kennen.
Ik wil een standaarddictaat gebruiken waarbij de sleutel wordt doorgegeven aan de standaardwaarden van pasgeborenen:
d = defaultdict(lambda : C(here_i_wish_the_key_to_be))
Heeft u suggesties?
Antwoord 1, autoriteit 100%
Het kwalificeert nauwelijks als slim– maar subclassificatie is je vriend:
class keydefaultdict(defaultdict):
def __missing__(self, key):
if self.default_factory is None:
raise KeyError( key )
else:
ret = self[key] = self.default_factory(key)
return ret
d = keydefaultdict(C)
d[x] # returns C(x)
Antwoord 2, autoriteit 22%
Nee, dat is er niet.
De defaultdict
-implementatie kan niet worden geconfigureerd om de ontbrekende key
direct door te geven aan de default_factory
. Je enige optie is om je eigen defaultdict
subklasse te implementeren, zoals voorgesteld door @JochenRitzel hierboven.
Maar dat is niet “slim” of bijna zo schoon als een standaard bibliotheekoplossing zou zijn (als die zou bestaan). Het antwoord op uw beknopte ja/nee-vraag is dus duidelijk “Nee”.
Het is jammer dat de standaardbibliotheek zo’n vaak nodig hulpmiddel mist.
Antwoord 3, autoriteit 5%
Ik denk niet dat je defaultdict
hier nodig hebt. Waarom gebruik je niet gewoon de dict.setdefault
-methode?
>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'
Dat zou natuurlijk veel instanties van C
opleveren. In het geval dat het een probleem is, denk ik dat de eenvoudigere aanpak zal volstaan:
>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')
Het zou sneller zijn dan het defaultdict
of een ander alternatief voor zover ik kan zien.
ETAmet betrekking tot de snelheid van de in
-test versus het gebruik van de try-except-clausule:
>>> def g():
d = {}
if 'a' in d:
return d['a']
>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
d = {}
try:
return d['a']
except KeyError:
return
>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
d = {'a': 2}
if 'a' in d:
return d['a']
>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
d = {'a': 2}
try:
return d['a']
except KeyError:
return
>>> timeit.timeit(p)
0.28588609450770264
Antwoord 4
Hier is een werkend voorbeeld van een woordenboek dat automatisch een waarde toevoegt. De demonstratietaak bij het vinden van dubbele bestanden in /usr/include. Opmerking: voor het aanpassen van woordenboek PathDictzijn slechts vier regels nodig:
class FullPaths:
def __init__(self,filename):
self.filename = filename
self.paths = set()
def record_path(self,path):
self.paths.add(path)
class PathDict(dict):
def __missing__(self, key):
ret = self[key] = FullPaths(key)
return ret
if __name__ == "__main__":
pathdict = PathDict()
for root, _, files in os.walk('/usr/include'):
for f in files:
path = os.path.join(root,f)
pathdict[f].record_path(path)
for fullpath in pathdict.values():
if len(fullpath.paths) > 1:
print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
Antwoord 5
Een andere manier waarop u mogelijk de gewenste functionaliteit kunt bereiken, is door decorateurs te gebruiken
def initializer(cls: type):
def argument_wrapper(
*args: Tuple[Any], **kwargs: Dict[str, Any]
) -> Callable[[], 'X']:
def wrapper():
return cls(*args, **kwargs)
return wrapper
return argument_wrapper
@initializer
class X:
def __init__(self, *, some_key: int, foo: int = 10, bar: int = 20) -> None:
self._some_key = some_key
self._foo = foo
self._bar = bar
@property
def key(self) -> int:
return self._some_key
@property
def foo(self) -> int:
return self._foo
@property
def bar(self) -> int:
return self._bar
def __str__(self) -> str:
return f'[Key: {self.key}, Foo: {self.foo}, Bar: {self.bar}]'
Vervolgens kun je een defaultdict
maken als volgt:
>>> d = defaultdict(X(some_key=10, foo=15, bar=20))
>>> d['baz']
[Key: 10, Foo: 15, Bar: 20]
>>> d['qux']
[Key: 10, Foo: 15, Bar: 20]
De default_factory
maakt nieuwe exemplaren van X
met de opgegeven
argumenten.
Natuurlijk zou dit alleen nuttig zijn als je weetdat de klasse zal worden gebruikt in een default_factory
. Anders zou u, om een individuele klasse te instantiëren, iets moeten doen als:
x = X(some_key=10, foo=15)()
Wat een beetje lelijk is… Als u dit echter wilt vermijden en een zekere mate van complexiteit wilt introduceren, kunt u ook een trefwoordparameter zoals factory
toevoegen aan de argument_wrapper
die generiek gedrag mogelijk maakt:
def initializer(cls: type):
def argument_wrapper(
*args: Tuple[Any], factory: bool = False, **kwargs: Dict[str, Any]
) -> Callable[[], 'X']:
def wrapper():
return cls(*args, **kwargs)
if factory:
return wrapper
return cls(*args, **kwargs)
return argument_wrapper
Waar je de klas dan als volgt zou kunnen gebruiken:
>>> X(some_key=10, foo=15)
[Key: 10, Foo: 15, Bar: 20]
>>> d = defaultdict(X(some_key=15, foo=15, bar=25, factory=True))
>>> d['baz']
[Key: 15, Foo: 15, Bar: 25]