Existe-t-il une manière pythonique de savoir quand la première et la dernière boucle d'un for est passée?
-
28-10-2019 - |
Question
J'ai un modèle dans lequel j'ai placé, disons 5 formulaires, mais tous désactivés pour être publiés sauf le premier.Le formulaire suivant ne peut être rempli que si je clique d'abord sur un bouton qui l'active.
Je cherche un moyen d'implémenter une variable de type forloop.last de type Django dans une boucle for à l'intérieur d'un test d'acceptation pour décider d'exécuter une méthode qui active la forme suivante ou non.
En gros, ce que je dois faire est quelque chose comme:
for form_data in step.hashes:
# get and fill the current form with data in form_data
if not forloop.last:
# click the button that enables the next form
# submit all filled forms
La solution
Si je comprends bien votre question, vous voulez un test simple pour savoir si vous êtes au début ou à la fin de la liste?
Si tel est le cas, ce serait le cas:
for item in list:
if item != list[-1]:
#Do stuff
Pour le premier élément de la liste, vous remplaceriez "-1" par 0.
Autres conseils
Je ne connais rien de intégré, mais vous pouvez facilement écrire un générateur pour vous donner les informations requises:
def firstlast(seq):
seq = iter(seq)
el = prev = next(seq)
is_first = True
for el in seq:
yield prev, is_first, False
is_first = False
prev = el
yield el, is_first, True
>>> list(firstlast(range(4)))
[(0, True, False), (1, False, False), (2, False, False), (3, False, True)]
>>> list(firstlast(range(0)))
[]
>>> list(firstlast(range(1)))
[(0, True, True)]
>>> list(firstlast(range(2)))
[(0, True, False), (1, False, True)]
>>> for count, is_first, is_last in firstlast(range(3)):
print(count, "first!" if is_first else "", "last!" if is_last else "")
0 first!
1
2 last!
Vous pouvez utiliser enumerate
et comparer le compteur avec la longueur de la liste:
for i, form_data in enumerate(step.hashes):
if i < len(step.hashes):
whatever()
for form_data in step.hashes[:-1]:
# get and fill the current form with data in form_data
for form_data in step.hashes[-1:]:
# get and fill the current form with data in form_data
# click the button that enables the next form
# submit all filled forms
Vous n'aimez pas la répétition de get and fill the current form with data in form_data
?Définissez une fonction.
Générateur avec tampon.
def first_last( iterable ):
i= iter(iterable)
f= next(i)
yield f, "first"
n= next(i)
for another in i:
yield n, None
n= another
yield n, "last"
for item, state in first_list( iterable ):
# state is "first", None or "last".
Zipper deux séquences
flags = ["first"] + (len(iterable)-2)*[None] + ["last"]
for item, state in zip( iterable, flags ):
# state is "first", None or "last".
Je pense qu'il veut avoir un wrapper autour de l'itérateur qui fournit les premières / dernières requêtes, aussi le paramètre pourrait être un itérateur donc toute sorte de len () échouerait
Voici ce que je suis venu jusqu'à présent, l'astuce est d'utiliser un double itérateur, celui qui regarde vers l'avenir une étape de la première:
class FirstLastIter(object):
def __init__(self, seq):
self._seq_iter = iter(seq)
self._seq_iter_next = iter(seq)
self._idx = -1
self._last = None
self.next_next()
@property
def first(self):
return self._idx == 0
@property
def last(self):
return self._last == True
def __iter__(self):
return self
def next_next(self):
try:
self._seq_iter_next.next()
except StopIteration:
self._last = True
def next(self):
val = self._seq_iter.next()
self._idx += 1
self.next_next()
return val
for x in FirstLastIter([]):
print x
iterator = FirstLastIter([1])
for x in iterator:
print x,iterator.first,iterator.last
iterator = FirstLastIter([1,2,3])
for x in iterator:
print x,iterator.first,iterator.last
renvoie:
1 True True
1 True False
2 False False
3 False True
Avant que quiconque aille aiguiser ses torches ou allumer les fourches, je ne suis pas un expert de ce qui est Pythonic , cela dit, il me semble que si first
et / ou last
est recherché dans une liste , dans le depuis de if first
ou if last
dans une boucle, il semble s'attendre à super
la classe, et à ajouter la fonctionnalité souhaitée ... peut-être, donc ce qui suit est totalement la version pré-alpha e-1 ^ 11% sorta code qui peut causer des ravages s'il est regardé un mardi de la bonne façon ...
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
... et c'est peut-être plus buggué que les luminaires de cet ami qui a parlé publiquement de rencontres proches, ils savent qui ils sont ... mais cela fait quelques astuces astucieuses
Exemple d'entrée un
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']))
Exemple de sortie un
self[1] -> 2
self[2] -> 3
self[3] -> 4
Exemple d'entrée deux
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']))
Exemple de sortie deux
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
Si vous sentez que votre cerveau devient tout à la menthe fraîche, mais ce n'est pas tout à fait correct et c'est à sa manière, c'est encore mieux ... pour moi, c'est le plus pythonique
Amusez-vous bien, et peut-être que s'il y a de l'intérêt, je pousserai ceci sur GitHub pour que tous puissent tirer et fourrer.
Note d'accompagnement @fabrizioM +1 pour une superbe utilisation de la magie
@property