Noções básicas do buffer de protocolo (versão Python)

Noções básicas do buffer de protocolo (versão Python)

Traduzido de: https://developers.google.com/protocol-buffers/docs/pythontutorial

A necessidade de usar o buffer de protocolo é dividida principalmente nas três etapas a seguir:

  • Defina o arquivo .proto por meio do formato de mensagem
  • Use o compilador de buffer de protocolo para gerar arquivos .py (semelhantes a outras linguagens)
  • Ler e escrever mensagens usando a API de buffer de protocolo Python

Este artigo é apenas uma introdução básica para informações detalhadas:

Vantagens do buffer de protocolo

  • Decapagem embutida do Python: não pode responder bem à evolução do esquema e não pode compartilhar dados com programas C ++ ou Java também.
  • Você pode personalizar uma forma de converter em string. Embora exija codificação e análise de código, a análise requer um certo custo de tempo. Mas devido à sua simplicidade e flexibilidade, é particularmente adequado para processar dados muito simples.
  • XML: XML é usado devido à sua legibilidade e compatibilidade com vários idiomas. No entanto, ele ocupa muito espaço de armazenamento, e a codificação e decodificação também requerem muito custo de tempo.Finalmente, percorrer a árvore XML DOM é muito mais complicado do que percorrer uma determinada classe.

Definir o formato do protocolo

Aqui está um exemplo de um catálogo de endereços para ilustração. O catálogo de endereços contém vários contatos e cada contato conterá um nome, ID, endereço de e-mail e número de contato. O arquivo .proto de tal catálogo de endereços é definido como segue. O exemplo aqui é baseado em proto2 e a versão mais recente é proto3.

// addressbook.proto
syntax = "proto2";

package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

O arquivo .proto primeiro especifica o nome do pacote, o que ajuda a resolver possíveis conflitos de nomenclatura em diferentes projetos. Mas porque Python gerencia nomes de pacotes por meio de caminhos de arquivo. Portanto, esta parte do conteúdo funciona apenas em linguagens não Python.

A seguir está a definição de mensagem. mensagem fornece muitos tipos de dados simples padrão, como bool, int32, float, double, string. Claro, você também pode definir estruturas de dados complexas.Por exemplo, no programa acima, Person inclui PhoneNumber; ao mesmo tempo, AddressBook inclui Person. Você também pode enumerar os valores de variáveis ​​predefinidas.

O '= 1', '= 2' após a variável fornece um rótulo exclusivo para cada variável. Ao mesmo tempo, uma vez que o tamanho do código usado por 1-15 é um byte menor que o de 16 ou mais, 1-15 são normalmente reservados para os campos obrigatórios e repetidos. Ao mesmo tempo, uma vez que cada elemento no campo repetido precisa ser recodificado, essa otimização é muito adequada para o campo repetido.

Ao mesmo tempo, no exemplo acima, pode-se descobrir que cada campo requer um modificador da seguinte forma:

  • obrigatório: este campo deve ser atribuído. Se não for atribuído, uma exceção será retornada se essa mensagem for serializada e não conseguirá analisar uma mensagem não inicializada. Além disso, não há diferença substancial entre obrigatório e opcional.
  • opcional: a atribuição deste campo é opcional. Se não for atribuída, a atribuição padrão será usada. Para classes simples, podemos nos especificar, como o tipo no exemplo acima. Ao mesmo tempo, cada classe simples fornece um valor inicial padrão do sistema, como tipo numérico (0), string (string vazia), variável lógica (false). Para mensagens integradas, o valor padrão é sempre a instância ou protótipo padrão da mensagem e seu domínio não é definido. Se você visitar um campo que não está explicitamente atribuído, você sempre obterá seu valor padrão.
  • repetido: pode ser repetido qualquer número de vezes (incluindo 0). A seqüência de valores repetidos será armazenada no buffer do protocolo. repetido pode ser entendido como uma matriz dinâmica.

Deve ser lembrado aqui que required é permanente: Portanto, você deve ter muito cuidado ao definir o domínio como exigido, pois se você precisar modificá-lo posteriormente, os usuários do programa antigo irão pensar que a mensagem não está corretamente atribuída e reportar um erro. Ao mesmo tempo, o Google também discutiu internamente se é necessário reter necessário. Parece que o buffer de protocolo 3 não especifica mais explicitamente esses modificadores.

Buffers de protocolo de compilação

Você pode  instalar o compilador no seguinte site   https://developers.google.com/protocol-buffers/docs/downloads e seguir as instruções.

protoc -I=$SRC_DIR --python_out=$DST_DIR $SRC_DIR/addressbook.proto

Descrição do parâmetro:

  • $ SRC_DIR: o código do aplicativo, se não for fornecido, o caminho atual será usado
  • $ DST_DIR: Onde colocar o código gerado. Como este exemplo é baseado em Python, use --python_out, semelhante a outras linguagens.
  • Finalmente é o caminho para o arquivo .proto

Este comando irá gerar um arquivo addressbook_pb2.py.

API de buffer de protocolo

Para Java e C ++, quando o código do buffer de protocolo é gerado, o código para acessar os dados será fornecido diretamente, mas para Python, não será fornecido. addressbook_pb2.py contém o seguinte:

class Person(message.Message):
  __metaclass__ = reflection.GeneratedProtocolMessageType

  class PhoneNumber(message.Message):
    __metaclass__ = reflection.GeneratedProtocolMessageType
    DESCRIPTOR = _PERSON_PHONENUMBER
  DESCRIPTOR = _PERSON

class AddressBook(message.Message):
  __metaclass__ = reflection.GeneratedProtocolMessageType
  DESCRIPTOR = _ADDRESSBOOK

As informações importantes em cada classe são __metaclass__ = reflection.GeneratedProtocolMessageType. O modo como as metaclasses específicas do Python funcionam pode estar além do escopo deste artigo, mas você pode entendê-lo como um modelo que precisa ser usado ao criar uma classe. No momento do carregamento, as metaclasses GeneratedProtocolMessageType usam um descritor específico para gerar todos os métodos Python de que você precisa e adicioná-los às classes correspondentes. Então, você pode usar a classe totalmente preenchida em seu código.

Por exemplo, no exemplo usado anteriormente neste artigo, podemos usar a classe Person definida na mensagem da seguinte maneira.

import addressbook_pb2
person = addressbook_pb2.Person()
person.id = 1234
person.name = "John Doe"
person.email = "[email protected]"
phone = person.phones.add()
phone.number = "555-4321"
phone.type = addressbook_pb2.Person.HOME

O processo de atribuição acima não é uma simples adição de membros na classe. Se você atribuir uma variável que não existe, AttributeError será retornado, e se você atribuir um tipo incorreto, TypeError será retornado. Se você acessar uma variável antes de atribuí-la, seu valor padrão será retornado.

person.no_such_field = 1  # raises AttributeError
person.id = "1234"        # raises TypeError

Classe de enumeração

A classe de enumeração é expandida por uma metaclasse em um conjunto de constantes simbólicas com valores inteiros. Por exemplo, no exemplo acima, addressbook_pb2.Person.WORK tem o valor 2.

Método de mensagem padrão

Cada mensagem também contém certas funções que permitem verificar ou manipular a mensagem inteira.

  • IsInitialized (): Verifique se todos os necessários estão atribuídos
  • __str __ (): retorna uma representação de mensagem legível, geralmente usada na depuração de str (mensagem) e impressão (mensagem)

Análise e serialização

  • SerializeToString (): serializa uma mensagem e retorna uma string. Os bytes retornados são binários e não de texto e só podem ser armazenados usando o tipo str
  • ParseFromString (data): analisa a mensagem da string fornecida

Outras funções de análise e serialização podem referir-se a: https://developers.google.com/protocol-buffers/docs/reference/python/google.protobuf.message.Message-class

Deve-se notar que ele é projetado com base em OO: O Buffer de Protocolo é essencialmente um armazenamento burro de dados, que é um pouco semelhante à estrutura da linguagem C, o que mostra que não é muito compatível com o modelo de objeto. Se você deseja adicionar comportamentos mais ricos a uma classe estendida, a melhor maneira é encapsular a classe de buffer de protocolo estendido na classe de aplicativo. Se você reutilizar o buffer de protocolo em outro projeto e não puder controlar o design do arquivo .proto, o encapsulamento também é uma boa maneira. Por meio do encapsulamento, você pode se adaptar melhor ao ambiente específico de aplicativos específicos, como ocultar alguns dados e métodos e abrir algumas funções convenientes. Mas você nunca deve estender a classe por herança. Isso destruirá a estrutura interna e não é uma boa abordagem orientada a objetos.

ler e escrever mensagem

#! /usr/bin/python

import addressbook_pb2
import sys

# This function fills in a Person message based on user input.
def PromptForAddress(person):
  person.id = int(raw_input("Enter person ID number: "))
  person.name = raw_input("Enter name: ")

  email = raw_input("Enter email address (blank for none): ")
  if email != "":
    person.email = email

  while True:
    number = raw_input("Enter a phone number (or leave blank to finish): ")
    if number == "":
      break

    phone_number = person.phones.add()
    phone_number.number = number

    type = raw_input("Is this a mobile, home, or work phone? ")
    if type == "mobile":
      phone_number.type = addressbook_pb2.Person.MOBILE
    elif type == "home":
      phone_number.type = addressbook_pb2.Person.HOME
    elif type == "work":
      phone_number.type = addressbook_pb2.Person.WORK
    else:
      print "Unknown phone type; leaving as default value."

# Main procedure:  Reads the entire address book from a file,
#   adds one person based on user input, then writes it back out to the same
#   file.
if len(sys.argv) != 2:
  print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"
  sys.exit(-1)

address_book = addressbook_pb2.AddressBook()

# Read the existing address book.
try:
  f = open(sys.argv[1], "rb")
  address_book.ParseFromString(f.read())
  f.close()
except IOError:
  print sys.argv[1] + ": Could not open file.  Creating a new one."

# Add an address.
PromptForAddress(address_book.people.add())

# Write the new address book back to disk.
f = open(sys.argv[1], "wb")
f.write(address_book.SerializeToString())
f.close()
#! /usr/bin/python

import addressbook_pb2
import sys

# Iterates though all people in the AddressBook and prints info about them.
def ListPeople(address_book):
  for person in address_book.people:
    print "Person ID:", person.id
    print "  Name:", person.name
    if person.HasField('email'):
      print "  E-mail address:", person.email

    for phone_number in person.phones:
      if phone_number.type == addressbook_pb2.Person.MOBILE:
        print "  Mobile phone #: ",
      elif phone_number.type == addressbook_pb2.Person.HOME:
        print "  Home phone #: ",
      elif phone_number.type == addressbook_pb2.Person.WORK:
        print "  Work phone #: ",
      print phone_number.number

# Main procedure:  Reads the entire address book from a file and prints all
#   the information inside.
if len(sys.argv) != 2:
  print "Usage:", sys.argv[0], "ADDRESS_BOOK_FILE"
  sys.exit(-1)

address_book = addressbook_pb2.AddressBook()

# Read the existing address book.
f = open(sys.argv[1], "rb")
address_book.ParseFromString(f.read())
f.close()

ListPeople(address_book)

Estenda o buffer de protocolo existente

Depois de liberar seu código de buffer de protocolo, inevitavelmente haverá uma situação em que você precisará atualizar o código. Neste momento, se você precisar que o código atualizado ainda seja executado normalmente no código anterior (compatibilidade com versões anteriores), o código atualizado deve Atenda aos seguintes requisitos:

  • Não deve alterar o número do rótulo de qualquer domínio existente (1,2,3, ...)
  • Não deve adicionar ou excluir nenhum campo obrigatório
  • Você pode excluir campos opcionais ou repetidos
  • Você pode adicionar novos campos opcionais ou repetidos, mas deve garantir o uso de novos números de etiqueta. O novo número do rótulo nunca deve ter sido usado, mesmo se o número do rótulo usado pelo domínio excluído não funcionar
  • Existem algumas outras regras, mas você não as encontrará; se precisar, consulte  https://developers.google.com/protocol-buffers/docs/proto#updating

Assim, o código antigo pode ler a nova medida normalmente e simplesmente ignorar quaisquer novos campos. Ao mesmo tempo, o código usará diretamente o valor padrão para o campo opcional excluído e o campo repetido ficará em branco. O novo código também lerá diretamente a mensagem antiga. No entanto, deve-se observar que o novo campo opcional não será fornecido no código antigo, portanto, você deve acessar explicitamente o sinalizador has_ ​​para determinar se ele está atribuído ou especificar o valor padrão usando [default = valor] após o número do sinalizador. Se o valor padrão não for especificado, o valor padrão do sistema será usado. Ao mesmo tempo, para o campo repetido, porque não tem has_ ​​flag, é impossível saber se ele está vazio porque o novo código não foi atribuído ou porque o código antigo não foi definido.

Uso avançado

Você pode  obter instruções mais detalhadas no site a seguir  https://developers.google.com/protocol-buffers/docs/reference/python/ .

Uma das características importantes é a reflexão. Você pode iterar os campos na mensagem e modificar seus valores sem precisar modificar seu código para qualquer tipo de mensagem específico. Uma função muito útil é usar reflexão para realizar a conversão mútua entre o buffer de protocolo e outros formatos de codificação, como XML ou JSON. E um recurso de alto nível é que a reflexão pode ser usada para encontrar diferenças no mesmo tipo de mensagem ou para desenvolver uma série de representações convencionais de mensagens de protocolo, nas quais você pode escrever expressões que correspondam ao conteúdo da mensagem característica. Ao mesmo tempo, você pode usar sua imaginação para usar o buffer de protocolo para resolver mais problemas que possa encontrar.

Para uma introdução detalhada à reflexão, consulte: https://developers.google.com/protocol-buffers/docs/reference/python/google.protobuf.message.Message-class

Acho que você gosta

Origin blog.csdn.net/a40850273/article/details/90482546
Recomendado
Clasificación