在任何人去磨砺他们的火炬或点燃干草叉之前,我不是什么是Pythonic的专家,也就是说,在我看来,如果first
和/或last
从列表中被想要,在循环中if first
或if last
循环内,似乎对课程的期望super
,并添加所需的功能......也许,所以接下来是完全预 alpha 版本的 e-1^11% 排序代码,如果在星期二以正确的方式查看可能会造成严重破坏...
import sys
## Prevent `.pyc` (Python byte code) files from being generated
sys.dont_write_bytecode = True
from collections import OrderedDict
class MetaList(list):
"""
Generates list of metadata dictionaries for list types
## Useful resources
- [C Source for list](https://github.com/python/cpython/blob/master/Objects/listobject.c)
- [Supering `list` and `collections.MutableSequence`](https://stackoverflow.com/a/38446773/2632107)
"""
# List supering methods; maybe buggy but seem to work so far...
def __init__(self, iterable = [], **kwargs):
"""
> Could not find what built in `list()` calls the initialized lists during init... might just be `self`...
> If feeling cleverer check the C source. For now this class will keep a copy
## License [GNU_GPL-2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
Generates list of metadata dictionaries for lists types
Copyright (C) 2019 S0AndS0
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2
of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
self.metadata = []
for index, value in enumerate(iterable):
if isinstance(value, list):
sub_kwargs = {}
sub_kwargs.update(kwargs)
sub_kwargs['address'] = kwargs.get('address', [index])
sub_list = MetaList(iterable = value, **sub_kwargs)
self.append(sub_list, **kwargs)
else:
self.append(value, **kwargs)
# Note; supering order matters when using built in methods during init
super(MetaList, self).__init__(iterable)
def __add__(self, other):
"""
Called when adding one list to another, eg `MetaList([1,2,3]) + [9,8,7]`
- Returns copy of list plus `other`, sorta like `self.extend` but without mutation
## Example input
test_list = MetaList([1 ,2, 3])
longer_list = test_list + [4, 5, 6]
## Example output
print("#\ttest_list -> {0}".format(test_list))
# test_list -> [1, 2, 3]
print("#\tlonger_list -> {0}".format(longer_list))
# longer_list -> [1, 2, 3, 4, 5, 6]
"""
super(MetaList, self).__add__(other)
output = MetaList(self)
output.extend(other)
return output
def __setitem__(self, index, item, **kwargs):
"""
Called when setting values by index, eg `listing[0] = 'value'`, this updates `self` and `self.metadata`
"""
super(MetaList, self).__setitem__(index, item)
address = kwargs.get('address', []) + [index]
value = item
dictionary = self.__return_dictionary(
address = address,
index = index,
value = value)
self.metadata[index] = dictionary
self.__refresh_first()
self.__refresh_last()
self.__refresh_indexes(start = index)
def append(self, item, **kwargs):
"""
Appends to `self.metadata` an `OrderedDict` with the following keys
- `address`: `[0]` or `[0, 1, 5]` list of indexes mapping to `value`
- `index`: `0` or `42` integer of index within current listing
- `value`: `string`, `['list']`, `{'dict': 'val'}`, etc; not enabled by default
- `first`: `True`/`False` boolean; item is first in current listing
- `last`: `True`/`False` boolean; item is last in current listing
"""
super(MetaList, self).append(item)
# Update last status of previously last item within `self.metadata`
if self.metadata:
self.metadata[-1]['last'] = False
index = len(self.metadata)
address = kwargs.get('address', []) + [index]
value = item
dictionary = self.__return_dictionary(
address = address,
index = index,
value = value)
dictionary['first'] = False
dictionary['last'] = True
if len(self.metadata) == 0:
dictionary['first'] = True
self.metadata += [dictionary]
def extend(self, listing, **kwargs):
"""
Extends `self.metadata` with data built from passed `listing`
- Returns: `None`
> `kwargs` is passed to `MetaList` when transmuting list types
"""
super(MetaList, self).extend(listing)
for index, value in enumerate(listing):
if isinstance(value, list):
last_address = []
if self.metadata:
# Grab `address` list minus last item
last_address = self.metadata[-1]['address'][0:-1]
# Add this `index` to `address` list for recursing
sub_list = MetaList(value, address = last_address + [index], **kwargs)
self.append(sub_list, **kwargs)
else:
self.append(value, **kwargs)
def insert(self, index, item, **kwargs):
"""
Inserts `item` at `index` for `self` and dictionary into `self.metadata`
- Returns: `None`
Note: `self.metadata[index + 1]` have the following data mutated
- `data['index']`
- `data['address']`
Additionally: `self.metadata[0]` and `self.metadata[-1]` data mutations will occur
- `data['first']`
- `data['last']`
"""
super(MetaList, self).insert(index, item)
address = kwargs.get('address', []) + [index]
dictionary = self.__return_dictionary(
address = address,
index = index,
value = item,
**kwargs)
self.metadata.insert(index, dictionary)
self.__refresh_first()
self.__refresh_last()
self.__refresh_indexes(start = index)
# Off-set to avoid n +- 1 errors ;-)
self.__refresh_addresses(
start = index + 1,
index = len(address) - 1,
modifier = 1)
def pop(self, index = -1, target = None):
"""
Pop value from `self` and `self.metadata`, at `index`
- Returns: `self.pop(i)` or `self.metadata.pop(i)` depending on `target`
"""
popped_self = super(MetaList, self).pop(index)
popped_meta = self.__pop_metadata(index)
if 'metadata' in target.lower():
return popped_meta
return popped_self
def remove(self, value):
"""
Removes `value` from `self` and `self.metadata` lists
- Returns: `None`
- Raises: `ValueError` if value does not exsist within `self` or `self.metadata` lists
"""
super(MetaList, self).remove(value)
productive = False
for data in self.metadata:
if data['value'] == value:
productive = True
self.__pop_metadata(data['index'])
break
if not productive:
raise ValueError("value not found in MetaList.metadata values")
# Special herbs and spices for keeping the metadata fresh
def __pop_metadata(self, index = -1):
"""
Pops `index` from `self.metadata` listing, last item if no `index` was passed
- Returns: `<dictionary>`
- Raises: `IndexError` if `index` is outside of listed range
"""
popped_metadata = self.metadata.pop(index)
addr_index = len(popped_metadata['address']) - 1
## Update values within `self.metadata` dictionaries
self.__refresh_first()
self.__refresh_last()
self.__refresh_indexes(start = index)
self.__refresh_addresses(start = index, index = addr_index, modifier = -1)
return popped_metadata
def __return_dictionary(self, address, index, value, **kwargs):
"""
Returns dictionaries for use in `self.metadata` that contains;
- `address`: list of indexes leading to nested value, eg `[0, 4, 2]`
- `index`: integer of where value is stored in current listing
- `value`: Duck!... Note list types will be converted to `MetaList`
- `first`: boolean `False` by default
- `last`: boolean `False` by default
> `kwargs`: passes through to `MetaList` if transmuting a list `value`
"""
if isinstance(value, list):
kwargs['address'] = address
value = MetaList(value, **kwargs)
dictionary = OrderedDict()
dictionary['address'] = address
dictionary['index'] = index
dictionary['value'] = value
dictionary['first'] = False
dictionary['last'] = False
return dictionary
def __refresh_indexes(self, start = 0):
"""
Update indexes from `start` till the last
- Returns: `None`
"""
for i in range(start, len(self.metadata)):
self.metadata[i]['index'] = i
def __refresh_addresses(self, start = 0, end = None, index = 0, modifier = -1):
"""
Updates `address`es within `self.metadata` recursively
- Returns: `None`
- Raises: `TODO`
> `index` is the *depth* within `address` that `modifier` will be applied to
"""
if not start or start < 0:
start = 0
if not end or end > len(self.metadata):
end = len(self.metadata)
for i in range(start, end):
metadata = self.metadata[i]
if isinstance(metadata['value'], list):
metadata['value'].__refresh_addresses(index = index, modifier = modifier)
else:
if len(metadata['address']) - 1 >= index:
metadata['address'][index] += modifier
else:
raise Exception("# TODO: __refresh_addresses append or extend address list")
def __refresh_last(self, quick = True):
"""
Sets/re-sets `self.metadata` `last` value
- Returns `True`/`False` based on if `self.metadata` was touched
If `quick` is `False` all items in current listing will be touched
If `quick` is `True` only the last item and second to last items are touched
"""
if not self.metadata:
return False
if len(self.metadata) > 1:
self.metadata[-2]['last'] = False
if not quick and len(self.metadata) > 1:
for i in range(0, len(self.metadata) - 1):
self.metadata[i]['last'] = False
self.metadata[-1]['last'] = True
return True
def __refresh_first(self, quick = True):
"""
Sets first dictionary within `self.metadata` `first` key to `True`
- Returns `True`/`False` based on if `self.metadata` was touched
If `quick` is `False` all items will be touched in current listing
If `quick` is `True` the first and second items are updated
"""
if not self.metadata:
return False
if len(self.metadata) > 1:
self.metadata[1]['first'] = False
if not quick and len(self.metadata) > 1:
for i in range(1, len(self.metadata)):
self.metadata[i]['first'] = False
self.metadata[0]['first'] = True
return True
# Stuff to play with
def deep_get(self, indexes, iterable = None):
"""
Loops over `indexes` returning inner list or value from `self.metadata`
- `indexes` list of indexes, eg `[1, 3, 2]`
- `iterable` maybe list, if not provided `self.metadata` is searched
"""
referance = self.metadata
if iterable:
reference = iterable
for index in indexes:
reference = reference[index]
return reference
def copy_metadata(self):
"""
Returns copy of `self.metadata`
"""
return list(self.metadata)
def yield_metadata(self, iterable = None, skip = {'first': False, 'last': False, 'between': False}, **kwargs):
"""
Yields a *flat* representation of `self.metadata`,
Prefilter via `skip = {}` dictionary with the following data
- `first`: boolean, if `True` skips items that are first
- `last`: boolean, if `True` skips items that are last
- `between`: boolean, if `True` skips items that are not last or first
"""
metadata = self.metadata
if iterable:
metadata = MetaList(iterable).metadata
for item in metadata:
if isinstance(item.get('value'), list):
# Recurse thy self
for data in item['value'].yield_metadata(skip = skip, **kwargs):
yield data
else:
if skip:
if skip.get('first', False) and item['first']:
continue
if skip.get('last', False) and item['last']:
continue
if skip.get('between', False) and not item['first'] and not item['last']:
continue
# If not skipped get to yielding
yield item
......它可能比那个公开谈论亲密接触的朋友的灯具更糟糕,他们知道自己是谁......但这确实做了一些漂亮的把戏
示例输入一
meta_list = MetaList([1, 2, 3, 4, 5])
for data in meta_list.metadata:
if data['first']:
continue
if data['last']:
continue
print("self[{0}] -> {1}".format(data['index'], data['value']))
示例输出一
self[1] -> 2
self[2] -> 3
self[3] -> 4
示例输入二
meta_list = MetaList(['item one', ['sub item one', ('sub', 'tuple'), [1, 2, 3], {'key': 'val'}], 'item two'])
for data in meta_list.yield_metadata():
address = "".join(["[{0}]".format(x) for x in data.get('address')])
value = data.get('value')
print("meta_list{0} -> {1} <- first: {2} | last: {3}".format(address, value, data['first'], data['last']))
示例输出二
meta_list[0] -> item one <- first: True | last: False
meta_list[1][0] -> sub item one <- first: True | last: False
meta_list[1][1] -> ('sub', 'tuple') <- first: False | last: False
meta_list[1][2][0] -> 1 <- first: True | last: False
meta_list[1][2][1] -> 2 <- first: False | last: False
meta_list[1][2][2] -> 3 <- first: False | last: True
meta_list[1][3] -> {'key': 'val'} <- first: False | last: True
meta_list[2] -> item two <- first: False | last: True
如果你觉得你的大脑变得很新鲜,但这有点不完全好,那就是它自己的方式更好......对我来说,这是最Pythonic
享受吧,如果有兴趣的话,我会把它推送到 GitHub 上供所有人使用 Pull and Fork。
旁注@fabrizioM +1 出色地使用@property
魔法