Python a certains types qu'il garantit aura seulement un exemple. Des exemples de ces instances sont None
, NotImplemented
, et Ellipsis
. Ce sont (par définition) des singletons et des choses comme None is None
sont garantis pour revenir True
car il n'y a aucun moyen de créer une nouvelle instance d' NoneType
.
Il fournit également quelques doubletons 1True
, False
2 -- Toutes les références à True
pointent vers le même objet. Encore une fois, c'est parce qu'il n'y a aucun moyen de créer une nouvelle instance d' bool
.
Ci-dessus, les choses sont garanties par le langage python. Cependant, comme vous l'avez remarqué, il y a certains types (tous immuable) qui stockent des certains cas de réutilisation. Ceci est permis par le langage, mais les différentes implémentations peuvent choisir d'utiliser cette allocation ou pas-en fonction de leurs stratégies d'optimisation. Quelques exemples qui relèvent de cette catégorie sont de petits entiers (-5 -> 255), le vide tuple
et vide frozenset
.
Enfin, Disponible intern
s certains des objets immuables au cours de l'analyse...
par exemple, si vous exécutez le script suivant avec Disponible, vous verrez qu'il renvoie True
:
def foo():
return (2,)
if __name__ == '__main__':
print foo() is foo()
Cela semble vraiment bizarre. Le truc qui est Disponible de jeu est que chaque fois qu'il construit la fonction foo
, il voit un tuple littérale qui contient d'autres simples (immuable) littéraux. Plutôt que de créer ce n-uplet (ou ses équivalents) de plus et plus, python crée juste une fois. Il n'y a pas de danger de cet objet d'être changé depuis l'accord de l'ensemble est immuable. Cela peut être une grande victoire pour la performance de la même boucle est appelée à plusieurs reprises. Des petites chaînes sont internés ainsi. La véritable victoire ici est dans le dictionnaire de recherches. Python peut faire un (très rapide) du pointeur de comparer et de retomber sur le plus lent de la chaîne des comparaisons lors de la vérification des collisions de hachage. Depuis tellement de python est construit sur dictionnaire des recherches, cela peut être une grande optimisation de la langue dans son ensemble.
1j'ai peut-être fait de ce mot ... Mais j'espère que vous avez compris l'idée...
2dans des circonstances normales, vous n'avez pas besoin de vérifier si l'objet est une référence à l' True
-- Habituellement, vous n'soins si l'objet est "truthy" -- par exemple, si if some_instance: ...
d'exécuter la branche. Mais, je l'ai mis ici juste pour être complet.
Notez que is
peut être utilisé pour comparer des choses qui ne sont pas des singletons. Une utilisation courante est de créer une sentinelle de la valeur:
sentinel = object()
item = next(iterable, sentinel)
if items is sentinel:
# iterable exhausted.
Ou:
_sentinel = object()
def function(a, b, none_is_ok_value_here=_sentinel):
if none_is_ok_value_here is sentinel:
# Treat the function as if `none_is_ok_value_here` was not provided.
La morale de cette histoire est de toujours dire ce que tu veux dire. Si vous voulez vérifier si une valeur est une autre valeur, puis utilisez l' is
de l'opérateur. Si vous voulez vérifier si une valeur est égale à une autre valeur (mais peut-être distinctes), puis utilisez ==
. Pour plus de détails sur la différence entre is
et ==
(et à utiliser), consultez l'un des postes suivants:
Addendum
Nous avons parlé à propos de ces Disponible détails de mise en œuvre et nous avons affirmé qu'ils sont des optimisations. Il serait bien de tenter de mesurer tout ce que nous obtenir de tout cela, de l'optimisation (autre qu'un peu de confusion lorsque l'on travaille avec l' is
opérateur).
Chaîne de "stage" et le dictionnaire des recherches.
Voici un petit script que vous pouvez exécuter pour voir comment beaucoup plus rapide dictionnaire des recherches sont si vous utilisez la même chaîne à rechercher la valeur au lieu d'une chaîne de caractère. Remarque, j'utilise le terme "internés" dans les noms de variable, Ces valeurs ne sont pas nécessairement des internés (bien qu'elles puissent être). Je suis juste en utilisant que pour indiquer que les "internés" chaîne est la chaîne dans le dictionnaire.
import timeit
interned = 'foo'
not_interned = (interned + ' ').strip()
assert interned is not not_interned
d = {interned: 'bar'}
print('Timings for short strings')
number = 100000000
print(timeit.timeit(
'd[interned]',
setup='from __main__ import interned, d',
number=number))
print(timeit.timeit(
'd[not_interned]',
setup='from __main__ import not_interned, d',
number=number))
####################################################
interned_long = interned * 100
not_interned_long = (interned_long + ' ').strip()
d[interned_long] = 'baz'
assert interned_long is not not_interned_long
print('Timings for long strings')
print(timeit.timeit(
'd[interned_long]',
setup='from __main__ import interned_long, d',
number=number))
print(timeit.timeit(
'd[not_interned_long]',
setup='from __main__ import not_interned_long, d',
number=number))
Les valeurs exactes ici ne devrait pas trop, mais sur mon ordinateur, les chaînes courtes de spectacle d'environ 1 partie en 7 plus rapide. Le long des chaînes sont presque 2x plus rapide (parce que la comparaison de chaînes de caractères prend plus de temps si la chaîne a plus de caractères à comparer). Les différences ne sont pas aussi frappant sur python3.x, mais ils sont toujours bel et bien là.
Tuple "interner"
Voici un petit script que vous pouvez jouer avec:
import timeit
def foo_tuple():
return (2, 3, 4)
def foo_list():
return [2, 3, 4]
assert foo_tuple() is foo_tuple()
number = 10000000
t_interned_tuple = timeit.timeit('foo_tuple()', setup='from __main__ import foo_tuple', number=number)
t_list = (timeit.timeit('foo_list()', setup='from __main__ import foo_list', number=number))
print(t_interned_tuple)
print(t_list)
print(t_interned_tuple / t_list)
print('*' * 80)
def tuple_creation(x):
return (x,)
def list_creation(x):
return [x]
t_create_tuple = timeit.timeit('tuple_creation(2)', setup='from __main__ import tuple_creation', number=number)
t_create_list = timeit.timeit('list_creation(2)', setup='from __main__ import list_creation', number=number)
print(t_create_tuple)
print(t_create_list)
print(t_create_tuple / t_create_list)
Celui-ci est un peu plus compliqué à l'heure (et je suis heureux de prendre toutes les meilleures idées combien de temps il est dans les commentaires). L'essentiel, c'est que, en moyenne (et sur mon ordinateur), un n-uplet prend environ 60%, tant pour créer une liste ne. Toutefois, foo_tuple()
est en moyenne de 40% le temps qu' foo_list()
prend. Qui montre que nous avons vraiment faire gagner un peu d'une accélération de ces stagiaires. Le gain de temps semble augmenter à mesure que le tuple devient plus grand (création d'une liste plus longue prend plus de temps -- Le tuple "création" prend de la constante de temps puisqu'elle a déjà été créé).
Notez également que j'ai appelé ce "stage". Il n'est pas vraiment (du moins pas dans le même sens que les chaînes sont internés). On peut voir la différence dans ce simple script:
def foo_tuple():
return (2,)
def bar_tuple():
return (2,)
def foo_string():
return 'foo'
def bar_string():
return 'foo'
print(foo_tuple() is foo_tuple()) # True
print(foo_tuple() is bar_tuple()) # False
print(foo_string() is bar_string()) # True
Nous voyons que les cordes sont vraiment des "internés" -- Différents appels en utilisant la même notation littérale retour le même objet. Le tuple "stage" semble être spécifique à une seule ligne.