REMARQUE:Ce site est obsolète. Le site sera désactivé après le 31 janvier 2023 et le trafic redirigera vers le nouveau site à l'adresse https://protobuf.dev. En attendant, les mises à jour ne seront effectuées que sur protobuf.dev.

Code généré par Python

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

Cette page décrit précisément les définitions de Python générées par le compilateur de tampon de protocole pour une définition de protocole donnée. Toute différence entre le code généré proto2 et le code généré proto3 est mise en évidence. Notez que ces différences résident dans le code généré tel que décrit dans ce document et non dans les classes/interfaces de messages de base, qui sont identiques dans les deux versions. Lisez le guide du langage proto2 et/ou le guide du langage proto3 avant de lire ce document.

L'implémentation de Protocol Buffers Python est légèrement différente de C++ et Java. En Python, le compilateur ne génère que le code permettant de créer des descripteurs pour les classes générées. Une métaclasse Python fait le travail. Ce document décrit ce que vous obtenez après l'application de la métaclasse.

Appel du compilateur

Le compilateur de tampon de protocole génère une sortie Python lorsqu'elle est appelée avec l'option de ligne de commande --python_out=. Le paramètre de l'option --python_out= correspond au répertoire dans lequel vous souhaitez que le compilateur écrive votre sortie Python. Le compilateur crée un fichier .py pour chaque entrée de fichier .proto. Les noms des fichiers de sortie sont calculés en prenant le nom du fichier .proto et en apportant deux modifications:

  • L'extension (.proto) est remplacée par _pb2.py.
  • Le chemin d'accès proto (spécifié avec l'option de ligne de commande --proto_path= ou -I) est remplacé par le chemin de sortie (spécifié avec l'option --python_out=).

Par exemple, imaginons que vous invoquez le compilateur comme suit :

protoc --proto_path=src --python_out=build/gen src/foo.proto src/bar/baz.proto

Le compilateur lit les fichiers src/foo.proto et src/bar/baz.proto, puis produit deux fichiers de sortie : build/gen/foo_pb2.py et build/gen/bar/baz_pb2.py. Le compilateur crée automatiquement le répertoire build/gen/bar si nécessaire, mais il ne crée pas build ni build/gen. Ils doivent déjà exister.

Notez que si le fichier .proto ou son chemin contient des caractères qui ne peuvent pas être utilisés dans les noms de modules Python (comme des traits d'union), ils sont remplacés par des traits de soulignement. Ainsi, le fichier foo-bar.proto devient le fichier Python foo_bar_pb2.py.

Lorsque vous générez du code Python, la capacité du compilateur de tampon de protocole à générer directement des archives ZIP est particulièrement pratique, car l'interpréteur Python est capable de lire directement à partir de ces archives si elles sont placées dans PYTHONPATH. Pour générer la sortie dans un fichier ZIP, il vous suffit de fournir un emplacement de sortie se terminant par .zip.

Le chiffre 2 dans l'extension _pb2.py désigne la version 2 des Protocol Buffers. La version 1 a été principalement utilisée dans Google, mais vous pouvez peut-être trouver certaines parties de ce code dans d'autres codes Python publiés avant Protocol Buffers. Étant donné que la version 2 de Protocoles Buffers Python a une interface complètement différente et que Python ne dispose pas de vérification du type au moment de la compilation pour détecter les erreurs, nous avons choisi de faire du numéro de version un composant proéminent des noms de fichiers Python générés. Actuellement, les protocoles générés par proto2 et proto3 utilisent _pb2.py.

Colis

Le code Python généré par le compilateur de tampon de protocole n'est pas affecté par le nom du package défini dans le fichier .proto. À la place, les packages Python sont identifiés par une structure de répertoires.

Messages

Voici une déclaration de message simple :

message Foo {}

Le compilateur de tampon de protocole génère une classe appelée Foo, qui sous-classe google.protobuf.Message. La classe est une classe concrète. Aucune méthode abstraite n'est laissée en œuvre. Contrairement à C++ et Java, le code généré par Python n'est pas affecté par l'option optimize_for dans le fichier .proto. En fait, tout le code Python est optimisé pour la taille du code.

Si le nom du message est un mot clé Python, sa classe ne sera accessible que via getattr(), comme décrit dans la section Noms en conflit avec des mots clés Python.

Vous ne devez pas créer vos propres sous-classes Foo. Les classes générées ne sont pas conçues pour le sous-classement et peuvent entraîner des problèmes de "classe fragile". De plus, l'héritage d'implémentation est de mauvaise qualité.

Les classes de message Python n'ont pas de membres publics autres que ceux définis par l'interface Message et générés pour les champs imbriqués, les messages et les types d'énumération (décrits ci-dessous). Message fournit des méthodes que vous pouvez utiliser pour vérifier, manipuler, lire ou écrire l'intégralité du message, y compris l'analyse et la sérialisation dans des chaînes binaires. En plus de ces méthodes, la classe Foo définit les méthodes statiques suivantes:

  • FromString(s) : renvoie une nouvelle instance de message désérialisée à partir de la chaîne donnée.

Notez que vous pouvez également utiliser le module text_format pour traiter des messages de protocole au format texte. Par exemple, la méthode Merge() vous permet de fusionner la représentation ASCII d'un message dans un message existant.

Types imbriqués

Un message peut être déclaré dans un autre message. Exemple : message Foo { message Bar { } }

Dans ce cas, la classe Bar est déclarée en tant que membre statique de Foo. Vous pouvez donc l'appeler Foo.Bar.

Types bien connus

Protocol Buffers fournit un certain nombre de types connus que vous pouvez utiliser dans vos fichiers .proto avec vos propres types de messages. Certains messages WKT utilisent des méthodes spéciales en plus des méthodes habituelles pour les messages Protocol Buffer, car ils sous-classe google.protobuf.Message et une classe WKT.

Tout

Pour tous les messages, vous pouvez appeler Pack() pour empaqueter un message spécifié dans le message "Tous" actuel ou Unpack() pour décompresser le message "Any" actuel dans un message donné. Exemple :

any_message.Pack(message)
any_message.Unpack(message)

Unpack() compare également le descripteur de l'objet message transmis avec celui stocké et renvoie False s'il ne correspond pas et ne tente pas de décompresser ; True dans le cas contraire.

Vous pouvez également appeler la méthode Is() pour vérifier si le message "Any" représente le type de tampon de protocole donné. Exemple :

assert any_message.Is(message.DESCRIPTOR)

Code temporel

Les messages d'horodatage peuvent être convertis au format de chaîne de date RFC 3339 (chaîne JSON) à l'aide des méthodes ToJsonString()/FromJsonString(). Par exemple,

timestamp_message.FromJsonString("1970-01-01T00:00:00Z")
assert timestamp_message.ToJsonString() == "1970-01-01T00:00:00Z"
.

Vous pouvez également appeler GetCurrentTime() pour remplir le message Timestamp avec l'heure actuelle :

timestamp_message.GetCurrentTime()

Pour convertir d'autres unités de temps depuis l'époque, vous pouvez appeler ToNanoseconds(), FromNanoseconds(), ToMicroseconds(), FromMicroseconds() ,ToMilliseconds(), FromMilliseconds(), ToSeconds() ou FromSeconds(). Le code généré comporte également les méthodes ToDatetime() et FromDatetime() pour convertir les objets datetime Python et les horodatages. Exemple :

timestamp_message.FromMicroseconds(-1)
assert timestamp_message.ToMicroseconds() == -1
dt = datetime(2016, 1, 1)
timestamp_message.FromDatetime(dt)
self.assertEqual(dt, timestamp_message.ToDatetime())

Durée

Les messages de durée utilisent les mêmes méthodes que l'horodatage pour la conversion entre une chaîne JSON et d'autres unités de temps. Pour convertir une valeur en temps et en durée, vous pouvez appeler ToTimedelta() ou FromTimedelta. Exemple :

duration_message.FromNanoseconds(1999999999)
td = duration_message.ToTimedelta()
assert td.seconds == 1
assert td.microseconds == 999999

FieldMask

Les messages FieldMask peuvent être convertis en chaînes JSON à l'aide des méthodes ToJsonString()/FromJsonString(). De plus, un message FieldMask comprend les méthodes suivantes:

  • IsValidForDescriptor: vérifie si le FieldMask est valide pour Message Descriptor.
  • AllFieldsFromDescriptor : récupère tous les champs directs du descripteur de message vers FieldMask.
  • CanonicalFormFromMask : convertit un FieldMask au format canonique.
  • Union: fusionne deux FieldMasks dans ce FieldMask.
  • Intersect : recoupe deux FieldMasks dans ce FieldMask.
  • MergeMessage: fusionne les champs spécifiés dans FieldMask de la source vers la destination.

Struct

Les messages structurés vous permettent d'obtenir et de définir les éléments directement. Par exemple :

struct_message["key1"] = 5
struct_message["key2"] = "abc"
struct_message["key3"] = True

Pour obtenir ou créer une liste ou une structure, vous pouvez appeler get_or_create_list()/get_or_create_struct(). Exemple :

struct.get_or_create_struct("key4")["subkey"] = 11.0
struct.get_or_create_list("key5")

ListValue

Un message ListValue agit comme une séquence Python et vous permet d'effectuer les opérations suivantes :

list_value = struct_message.get_or_create_list("key")
list_value.extend([6, "seven", True, None])
list_value.append(False)
assert len(list_value) == 5
assert list_value[0] == 6
assert list_value[1] == "seven"
assert list_value[2] == True
assert list_value[3] == None
assert list_Value[4] == False

Pour ajouter une valeur ListValue/Struct, appelez add_list()/add_struct(). Exemple :

list_value.add_struct()["key"] = 1
list_value.add_list().extend([1, "two", True])

Champs

Pour chaque champ d'un type de message, la classe correspondante possède une propriété portant le même nom que le champ. La façon dont vous pouvez manipuler la propriété dépend de son type.

En plus d'une propriété, le compilateur génère une constante entière pour chaque champ contenant son numéro de champ. Le nom constant correspond au nom du champ converti en majuscules suivi de _FIELD_NUMBER. Par exemple, pour le champ optional int32 foo_bar = 5;, le compilateur générera la constante FOO_BAR_FIELD_NUMBER = 5.

Si le nom du champ est un mot clé Python, sa propriété ne sera accessible que via getattr() et setattr(), comme décrit dans la section Noms en conflit avec des mots clés Python.

Champs singuliers (proto2)

Si vous disposez d'un champ unique (facultatif ou obligatoire) foo de tout type autre qu'un message, vous pouvez manipuler le champ foo comme s'il s'agissait d'un champ standard. Par exemple, si le type de foo est int32, vous pouvez dire :

message.foo = 123
print(message.foo)

Notez que la définition de foo sur une valeur d'un type incorrect génère une TypeError.

Si foo est lu alors qu'il n'est pas défini, sa valeur est la valeur par défaut de ce champ. Pour vérifier si foo est défini ou pour effacer la valeur de foo, vous devez appeler les méthodes HasField() ou ClearField() de l'interface Message. Exemple :

assert not message.HasField("foo")
message.foo = 123
assert message.HasField("foo")
message.ClearField("foo")
assert not message.HasField("foo")

Champs singuliers (proto3)

Si vous possédez un champ unique foo de tout type autre qu'un message, vous pouvez manipuler le champ foo comme s'il s'agissait d'un champ standard. Par exemple, si le type de foo est int32, vous pouvez dire :

message.foo = 123
print(message.foo)

Notez que la définition de foo sur une valeur d'un type incorrect génère une TypeError.

Si foo est lu alors qu'il n'est pas défini, sa valeur est la valeur par défaut de ce champ. Pour effacer la valeur de foo et rétablir sa valeur par défaut pour son type, appelez la méthode ClearField() de l'interface Message. Exemple :

message.foo = 123
message.ClearField("foo")

Contrairement à proto2, vous ne pouvez pas appeler HasField() pour un champ unique sans message dans proto3 et la bibliothèque renverra une exception si vous essayez de le faire.

Champs de message singuliers

Les types de messages fonctionnent légèrement différemment. Vous ne pouvez pas attribuer de valeur à un champ de message intégré. Au lieu de cela, attribuer une valeur à un champ du message enfant implique de définir le champ du message dans le parent. Dans proto3, vous pouvez également utiliser la méthode HasField() du message parent pour vérifier si une valeur de champ de type de message a été définie, ce que vous ne pouvez pas faire avec d'autres types de champ singulier de proto3.

Par exemple, supposons que vous ayez la définition .proto suivante :

message Foo {
  optional Bar bar = 1;
}
message Bar {
  optional int32 i = 1;
}

Vous ne pouvez pas effectuer les opérations suivantes :

foo = Foo()
foo.bar = Bar()  # WRONG!

Pour définir bar, il vous suffit d'attribuer une valeur directement à un champ de bar. - foo comporte un champ bar :

foo = Foo()
assert not foo.HasField("bar")
foo.bar.i = 1
assert foo.HasField("bar")
assert foo.bar.i == 1
foo.ClearField("bar")
assert not foo.HasField("bar")
assert foo.bar.i == 0  # Default value

De même, vous pouvez définir bar à l'aide de la méthode CopyFrom() de l'interface Message. Cette opération permet de copier toutes les valeurs d'un autre message du même type que bar.

foo.bar.CopyFrom(baz)

Notez que lire un champ dans bar ne le définit pas :

foo = Foo()
assert not foo.HasField("bar")
print(foo.bar.i)  # Print i's default value
assert not foo.HasField("bar")

Si vous avez besoin de l'extrait "comporte" sur un message qui ne comporte aucun champ que vous pouvez définir ou souhaitez définir, vous pouvez utiliser la méthode SetInParent().

foo = Foo()
assert not foo.HasField("bar")
foo.bar.SetInParent()  # Set Foo.bar to a default Bar message
assert foo.HasField("bar")

Champs répétés

Les champs répétés sont représentés sous la forme d'un objet qui agit comme une séquence Python. Comme pour les messages intégrés, vous ne pouvez pas attribuer le champ directement, mais vous pouvez le manipuler. Par exemple, avec la définition de message suivante :

message Foo {
  repeated int32 nums = 1;
}

Vous pouvez effectuer les opérations suivantes :

foo = Foo()
foo.nums.append(15)        # Appends one value
foo.nums.extend([32, 47])  # Appends an entire list

assert len(foo.nums) == 3
assert foo.nums[0] == 15
assert foo.nums[1] == 32
assert foo.nums == [15, 32, 47]

foo.nums[:] = [33, 48]     # Assigns an entire list
assert foo.nums == [33, 48]

foo.nums[1] = 56    # Reassigns a value
assert foo.nums[1] == 56
for i in foo.nums:  # Loops and print
  print(i)
del foo.nums[:]     # Clears list (works just like in a Python list)

La méthode ClearField() de l'interface Message fonctionne en plus de l'del Python.

Champs de message répétés

Les champs de message répétés fonctionnent de la même manière que les champs scalaires répétés. Toutefois, l'objet Python correspondant comporte également une méthode add() qui crée un objet de message, l'ajoute à la liste et le renvoie à l'appelant pour le renseigner. De plus, la méthode append() de l'objet crée une copie du message donné et l'ajoute à la liste. Ainsi, les messages appartiennent toujours au message parent afin d'éviter les références circulaires et toute autre confusion pouvant se produire lorsqu'une structure de données modifiable a plusieurs propriétaires. De même, la méthode extend() de l'objet ajoute une liste complète de messages, mais crée une copie de chaque message de la liste.

Par exemple, avec la définition de message suivante :

message Foo {
  repeated Bar bars = 1;
}
message Bar {
  optional int32 i = 1;
  optional int32 j = 2;
}

Vous pouvez effectuer les opérations suivantes :

foo = Foo()
bar = foo.bars.add()        # Adds a Bar then modify
bar.i = 15
foo.bars.add().i = 32       # Adds and modify at the same time
new_bar = Bar()
new_bar.i = 40
another_bar = Bar()
another_bar.i = 57
foo.bars.append(new_bar)        # Uses append() to copy
foo.bars.extend([another_bar])  # Uses extend() to copy

assert len(foo.bars) == 4
assert foo.bars[0].i == 15
assert foo.bars[1].i == 32
assert foo.bars[2].i == 40
assert foo.bars[2] == new_bar      # The appended message is equal,
assert foo.bars[2] is not new_bar  # but it is a copy!
assert foo.bars[3].i == 57
assert foo.bars[3] == another_bar      # The extended message is equal,
assert foo.bars[3] is not another_bar  # but it is a copy!

foo.bars[1].i = 56    # Modifies a single element
assert foo.bars[1].i == 56
for bar in foo.bars:  # Loops and print
  print(bar.i)
del foo.bars[:]       # Clears list

# add() also forwards keyword arguments to the concrete class.
# For example, you can do:

foo.bars.add(i=12, j=13)

# Initializers forward keyword arguments to a concrete class too.
# For example:

foo = Foo(             # Creates Foo
  bars=[               # with its field bars set to a list
    Bar(i=15, j=17),   # where each list member is also initialized during creation.
    Bar(i=32),
    Bar(i=47, j=77),
  ]
)

assert len(foo.bars) == 3
assert foo.bars[0].i == 15
assert foo.bars[0].j == 17
assert foo.bars[1].i == 32
assert foo.bars[2].i == 47
assert foo.bars[2].j == 77

Contrairement aux champs scalaires répétés, les champs de message répétés ne sont pas compatibles avec l'attribution d'éléments (par exemple, __setitem__). Par exemple:

foo = Foo()
foo.bars.add(i=3)
# WRONG!
foo.bars[0] = Bar(i=15)  # Raises an exception
# WRONG!
foo.bars[:] = [Bar(i=15), Bar(i=17)]  # Also raises an exception

Groupes (proto2)

Notez que les groupes sont obsolètes et ne doivent pas être utilisés lors de la création de types de messages. Utilisez plutôt des types de messages imbriqués.

Un groupe combine un type de message imbriqué et un champ dans une même déclaration, et utilise un format de fil différent pour le message. Le message généré porte le même nom que le groupe. Le nom du champ généré est le nom en minuscules du groupe.

Par exemple, à l'exception du format de communication par filaire, les deux définitions de message suivantes sont équivalentes :

// Version 1: Using groups
message SearchResponse {
  repeated group SearchResult = 1 {
    required string url = 2;
  }
}
// Version 2: Not using groups
message SearchResponse {
  message SearchResult {
    required string url = 2;
  }
  repeated SearchResult searchresult = 1;
}

Un groupe peut être required, optional ou repeated. Un groupe obligatoire ou facultatif est manipulé à l'aide de la même API qu'un champ de message au singulier standard. Un groupe répété est manipulé à l'aide de la même API qu'un champ de message répété standard.

Par exemple, avec la définition SearchResponse ci-dessus, vous pouvez effectuer les opérations suivantes :

resp = SearchResponse()
resp.searchresult.add(url="https://blog.google")
assert resp.searchresult[0].url == "https://blog.google"
assert resp.searchresult[0] == SearchResponse.SearchResult(url="https://blog.google")

Champs de carte

Selon la définition de ce message :

message MyMessage {
  map<int32, int32> mapfield = 1;
}

L'API Python générée pour le champ de mappage est semblable à un dict Python :

# Assign value to map
m.mapfield[5] = 10

# Read value from map
m.mapfield[5]

# Iterate over map keys
for key in m.mapfield:
  print(key)
  print(m.mapfield[key])

# Test whether key is in map:
if 5 in m.mapfield:
  print(“Found!”)

# Delete key from map.
del m.mapfield[key]

Comme pour les champs de messages intégrés, les messages ne peuvent pas être directement attribués à une valeur de mappage. Pour ajouter un message en tant que valeur de mappage, vous devez référencer une clé non définie, qui construit et renvoie un nouveau sous-message :

m.message_map[key].submessage_field = 10

Pour en savoir plus sur les clés non définies, consultez la section suivante.

Référencer des clés non définies

La sémantique des mappages de Protocol Buffer se comporte légèrement différemment des dict Python en ce qui concerne les clés non définies. Dans un dict Python standard, le référencement d'une clé non définie génère une exception KeyError :

>>> x = {}
>>> x[5]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 5

Toutefois, dans les mappages de Protocol Buffers, le référencement d'une clé non définie crée la clé dans le mappage avec une valeur nulle/faux/vide. Ce comportement est semblable à celui de la bibliothèque Python standard defaultdict.

>>> dict(m.mapfield)
{}
>>> m.mapfield[5]
0
>>> dict(m.mapfield)
{5: 0}

Ce comportement est particulièrement pratique pour les mappages avec des valeurs de type de message, car vous pouvez directement mettre à jour les champs du message renvoyé.

>>> m.message_map[5].foo = 3

Notez que même si vous n'attribuez aucune valeur aux champs du message, le sous-message est toujours créé dans le mappage :

>>> m.message_map[10]
<test_pb2.M2 object at 0x7fb022af28c0>
>>> dict(m.message_map)
{10: <test_pb2.M2 object at 0x7fb022af28c0>}

Elle est différente des champs de message intégrés standards, dans lesquels le message n'est créé qu'une fois que vous avez attribué une valeur à l'un de ses champs.

Il n'est pas forcément évident de lire immédiatement le code que m.message_map[10] peut, par exemple, créer un sous-message. C'est pourquoi nous fournissons également une méthode get_or_create() qui fait la même chose, mais dont le nom rend la création de message plus explicite :

# Equivalent to:
#   m.message_map[10]
# but more explicit that the statement might be creating a new
# empty message in the map.
m.message_map.get_or_create(10)

Énumérations

En Python, les énumérations ne sont que des entiers. Un ensemble de constantes d'intégration correspond aux valeurs définies de l'énumération. Exemple :

message Foo {
  enum SomeEnum {
    VALUE_A = 0;
    VALUE_B = 5;
    VALUE_C = 1234;
  }
  optional SomeEnum bar = 1;
}

Les constantes VALUE_A, VALUE_B et VALUE_C sont définies avec les valeurs 0, 5 et 1 234, respectivement. Vous pouvez accéder à SomeEnum si vous le souhaitez. Si une énumération est définie dans le champ d'application externe, les valeurs sont des constantes de module. Si elles sont définies dans un message (comme ci-dessus), elles deviennent des membres statiques de cette classe de message.

Par exemple, dans un proto, vous pouvez accéder aux valeurs des trois manières suivantes pour l'énumération suivante:

enum SomeEnum {
    VALUE_A = 0;
    VALUE_B = 5;
    VALUE_C = 1234;
}
value_a = myproto_pb2.SomeEnum.VALUE_A
# or
myproto_pb2.VALUE_A
# or
myproto_pb2.SomeEnum.Value('VALUE_A')

Un champ d'énumération fonctionne comme un champ scalaire.

foo = Foo()
foo.bar = Foo.VALUE_A
assert foo.bar == 0
assert foo.bar == Foo.VALUE_A

Si le nom de l'énumération (ou une valeur d'énumération) est un mot clé Python, son objet (ou la propriété de la valeur d'énumération) ne sera accessible que via getattr(), comme décrit dans la section Noms en conflit avec des mots clés Python.

Les valeurs que vous pouvez définir dans une énumération dépendent de votre version de Protocol Buffers:

  • Dans proto2, une énumération ne peut pas contenir de valeur numérique autre que celles définies pour le type d'énumération. Si vous attribuez une valeur qui ne figure pas dans l'énumération, le code généré génère une exception.
  • proto3 utilise une sémantique d'énumération ouverte: les champs enum peuvent contenir n'importe quelle valeur int32.

Les énumérations ont plusieurs méthodes utilitaires pour obtenir les noms de champs à partir de valeurs et inversement, des listes de champs, etc. Elles sont définies dans enum_type_wrapper.EnumTypeWrapper (la classe de base pour les classes d'énumération générées). Par exemple, si vous disposez de l'énumération autonome suivante dans myproto.proto :

enum SomeEnum {
    VALUE_A = 0;
    VALUE_B = 5;
    VALUE_C = 1234;
}

Vous pouvez procéder comme suit :

self.assertEqual('VALUE_A', myproto_pb2.SomeEnum.Name(myproto_pb2.VALUE_A))
self.assertEqual(5, myproto_pb2.SomeEnum.Value('VALUE_B'))
Pour une énumération déclarée dans un message de protocole (par exemple, Foo), la syntaxe est similaire :
self.assertEqual('VALUE_A', myproto_pb2.Foo.SomeEnum.Name(myproto_pb2.Foo.VALUE_A))
self.assertEqual(5, myproto_pb2.Foo.SomeEnum.Value('VALUE_B'))
Si plusieurs constantes d'énumération ont la même valeur (alias), la première constante définie est renvoyée.
enum SomeEnum {
    option allow_alias = true;
    VALUE_A = 0;
    VALUE_B = 5;
    VALUE_C = 1234;
    VALUE_B_ALIAS = 5;
}
Dans l'exemple ci-dessus, myproto_pb2.SomeEnum.Name(5) renvoie "VALUE_B".

Un

Message reçu avec l'un des éléments suivants :

message Foo {
  oneof test_oneof {
     string name = 1;
     int32 serial_number = 2;
  }
}

La classe Python correspondant à Foo aura des propriétés appelées name et serial_number, tout comme les champs standards. Toutefois, contrairement aux champs standards, un seul des champs d'un champ peut être défini à la fois, ce qui est assuré par l'environnement d'exécution. Par exemple,

message = Foo()
message.name = "Bender"
assert message.HasField("name")
message.serial_number = 2716057
assert message.HasField("serial_number")
assert not message.HasField("name")
 :

La classe de message dispose également d'une méthode WhichOneof qui vous permet de savoir quel champ (le cas échéant) a été défini. Cette méthode renvoie le nom du champ défini, ou None si rien n'a été défini :

assert message.WhichOneof("test_oneof") is None
message.name = "Bender"
assert message.WhichOneof("test_oneof") == "name"

HasField et ClearField acceptent également l'un des noms en plus des noms de champs :

assert not message.HasField("test_oneof")
message.name = "Bender"
assert message.HasField("test_oneof")
message.serial_number = 2716057
assert message.HasField("test_oneof")
message.ClearField("test_oneof")
assert not message.HasField("test_oneof")
assert not message.HasField("serial_number")

Notez que si vous appelez ClearField sur l'un de ces champs, le champ actuellement défini est effacé.

Noms en conflit avec des mots clés Python

Si le nom d'un message, champ, énumération ou valeur d'énumération est un mot clé Python, le nom de la classe ou de la propriété correspondante sera le même, mais vous ne pourrez y accéder qu'à l'aide des fonctions intégrées getattr() et setattr() de Python, et non via la syntaxe de référence d'attribut normale de Python (l'opérateur point).

Par exemple, si vous avez la définition .proto suivante :

message Baz {
  optional int32 from = 1
  repeated int32 in = 2;
}

Vous devez accéder à ces champs comme suit :

baz = Baz()
setattr(baz, "from", 99)
assert getattr(baz, "from") == 99
getattr(baz, "in").append(42)
assert getattr(baz, "in") == [42]

En revanche, si vous utilisez la syntaxe obj.attr pour accéder à ces champs, Python génère des erreurs de syntaxe lors de l'analyse de votre code :

# WRONG!
baz.in  # SyntaxError: invalid syntax
baz.from  # SyntaxError: invalid syntax

Extensions (proto2 uniquement)

Message reçu avec une plage d'extensions :

message Foo {
  extensions 100 to 199;
}

La classe Python correspondant à Foo comportera un membre appelé Extensions, qui est un identifiant d'extension de dictionnaire mappant ses valeurs actuelles.

Selon une définition d'extension :

extend Foo {
  optional int32 bar = 123;
}

Le compilateur de tampon de protocole génère un "identifiant d'extension" appelé bar. L'identifiant agit comme une clé du dictionnaire Extensions. La recherche d'une valeur dans ce dictionnaire est exactement la même que si vous accédiez à un champ normal du même type. Ainsi, avec l'exemple ci-dessus, vous pouvez faire :

foo = Foo()
foo.Extensions[proto_file_pb2.bar] = 2
assert foo.Extensions[proto_file_pb2.bar] == 2

Notez que vous devez spécifier la constante de l'identifiant d'extension, et pas seulement un nom de chaîne: en effet, il est possible de spécifier plusieurs extensions portant le même nom dans des champs d'application différents.

Comme pour les champs normaux, Extensions[...] renvoie un objet message pour les messages au singulier et une séquence pour les champs répétés.

Les méthodes HasField() et ClearField() de l'interface Message ne fonctionnent pas avec les extensions. Vous devez utiliser HasExtension() et ClearExtension() à la place. Pour utiliser les méthodes HasExtension() et ClearExtension(), transmettez field_descriptor à l'extension dont vous souhaitez vérifier l'existence.

Services

Si le fichier .proto contient la ligne suivante:

option py_generic_services = true;

Le compilateur de tampon de protocole générera ensuite du code en fonction des définitions de service trouvées dans le fichier, comme décrit dans cette section. Cependant, le code généré peut ne pas être souhaitable, car il n'est lié à aucun système RPC particulier et nécessite donc plus de niveaux d'indirection que le code personnalisé pour un seul système. Si vous NE souhaitez PAS que ce code soit généré, ajoutez la ligne suivante au fichier:

option py_generic_services = false;

Si aucune de ces lignes n'est indiquée, l'option par défaut est false, car les services génériques sont obsolètes. Notez qu'avant la version 2.4.0, l'option est définie par défaut sur true.

Les systèmes RPC basés sur les définitions de service en .proto doivent fournir des plug-ins permettant de générer un code adapté au système. Ces plug-ins sont susceptibles d'exiger que les services abstraits soient désactivés pour pouvoir générer leurs propres classes du même nom. Les plug-ins sont nouveaux dans la version 2.3.0 (janvier 2010).

Le reste de cette section décrit ce que le compilateur de tampon de protocole génère lorsque les services abstraits sont activés.

Interface

Selon une définition de service :

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

Le compilateur de tampon de protocole générera une classe Foo pour représenter ce service. Foo comporte une méthode pour chaque méthode définie dans la définition de service. Dans ce cas, la méthode Bar est définie comme suit :

def Bar(self, rpc_controller, request, done)

Les paramètres sont équivalents aux paramètres de Service.CallMethod(), sauf que l'argument method_descriptor est implicite.

Les méthodes générées sont destinées à être remplacées par les sous-classes. Les implémentations par défaut appellent simplement controller.SetFailed() avec un message d'erreur indiquant que la méthode n'est pas mise en œuvre, puis appelle le rappel done. Lorsque vous implémentez votre propre service, vous devez sous-classer ce service généré et implémenter ses méthodes en conséquence.

Foo sous-classe l'interface Service. Le compilateur de tampon de protocole génère automatiquement des implémentations des méthodes de Service comme suit:

  • GetDescriptor : renvoie la valeur ServiceDescriptor du service.
  • CallMethod : détermine quelle méthode est appelée en fonction du descripteur de méthode fourni et l'appelle directement.
  • GetRequestClass et GetResponseClass: affiche la classe de la requête ou de la réponse du type approprié pour la méthode donnée.

Stub

Le compilateur de tampon de protocole génère également une implémentation « bouchon » de chaque interface de service, qui est utilisée par les clients souhaitant envoyer des requêtes aux serveurs mettant en œuvre le service. Pour le service Foo (ci-dessus), l'implémentation de bouchon Foo_Stub sera définie.

Foo_Stub est une sous-classe de Foo. Son constructeur utilise un RpcChannel comme paramètre. La simulation implémente ensuite chacune des méthodes du service en appelant la méthode CallMethod() du canal.

La bibliothèque de tampons de protocole n'inclut pas de mise en œuvre RPC. Toutefois, elle inclut tous les outils dont vous avez besoin pour relier une classe de service générée à l'implémentation RPC arbitraire de votre choix. Il vous suffit de fournir des implémentations de RpcChannel et RpcController.

Points d'insertion des plug-ins

Les plug-ins de génération de code qui souhaitent étendre la sortie du générateur de code Python peuvent insérer du code des types suivants en utilisant les noms de point d'insertion donnés.

  • imports : importer des instructions.
  • module_scope: déclarations de niveau supérieur.

Ne générez pas de code reposant sur des membres de classe privée déclarés par le générateur de code standard, car ces détails d'implémentation peuvent changer dans les futures versions de Protocol Buffers.

Partager des messages entre Python et C++

Avant la version 4.21.0 de l'API Python Protobuf, les applications Python pouvaient partager des messages avec C++ à l'aide d'une extension native. À partir de la version 4.21.0 de l'API, l'installation par défaut n'est pas compatible avec le partage de messages entre Python et C++. Pour activer cette fonctionnalité lorsque vous utilisez les versions 4.x et ultérieures de l'API Python Protobuf, définissez la variable d'environnement PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp et assurez-vous que l'extension Python/C++ est installée.