swizzle 2.3.0__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {swizzle-2.3.0/swizzle.egg-info → swizzle-2.3.1}/PKG-INFO +1 -1
- {swizzle-2.3.0 → swizzle-2.3.1}/swizzle/__init__.py +228 -93
- swizzle-2.3.1/swizzle/trie.py +61 -0
- {swizzle-2.3.0 → swizzle-2.3.1/swizzle.egg-info}/PKG-INFO +1 -1
- {swizzle-2.3.0 → swizzle-2.3.1}/swizzle.egg-info/SOURCES.txt +1 -0
- {swizzle-2.3.0 → swizzle-2.3.1}/LICENSE +0 -0
- {swizzle-2.3.0 → swizzle-2.3.1}/README.md +0 -0
- {swizzle-2.3.0 → swizzle-2.3.1}/setup.cfg +0 -0
- {swizzle-2.3.0 → swizzle-2.3.1}/setup.py +0 -0
- {swizzle-2.3.0 → swizzle-2.3.1}/swizzle.egg-info/dependency_links.txt +0 -0
- {swizzle-2.3.0 → swizzle-2.3.1}/swizzle.egg-info/top_level.txt +0 -0
@@ -1,12 +1,16 @@
|
|
1
1
|
# Copyright (c) 2024 Jan T. Müller <mail@jantmueller.com>
|
2
2
|
|
3
|
-
from functools import wraps
|
4
|
-
import types
|
5
3
|
import builtins
|
6
4
|
import sys as _sys
|
7
|
-
|
5
|
+
import types
|
6
|
+
import unicodedata
|
7
|
+
from enum import EnumType
|
8
|
+
from functools import wraps
|
8
9
|
from keyword import iskeyword as _iskeyword
|
9
10
|
from operator import itemgetter as _itemgetter
|
11
|
+
|
12
|
+
from .trie import Trie
|
13
|
+
|
10
14
|
try:
|
11
15
|
from _collections import _tuplegetter
|
12
16
|
except ImportError:
|
@@ -14,11 +18,21 @@ except ImportError:
|
|
14
18
|
|
15
19
|
_type = builtins.type
|
16
20
|
|
17
|
-
__version__ = "2.3.
|
21
|
+
__version__ = "2.3.1"
|
18
22
|
|
19
23
|
MISSING = object()
|
20
24
|
|
21
|
-
|
25
|
+
|
26
|
+
def swizzledtuple(
|
27
|
+
typename,
|
28
|
+
field_names,
|
29
|
+
*,
|
30
|
+
rename=False,
|
31
|
+
defaults=None,
|
32
|
+
module=None,
|
33
|
+
arrange_names=None,
|
34
|
+
sep=None,
|
35
|
+
):
|
22
36
|
"""
|
23
37
|
Create a custom named tuple class with swizzled attributes, allowing for rearranged field names
|
24
38
|
and customized attribute access.
|
@@ -41,9 +55,13 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
41
55
|
in which fields should be arranged in the resulting named tuple. This allows for fields
|
42
56
|
to be rearranged and, unlike standard `namedtuple`, can include duplicates. Defaults
|
43
57
|
to the order given in `field_names`.
|
44
|
-
sep (str, optional): A separator string
|
45
|
-
|
46
|
-
|
58
|
+
sep (str, optional): A separator string used to control how attribute names are constructed.
|
59
|
+
If provided, fields will be joined using this separator to create compound attribute names.
|
60
|
+
Defaults to None.
|
61
|
+
|
62
|
+
Special case: If all field names have the same length `n` after optional renaming,
|
63
|
+
and `sep` is still None, then `sep` is automatically set to `"+n"` (e.g. "+2").
|
64
|
+
This indicates that names should be split every `n` characters for improved performance.
|
47
65
|
|
48
66
|
Returns:
|
49
67
|
type: A new subclass of `tuple` with named fields and customized attribute access.
|
@@ -69,29 +87,41 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
69
87
|
"""
|
70
88
|
|
71
89
|
if isinstance(field_names, str):
|
72
|
-
field_names = field_names.replace(
|
90
|
+
field_names = field_names.replace(",", " ").split()
|
73
91
|
field_names = list(map(str, field_names))
|
74
92
|
if arrange_names is not None:
|
75
93
|
if isinstance(arrange_names, str):
|
76
|
-
arrange_names = arrange_names.replace(
|
94
|
+
arrange_names = arrange_names.replace(",", " ").split()
|
77
95
|
arrange_names = list(map(str, arrange_names))
|
78
|
-
assert set(arrange_names) == set(
|
96
|
+
assert set(arrange_names) == set(
|
97
|
+
field_names
|
98
|
+
), "Arrangement must contain all field names"
|
79
99
|
else:
|
80
100
|
arrange_names = field_names.copy()
|
81
101
|
|
82
|
-
|
83
102
|
typename = _sys.intern(str(typename))
|
84
103
|
|
85
|
-
_dir = dir(tuple) + [
|
104
|
+
_dir = dir(tuple) + [
|
105
|
+
"__match_args__",
|
106
|
+
"__module__",
|
107
|
+
"__slots__",
|
108
|
+
"_asdict",
|
109
|
+
"_field_defaults",
|
110
|
+
"_fields",
|
111
|
+
"_make",
|
112
|
+
"_replace",
|
113
|
+
]
|
86
114
|
if rename:
|
87
115
|
seen = set()
|
88
116
|
name_newname = {}
|
89
117
|
for index, name in enumerate(field_names):
|
90
|
-
if (
|
118
|
+
if (
|
119
|
+
not name.isidentifier()
|
91
120
|
or _iskeyword(name)
|
92
121
|
or name in _dir
|
93
|
-
or name in seen
|
94
|
-
|
122
|
+
or name in seen
|
123
|
+
):
|
124
|
+
field_names[index] = f"_{index}"
|
95
125
|
name_newname[name] = field_names[index]
|
96
126
|
seen.add(name)
|
97
127
|
for index, name in enumerate(arrange_names):
|
@@ -99,20 +129,24 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
99
129
|
|
100
130
|
for name in [typename] + field_names:
|
101
131
|
if type(name) is not str:
|
102
|
-
raise TypeError(
|
132
|
+
raise TypeError("Type names and field names must be strings")
|
103
133
|
if not name.isidentifier():
|
104
|
-
raise ValueError(
|
105
|
-
|
134
|
+
raise ValueError(
|
135
|
+
"Type names and field names must be valid " f"identifiers: {name!r}"
|
136
|
+
)
|
106
137
|
if _iskeyword(name):
|
107
|
-
raise ValueError(
|
108
|
-
|
138
|
+
raise ValueError(
|
139
|
+
"Type names and field names cannot be a " f"keyword: {name!r}"
|
140
|
+
)
|
109
141
|
seen = set()
|
110
142
|
for name in field_names:
|
111
|
-
if name in _dir
|
112
|
-
raise ValueError(
|
113
|
-
|
143
|
+
if name in _dir:
|
144
|
+
raise ValueError(
|
145
|
+
"Field names cannot be an attribute name which would shadow the namedtuple methods or attributes"
|
146
|
+
f"{name!r}"
|
147
|
+
)
|
114
148
|
if name in seen:
|
115
|
-
raise ValueError(f
|
149
|
+
raise ValueError(f"Encountered duplicate field name: {name!r}")
|
116
150
|
seen.add(name)
|
117
151
|
|
118
152
|
arrange_indices = [field_names.index(name) for name in arrange_names]
|
@@ -128,29 +162,30 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
128
162
|
if defaults is not None:
|
129
163
|
defaults = tuple(defaults)
|
130
164
|
if len(defaults) > len(field_names):
|
131
|
-
raise TypeError(
|
132
|
-
field_defaults = dict(
|
133
|
-
|
165
|
+
raise TypeError("Got more default values than field names")
|
166
|
+
field_defaults = dict(
|
167
|
+
reversed(list(zip(reversed(field_names), reversed(defaults))))
|
168
|
+
)
|
134
169
|
|
135
170
|
field_names = tuple(map(_sys.intern, field_names))
|
136
171
|
arrange_names = tuple(map(_sys.intern, arrange_names))
|
137
172
|
num_fields = len(field_names)
|
138
173
|
num_arrange_fields = len(arrange_names)
|
139
|
-
arg_list =
|
174
|
+
arg_list = ", ".join(field_names)
|
140
175
|
if num_fields == 1:
|
141
|
-
arg_list +=
|
142
|
-
repr_fmt =
|
176
|
+
arg_list += ","
|
177
|
+
repr_fmt = "(" + ", ".join(f"{name}=%r" for name in arrange_names) + ")"
|
143
178
|
_dict, _tuple, _len, _map, _zip = dict, tuple, len, map, zip
|
144
179
|
|
145
180
|
namespace = {
|
146
|
-
|
147
|
-
|
148
|
-
|
181
|
+
"_tuple_new": tuple_new,
|
182
|
+
"__builtins__": {},
|
183
|
+
"__name__": f"swizzledtuple_{typename}",
|
149
184
|
}
|
150
|
-
code = f
|
185
|
+
code = f"lambda _cls, {arg_list}: _tuple_new(_cls, ({arg_list}))"
|
151
186
|
__new__ = eval(code, namespace)
|
152
|
-
__new__.__name__ =
|
153
|
-
__new__.__doc__ = f
|
187
|
+
__new__.__name__ = "__new__"
|
188
|
+
__new__.__doc__ = f"Create new instance of {typename}({arg_list})"
|
154
189
|
if defaults is not None:
|
155
190
|
__new__.__defaults__ = defaults
|
156
191
|
|
@@ -158,11 +193,14 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
158
193
|
def _make(cls, iterable):
|
159
194
|
result = tuple_new(cls, iterable)
|
160
195
|
if _len(result) != num_arrange_fields:
|
161
|
-
raise TypeError(
|
196
|
+
raise TypeError(
|
197
|
+
f"Expected {num_arrange_fields} arguments, got {len(result)}"
|
198
|
+
)
|
162
199
|
return result
|
163
200
|
|
164
|
-
_make.__func__.__doc__ = (
|
165
|
-
|
201
|
+
_make.__func__.__doc__ = (
|
202
|
+
f"Make a new {typename} object from a sequence " "or iterable"
|
203
|
+
)
|
166
204
|
|
167
205
|
def _replace(self, /, **kwds):
|
168
206
|
def generator():
|
@@ -174,33 +212,29 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
174
212
|
|
175
213
|
result = self._make(iter(generator()))
|
176
214
|
if kwds:
|
177
|
-
raise ValueError(f
|
215
|
+
raise ValueError(f"Got unexpected field names: {list(kwds)!r}")
|
178
216
|
return result
|
179
217
|
|
180
|
-
_replace.__doc__ = (
|
181
|
-
|
218
|
+
_replace.__doc__ = (
|
219
|
+
f"Return a new {typename} object replacing specified " "fields with new values"
|
220
|
+
)
|
182
221
|
|
183
222
|
def __repr__(self):
|
184
|
-
|
223
|
+
"Return a nicely formatted representation string"
|
185
224
|
return self.__class__.__name__ + repr_fmt % self
|
186
225
|
|
187
226
|
def _asdict(self):
|
188
|
-
|
227
|
+
"Return a new dict which maps field names to their values."
|
189
228
|
return _dict(_zip(arrange_names, self))
|
190
229
|
|
191
230
|
def __getnewargs__(self):
|
192
|
-
|
231
|
+
"Return self as a plain tuple. Used by copy and pickle."
|
193
232
|
return _tuple(self)
|
194
233
|
|
195
|
-
@swizzle_attributes_retriever(sep=sep, type =
|
234
|
+
@swizzle_attributes_retriever(sep=sep, type=swizzledtuple, only_attrs=field_names)
|
196
235
|
def __getattribute__(self, attr_name):
|
197
236
|
return super(_tuple, self).__getattribute__(attr_name)
|
198
237
|
|
199
|
-
# def __getitem__(self, index):
|
200
|
-
# a_names = arrange_names[index]
|
201
|
-
# _sep = '' if sep is None else sep
|
202
|
-
# return getattr(self, _sep.join(a_names))
|
203
|
-
|
204
238
|
def __getitem__(self, index):
|
205
239
|
if not isinstance(index, slice):
|
206
240
|
return _tuple.__getitem__(self, index)
|
@@ -227,11 +261,9 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
227
261
|
defaults=filtered_values,
|
228
262
|
module=module,
|
229
263
|
arrange_names=arrange_names[index],
|
230
|
-
sep=sep
|
264
|
+
sep=sep,
|
231
265
|
)()
|
232
266
|
|
233
|
-
|
234
|
-
|
235
267
|
for method in (
|
236
268
|
__new__,
|
237
269
|
_make.__func__,
|
@@ -240,29 +272,29 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
240
272
|
_asdict,
|
241
273
|
__getnewargs__,
|
242
274
|
__getattribute__,
|
243
|
-
__getitem__
|
275
|
+
__getitem__,
|
244
276
|
):
|
245
|
-
method.__qualname__ = f
|
277
|
+
method.__qualname__ = f"{typename}.{method.__name__}"
|
246
278
|
|
247
279
|
class_namespace = {
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
280
|
+
"__doc__": f"{typename}({arg_list})",
|
281
|
+
"__slots__": (),
|
282
|
+
"_fields": field_names,
|
283
|
+
"_field_defaults": field_defaults,
|
284
|
+
"__new__": __new__,
|
285
|
+
"_make": _make,
|
286
|
+
"_replace": _replace,
|
287
|
+
"__repr__": __repr__,
|
288
|
+
"_asdict": _asdict,
|
289
|
+
"__getnewargs__": __getnewargs__,
|
290
|
+
"__getattribute__": __getattribute__,
|
291
|
+
"__getitem__": __getitem__,
|
260
292
|
}
|
261
293
|
seen = set()
|
262
294
|
for index, name in enumerate(arrange_names):
|
263
295
|
if name in seen:
|
264
296
|
continue
|
265
|
-
doc = _sys.intern(f
|
297
|
+
doc = _sys.intern(f"Alias for field number {index}")
|
266
298
|
class_namespace[name] = _tuplegetter(index, doc)
|
267
299
|
seen.add(name)
|
268
300
|
|
@@ -270,10 +302,10 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
270
302
|
|
271
303
|
if module is None:
|
272
304
|
try:
|
273
|
-
module = _sys._getframemodulename(1) or
|
305
|
+
module = _sys._getframemodulename(1) or "__main__"
|
274
306
|
except AttributeError:
|
275
307
|
try:
|
276
|
-
module = _sys._getframe(1).f_globals.get(
|
308
|
+
module = _sys._getframe(1).f_globals.get("__name__", "__main__")
|
277
309
|
except (AttributeError, ValueError):
|
278
310
|
pass
|
279
311
|
if module is not None:
|
@@ -281,27 +313,69 @@ def swizzledtuple(typename, field_names, *, rename=False, defaults=None, module=
|
|
281
313
|
|
282
314
|
return result
|
283
315
|
|
316
|
+
|
284
317
|
# Helper function to split a string based on a sep
|
285
318
|
def split_string(string, sep):
|
286
|
-
if sep ==
|
287
|
-
|
319
|
+
if sep[0] == "+":
|
320
|
+
n = int(sep)
|
321
|
+
return [string[i : i + n] for i in range(0, len(string), n)]
|
288
322
|
else:
|
289
323
|
return string.split(sep)
|
290
324
|
|
325
|
+
|
291
326
|
# Helper function to collect attribute retrieval functions from a class or meta-class
|
292
327
|
def collect_attribute_functions(cls):
|
293
328
|
funcs = []
|
294
|
-
if hasattr(cls,
|
329
|
+
if hasattr(cls, "__getattribute__"):
|
295
330
|
funcs.append(cls.__getattribute__)
|
296
|
-
if hasattr(cls,
|
331
|
+
if hasattr(cls, "__getattr__"):
|
297
332
|
funcs.append(cls.__getattr__)
|
298
333
|
if not funcs:
|
299
|
-
raise AttributeError(
|
334
|
+
raise AttributeError(
|
335
|
+
"No __getattr__ or __getattribute__ found on the class or meta-class"
|
336
|
+
)
|
300
337
|
return funcs
|
301
338
|
|
339
|
+
|
302
340
|
# Function to combine multiple attribute retrieval functions
|
303
341
|
|
304
|
-
|
342
|
+
|
343
|
+
def is_valid_sep(s):
|
344
|
+
# if not s:
|
345
|
+
# return False
|
346
|
+
if s[0] == "+" and s[1:].isdigit():
|
347
|
+
return True
|
348
|
+
for ch in s:
|
349
|
+
if ch == "_":
|
350
|
+
continue
|
351
|
+
cat = unicodedata.category(ch)
|
352
|
+
if not (cat.startswith("L") or cat == "Nd"):
|
353
|
+
return False
|
354
|
+
return True
|
355
|
+
|
356
|
+
|
357
|
+
def swizzle_attributes_retriever(
|
358
|
+
attribute_funcs=None, sep=None, type=swizzledtuple, only_attrs=None
|
359
|
+
):
|
360
|
+
trie = None
|
361
|
+
|
362
|
+
if sep == "":
|
363
|
+
sep = "+1" # for backwards compatibility, remove on next version
|
364
|
+
|
365
|
+
if sep is None and only_attrs:
|
366
|
+
only_attrs_length = set(len(fname) for fname in only_attrs)
|
367
|
+
if len(only_attrs_length) == 1:
|
368
|
+
sep = f"+{next(iter(only_attrs_length))}"
|
369
|
+
else:
|
370
|
+
trie = Trie(only_attrs)
|
371
|
+
|
372
|
+
if sep is not None and not is_valid_sep(sep):
|
373
|
+
raise ValueError(
|
374
|
+
f"Invalid value for sep: {sep!r}. Must be either:"
|
375
|
+
" (1) a non-empty string containing only letters, digits, or underscores, "
|
376
|
+
"or (2) a pattern of the form '+N' where N is a positive integer."
|
377
|
+
)
|
378
|
+
|
305
379
|
def _swizzle_attributes_retriever(attribute_funcs):
|
306
380
|
if not isinstance(attribute_funcs, list):
|
307
381
|
attribute_funcs = [attribute_funcs]
|
@@ -328,11 +402,25 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
328
402
|
attr_parts = split_string(attr_name, sep)
|
329
403
|
arranged_names = attr_parts
|
330
404
|
for part in attr_parts:
|
405
|
+
if only_attrs and part not in only_attrs:
|
406
|
+
raise AttributeError(
|
407
|
+
f"Attribute {part} is not part of an allowed field for swizzling"
|
408
|
+
)
|
331
409
|
attribute = retrieve_attribute(obj, part)
|
332
410
|
if attribute is not MISSING:
|
333
411
|
matched_attributes.append(attribute)
|
334
412
|
else:
|
335
|
-
raise AttributeError(f"No matching attribute found for
|
413
|
+
raise AttributeError(f"No matching attribute found for {part}")
|
414
|
+
elif only_attrs:
|
415
|
+
arranged_names = trie.split_longest_prefix(attr_name)
|
416
|
+
if arranged_names is None:
|
417
|
+
raise AttributeError(f"No matching attribute found for {attr_name}")
|
418
|
+
for name in arranged_names:
|
419
|
+
attribute = retrieve_attribute(obj, name)
|
420
|
+
if attribute is not MISSING:
|
421
|
+
matched_attributes.append(attribute)
|
422
|
+
else:
|
423
|
+
raise AttributeError(f"No matching attribute found for {name}")
|
336
424
|
else:
|
337
425
|
# No sep provided, attempt to match substrings
|
338
426
|
i = 0
|
@@ -348,7 +436,9 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
348
436
|
match_found = True
|
349
437
|
break
|
350
438
|
if not match_found:
|
351
|
-
raise AttributeError(
|
439
|
+
raise AttributeError(
|
440
|
+
f"No matching attribute found for substring: {attr_name[i:]}"
|
441
|
+
)
|
352
442
|
|
353
443
|
if type == swizzledtuple:
|
354
444
|
field_names = set(arranged_names)
|
@@ -359,11 +449,15 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
359
449
|
elif hasattr(obj, "__class__"):
|
360
450
|
if hasattr(obj.__class__, "__name__"):
|
361
451
|
name = obj.__class__.__name__
|
362
|
-
result = type(
|
452
|
+
result = type(
|
453
|
+
name,
|
454
|
+
field_names,
|
455
|
+
arrange_names=arranged_names,
|
456
|
+
sep=sep,
|
457
|
+
)
|
363
458
|
result = result(*field_values)
|
364
459
|
return result
|
365
460
|
|
366
|
-
|
367
461
|
return type(matched_attributes)
|
368
462
|
|
369
463
|
return retrieve_swizzled_attributes
|
@@ -373,28 +467,66 @@ def swizzle_attributes_retriever(attribute_funcs=None, sep=None, type = swizzled
|
|
373
467
|
else:
|
374
468
|
return _swizzle_attributes_retriever
|
375
469
|
|
376
|
-
|
377
|
-
def swizzle(cls=None, meta=False, sep=None, type =
|
470
|
+
|
471
|
+
def swizzle(cls=None, meta=False, sep=None, type=tuple, only_attrs=None):
|
472
|
+
|
473
|
+
def preserve_metadata(
|
474
|
+
target,
|
475
|
+
source,
|
476
|
+
keys=("__name__", "__qualname__", "__doc__", "__module__", "__annotations__"),
|
477
|
+
):
|
478
|
+
for key in keys:
|
479
|
+
if hasattr(source, key):
|
480
|
+
try:
|
481
|
+
setattr(target, key, getattr(source, key))
|
482
|
+
except (TypeError, AttributeError):
|
483
|
+
pass # some attributes may be read-only
|
378
484
|
|
379
485
|
def class_decorator(cls):
|
380
486
|
# Collect attribute retrieval functions from the class
|
381
487
|
attribute_funcs = collect_attribute_functions(cls)
|
382
488
|
|
383
489
|
# Apply the swizzling to the class's attribute retrieval
|
384
|
-
setattr(
|
490
|
+
setattr(
|
491
|
+
cls,
|
492
|
+
attribute_funcs[-1].__name__,
|
493
|
+
swizzle_attributes_retriever(attribute_funcs, sep, type, only_attrs),
|
494
|
+
)
|
385
495
|
|
386
496
|
# Handle meta-class swizzling if requested
|
387
497
|
if meta:
|
388
498
|
meta_cls = _type(cls)
|
389
|
-
|
390
|
-
|
499
|
+
|
500
|
+
class SwizzledMetaType(meta_cls):
|
501
|
+
pass
|
502
|
+
|
503
|
+
if meta_cls == EnumType:
|
504
|
+
|
505
|
+
def cfem_dummy(*args, **kwargs):
|
391
506
|
pass
|
392
|
-
meta_cls = SwizzledMetaType
|
393
|
-
cls = meta_cls(cls.__name__, cls.__bases__, dict(cls.__dict__))
|
394
507
|
|
395
|
-
|
396
|
-
|
508
|
+
cfem = SwizzledMetaType._check_for_existing_members_
|
509
|
+
SwizzledMetaType._check_for_existing_members_ = cfem_dummy
|
510
|
+
|
511
|
+
class SwizzledClass(cls, metaclass=SwizzledMetaType):
|
512
|
+
pass
|
513
|
+
|
514
|
+
if meta_cls == EnumType:
|
515
|
+
SwizzledMetaType._check_for_existing_members_ = cfem
|
397
516
|
|
517
|
+
# Preserve metadata on swizzled meta and class
|
518
|
+
preserve_metadata(SwizzledMetaType, meta_cls)
|
519
|
+
preserve_metadata(SwizzledClass, cls)
|
520
|
+
|
521
|
+
meta_cls = SwizzledMetaType
|
522
|
+
cls = SwizzledClass
|
523
|
+
|
524
|
+
meta_funcs = collect_attribute_functions(meta_cls)
|
525
|
+
setattr(
|
526
|
+
meta_cls,
|
527
|
+
meta_funcs[-1].__name__,
|
528
|
+
swizzle_attributes_retriever(meta_funcs, sep, type, only_attrs),
|
529
|
+
)
|
398
530
|
return cls
|
399
531
|
|
400
532
|
if cls is None:
|
@@ -408,7 +540,10 @@ class Swizzle(types.ModuleType):
|
|
408
540
|
types.ModuleType.__init__(self, __name__)
|
409
541
|
self.__dict__.update(_sys.modules[__name__].__dict__)
|
410
542
|
|
411
|
-
def __call__(
|
412
|
-
|
543
|
+
def __call__(
|
544
|
+
self, cls=None, meta=False, sep=None, type=swizzledtuple, only_attrs=None
|
545
|
+
):
|
546
|
+
return swizzle(cls, meta, sep, type, only_attrs)
|
547
|
+
|
413
548
|
|
414
549
|
_sys.modules[__name__] = Swizzle()
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class TrieNode:
|
2
|
+
def __init__(self):
|
3
|
+
self.children = {}
|
4
|
+
self.is_end = False
|
5
|
+
|
6
|
+
def add(self, word):
|
7
|
+
node = self
|
8
|
+
for char in word:
|
9
|
+
if char not in node.children:
|
10
|
+
node.children[char] = TrieNode()
|
11
|
+
node = node.children[char]
|
12
|
+
node.is_end = True
|
13
|
+
|
14
|
+
def step(self, char):
|
15
|
+
return self.children.get(char)
|
16
|
+
|
17
|
+
|
18
|
+
class Trie:
|
19
|
+
def __init__(self, words=None):
|
20
|
+
self.root = TrieNode()
|
21
|
+
if words:
|
22
|
+
for word in words:
|
23
|
+
self.add(word)
|
24
|
+
|
25
|
+
def add(self, word):
|
26
|
+
self.root.add(word)
|
27
|
+
|
28
|
+
def split_longest_prefix(self, query):
|
29
|
+
result = []
|
30
|
+
length = len(query)
|
31
|
+
i = 0
|
32
|
+
while i < length:
|
33
|
+
node = self.root
|
34
|
+
longest_end = -1
|
35
|
+
j = i
|
36
|
+
children = node.children # cache children dict
|
37
|
+
while j < length:
|
38
|
+
next_node = children.get(query[j])
|
39
|
+
if not next_node:
|
40
|
+
break
|
41
|
+
node = next_node
|
42
|
+
children = node.children # update cache
|
43
|
+
j += 1
|
44
|
+
if node.is_end:
|
45
|
+
longest_end = j
|
46
|
+
if longest_end == -1:
|
47
|
+
return None
|
48
|
+
result.append(query[i:longest_end])
|
49
|
+
i = longest_end
|
50
|
+
return result
|
51
|
+
|
52
|
+
|
53
|
+
if __name__ == "__main__":
|
54
|
+
words = ["mango", "man", "go", "bat", "manbat"]
|
55
|
+
trie = Trie(words)
|
56
|
+
|
57
|
+
print(trie.split_longest_prefix("mangomanmanbat")) # ['mango', 'man', 'bat']
|
58
|
+
print(trie.split_longest_prefix("manbat")) # ['manbat']
|
59
|
+
print(trie.split_longest_prefix("mango")) # ['mango']
|
60
|
+
print(trie.split_longest_prefix("mangoman")) # ['mango', 'man']
|
61
|
+
print(trie.split_longest_prefix("unknownword")) # None
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|