Pourquoi est-ce string.join (list) au lieu de list.join (string)?

1762

Cela m'a toujours dérouté. Il semble que ce serait mieux:

my_list = ["Hello", "world"]
print(my_list.join("-"))
# Produce: "Hello-world"

Que ceci:

my_list = ["Hello", "world"]
print("-".join(my_list))
# Produce: "Hello-world"

Y a-t-il une raison précise à cela?

Evan Fosmark
la source
1
Pour faciliter la mémoire et la compréhension, -déclare que vous joignez une liste et que vous la convertissez en chaîne. Elle est orientée vers les résultats.
Calcul
11
@JawSaw: Cela embrouille encore plus les mem.
einpoklum
34
Je pense que la réponse courte est que c'est parce que le système de type de Python n'est pas assez fort, et il était plus facile d'implémenter cette fonctionnalité une fois strque de l'implémenter sur chaque type itérable.
BallpointBen
3
Je pense que l'idée originale est que parce que join () renvoie une chaîne, elle devrait être appelée à partir du contexte de la chaîne. Mettre join () sur une liste n'a pas beaucoup de sens dans la mesure où une liste est un conteneur d'objets et ne devrait pas avoir une fonction unique spécifique aux seules chaînes.
Joshua Burns

Réponses:

1248

C'est parce que tout itérable peut être joint (par exemple, list, tuple, dict, set), mais le résultat et le "joiner" doivent être des chaînes.

Par exemple:

'_'.join(['welcome', 'to', 'stack', 'overflow'])
'_'.join(('welcome', 'to', 'stack', 'overflow'))
'welcome_to_stack_overflow'

Utiliser autre chose que des chaînes générera l'erreur suivante:

TypeError: élément de séquence 0: instance de chaîne attendue, int trouvé

récursif
la source
57
Je ne suis pas d'accord conceptuellement même si cela a du sens sur le plan du code. list.join(string)semble plus une approche orientée objet alors qu'elle string.join(list)me semble beaucoup plus procédurale.
Eduardo Pignatelli
22
Alors pourquoi n'est-il pas implémenté sur itérable?
Steen Schütt
10
@TimeSheep: Une liste d'entiers n'a pas de jointure significative, même si elle est itérable.
récursive
16
J'ai essayé d'utiliser print(str.join('-', my_list))et ça marche, ça se sent mieux.
pimgeek
13
@TimeSheep Parce que l'itérable n'est pas un type concret, l'itérable est une interface, tout type qui définit une __iter__méthode. Exiger que tous les itérables soient également implémentés joincompliquerait une interface générale (qui couvre également les itérables sur des non-chaînes) pour un cas d'utilisation très particulier. La définition joinsur les strins des étapes secondaires de ce problème au détriment de la commande "non intuitive". Un meilleur choix aurait pu être de le garder une fonction avec le premier argument étant l'itérable et le second (optionnel) étant la chaîne de menuisier - mais ce navire a navigué.
user4815162342
319

Cela a été discuté dans les méthodes String ... enfin enfiler dans le Python-Dev achive, et a été accepté par Guido. Ce fil a commencé en juin 1999 et a str.joinété inclus dans Python 1.6 qui a été publié en septembre 2000 (et pris en charge Unicode). Python 2.0 ( strméthodes prises en charge incluses join) a été publié en octobre 2000.

  • Il y avait quatre options proposées dans ce fil:
    • str.join(seq)
    • seq.join(str)
    • seq.reduce(str)
    • join comme fonction intégrée
  • Guido voulait prendre en charge non seulement lists, tuples, mais toutes les séquences / itérables.
  • seq.reduce(str) est difficile pour les nouveaux arrivants.
  • seq.join(str) introduit une dépendance inattendue des séquences vers str / unicode.
  • join()en tant que fonction intégrée ne prendrait en charge que des types de données spécifiques. Donc, utiliser un espace de noms intégré n'est pas bon. Si join()prend en charge de nombreux types de données, la création d'une implémentation optimisée serait difficile, si elle est implémentée à l'aide de la __add__méthode, alors c'est O (n²).
  • La chaîne de séparation ( sep) ne doit pas être omise. Explicite vaut mieux qu'implicite.

Il n'y a pas d'autres raisons proposées dans ce fil.

Voici quelques réflexions supplémentaires (les miennes et celles de mes amis):

  • Le support Unicode arrivait, mais ce n'était pas définitif. À cette époque, l'UTF-8 était le plus susceptible de remplacer UCS2 / 4. Pour calculer la longueur totale du tampon des chaînes UTF-8, il doit connaître la règle de codage des caractères.
  • À cette époque, Python avait déjà décidé d'une règle d'interface de séquence commune où un utilisateur pourrait créer une classe de type séquence (itérable). Mais Python ne prenait pas en charge l'extension des types intégrés jusqu'à la version 2.2. A cette époque, il était difficile de fournir une classe itérable de base (qui est mentionnée dans un autre commentaire).

La décision de Guido est enregistrée dans un courrier historique , décidant str.join(seq):

C'est drôle, mais ça semble juste! Barry, fonce ...
--Guido van Rossum

Yoshiki Shibukawa
la source
251

Parce que la join()méthode est dans la classe de chaîne, au lieu de la classe de liste?

Je suis d'accord, ça a l'air drôle.

Voir http://www.faqs.org/docs/diveintopython/odbchelper_join.html :

Note historique.Quand j'ai appris Python pour la première fois, je m'attendais à ce que join soit une méthode de liste, qui prendrait le délimiteur comme argument. Beaucoup de gens ressentent la même chose, et il y a une histoire derrière la méthode de jointure. Avant Python 1.6, les chaînes n'avaient pas toutes ces méthodes utiles. Il y avait un module de chaîne séparé qui contenait toutes les fonctions de chaîne; chaque fonction a pris une chaîne comme premier argument. Les fonctions ont été jugées suffisamment importantes pour être placées sur les chaînes elles-mêmes, ce qui était logique pour des fonctions telles que inférieure, supérieure et divisée. Mais de nombreux programmeurs Python hard-core se sont opposés à la nouvelle méthode de jointure, arguant qu'elle devrait plutôt être une méthode de la liste, ou qu'elle ne devrait pas bouger du tout mais simplement rester une partie de l'ancien module de chaîne (qui a encore beaucoup de choses utiles en elle).

--- Mark Pilgrim, Plongez dans Python

Bill Karwin
la source
12
La stringbibliothèque Python 3 a supprimé toutes les strméthodes redondantes , vous ne pouvez donc plus les utiliser string.join(). Personnellement, je n'ai jamais pensé que c'était «drôle», c'est parfaitement logique, car vous pouvez rejoindre bien plus que de simples listes, mais le menuisier est toujours une chaîne!
Martijn Pieters
67

Je suis d'accord que c'est contre-intuitif au début, mais il y a une bonne raison. Join ne peut pas être une méthode d'une liste car:

  • il doit également fonctionner pour différents itérables (tuples, générateurs, etc.)
  • il doit avoir un comportement différent entre différents types de chaînes.

Il existe en fait deux méthodes de jointure (Python 3.0):

>>> b"".join
<built-in method join of bytes object at 0x00A46800>
>>> "".join
<built-in method join of str object at 0x00A28D40>

Si join était une méthode d'une liste, alors il devrait inspecter ses arguments pour décider lequel appeler. Et vous ne pouvez pas joindre octet et str ensemble, donc la façon dont ils l'ont maintenant a du sens.

Kiv
la source
45

Pourquoi est-ce string.join(list)au lieu de list.join(string)?

C'est parce que joinc'est une méthode "chaîne"! Il crée une chaîne à partir de n'importe quel itérable. Si nous avons collé la méthode sur des listes, qu'en est-il lorsque nous avons des itérables qui ne sont pas des listes?

Et si vous avez un tuple de cordes? S'il s'agissait d'une listméthode, vous devriez listconvertir chaque itérateur de chaînes en un avant de pouvoir joindre les éléments en une seule chaîne! Par exemple:

some_strings = ('foo', 'bar', 'baz')

Lançons notre propre méthode de jointure de liste:

class OurList(list): 
    def join(self, s):
        return s.join(self)

Et pour l'utiliser, notez que nous devons d'abord créer une liste à partir de chaque itérable pour joindre les chaînes dans cet itérable, gaspillant à la fois de la mémoire et de la puissance de traitement:

>>> l = OurList(some_strings) # step 1, create our list
>>> l.join(', ') # step 2, use our list join method!
'foo, bar, baz'

Nous voyons donc que nous devons ajouter une étape supplémentaire pour utiliser notre méthode de liste, au lieu d'utiliser simplement la méthode de chaîne intégrée:

>>> ' | '.join(some_strings) # a single step!
'foo | bar | baz'

Avertissement de performance pour les générateurs

L'algorithme utilisé par Python pour créer la chaîne finale str.joindoit en fait passer deux fois sur l'itérable, donc si vous lui fournissez une expression de générateur, il doit d'abord le matérialiser dans une liste avant de pouvoir créer la chaîne finale.

Ainsi, bien que le fait de contourner les générateurs soit généralement meilleur que les compréhensions de liste, il str.joins'agit d'une exception:

>>> import timeit
>>> min(timeit.repeat(lambda: ''.join(str(i) for i in range(10) if i)))
3.839168446022086
>>> min(timeit.repeat(lambda: ''.join([str(i) for i in range(10) if i])))
3.339879313018173

Néanmoins, l' str.joinopération est toujours sémantiquement une opération "chaîne", il est donc logique de l'avoir sur l' strobjet que sur divers itérables.

Aaron Hall
la source
24

Considérez-le comme l'opération orthogonale naturelle à diviser.

Je comprends pourquoi il s'applique à tout ce qui est itérable et ne peut donc pas être facilement mis en œuvre simplement sur la liste.

Pour plus de lisibilité, j'aimerais le voir dans le langage, mais je ne pense pas que ce soit réellement faisable - si l'itérabilité était une interface, elle pourrait être ajoutée à l'interface, mais ce n'est qu'une convention et il n'y a donc pas de moyen central de ajoutez-le à l'ensemble des choses qui sont itérables.

Andy Dent
la source
13

Principalement parce que le résultat d'un someString.join()est une chaîne.

La séquence (liste ou tuple ou autre) n'apparaît pas dans le résultat, juste une chaîne. Parce que le résultat est une chaîne, il est logique en tant que méthode d'une chaîne.

S.Lott
la source
10

- in "-". join (my_list) déclare que vous êtes en train de convertir une chaîne en joignant des éléments à une liste. Elle est orientée résultats. (juste pour une mémoire et une compréhension faciles)

Je fais une feuille de triche exhaustive de methods_of_string pour votre référence.

string_methonds_44 = {
    'convert': ['join','split', 'rsplit','splitlines', 'partition', 'rpartition'],
    'edit': ['replace', 'lstrip', 'rstrip', 'strip'],
    'search': ['endswith', 'startswith', 'count', 'index', 'find','rindex', 'rfind',],
    'condition': ['isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isnumeric','isidentifier',
                  'islower','istitle', 'isupper','isprintable', 'isspace', ],
    'text': ['lower', 'upper', 'capitalize', 'title', 'swapcase',
             'center', 'ljust', 'rjust', 'zfill', 'expandtabs','casefold'],
    'encode': ['translate', 'maketrans', 'encode'],
    'format': ['format', 'format_map']}
Calcul
la source
3

Les deux ne sont pas gentils.

string.join (xs, delimit) signifie que le module de chaîne est au courant de l'existence d'une liste, qu'il n'a aucune raison de connaître, car le module de chaîne ne fonctionne qu'avec des chaînes.

list.join (delimit) est un peu plus agréable car nous sommes tellement habitués à ce que les chaînes soient un type fondamental (et linguistiquement, elles le sont). Cependant, cela signifie que la jointure doit être distribuée de manière dynamique car dans le contexte arbitraire du a.split("\n")compilateur python, il se peut que vous ne sachiez pas ce qu'est un fichier, et devrez le rechercher (de manière analogue à la recherche vtable), ce qui est coûteux si vous le faites beaucoup de fois.

si le compilateur d'exécution python sait que la liste est un module intégré, il peut ignorer la recherche dynamique et encoder l'intention directement dans le bytecode, alors qu'autrement il doit résoudre dynamiquement la "jointure" de "a", qui peut être composée de plusieurs couches d'héritage par appel (car entre les appels, la signification de join peut avoir changé, car python est un langage dynamique).

malheureusement, c'est le défaut ultime de l'abstraction; peu importe l'abstraction que vous choisissez, votre abstraction n'aura de sens que dans le contexte du problème que vous essayez de résoudre, et en tant que tel, vous ne pourrez jamais avoir une abstraction cohérente qui ne devienne pas incompatible avec les idéologies sous-jacentes lorsque vous commencez à les coller. ensemble sans les envelopper dans une vue conforme à votre idéologie. Sachant cela, l'approche de python est plus flexible car c'est moins cher, c'est à vous de payer plus pour le rendre "plus joli", soit en créant votre propre wrapper, soit votre propre préprocesseur.

Dmitry
la source
0

Les variables my_listet "-"sont les deux objets. Plus précisément, ce sont des instances des classes listet str, respectivement. La joinfonction appartient à la classe str. Par conséquent, la syntaxe "-".join(my_list)est utilisée car l'objet "-"prend my_listen entrée.

cinquante cartes
la source