A maneira mais Pythônica de fornecer variáveis ​​de configuração globais em config.py? [fechadas]

98

Em minha busca interminável de complicar demais coisas simples, estou pesquisando a maneira mais 'Pythônica' de fornecer variáveis ​​de configuração globais dentro do típico ' config.py ' encontrado em pacotes de ovo Python.

A maneira tradicional (aah, bom e velho #define !) É a seguinte:

MYSQL_PORT = 3306
MYSQL_DATABASE = 'mydb'
MYSQL_DATABASE_TABLES = ['tb_users', 'tb_groups']

Portanto, as variáveis ​​globais são importadas de uma das seguintes maneiras:

from config import *
dbname = MYSQL_DATABASE
for table in MYSQL_DATABASE_TABLES:
    print table

ou:

import config
dbname = config.MYSQL_DATABASE
assert(isinstance(config.MYSQL_PORT, int))

Faz sentido, mas às vezes pode ser um pouco confuso, especialmente quando você está tentando lembrar os nomes de certas variáveis. Além disso, fornecer um objeto de 'configuração' , com variáveis ​​como atributos , pode ser mais flexível. Então, seguindo a orientação do arquivo bpython config.py, eu vim com:

class Struct(object):

    def __init__(self, *args):
        self.__header__ = str(args[0]) if args else None

    def __repr__(self):
        if self.__header__ is None:
             return super(Struct, self).__repr__()
        return self.__header__

    def next(self):
        """ Fake iteration functionality.
        """
        raise StopIteration

    def __iter__(self):
        """ Fake iteration functionality.
        We skip magic attribues and Structs, and return the rest.
        """
        ks = self.__dict__.keys()
        for k in ks:
            if not k.startswith('__') and not isinstance(k, Struct):
                yield getattr(self, k)

    def __len__(self):
        """ Don't count magic attributes or Structs.
        """
        ks = self.__dict__.keys()
        return len([k for k in ks if not k.startswith('__')\
                    and not isinstance(k, Struct)])

e um 'config.py' que importa a classe e lê o seguinte:

from _config import Struct as Section

mysql = Section("MySQL specific configuration")
mysql.user = 'root'
mysql.pass = 'secret'
mysql.host = 'localhost'
mysql.port = 3306
mysql.database = 'mydb'

mysql.tables = Section("Tables for 'mydb'")
mysql.tables.users = 'tb_users'
mysql.tables.groups =  'tb_groups'

e é usado desta forma:

from sqlalchemy import MetaData, Table
import config as CONFIG

assert(isinstance(CONFIG.mysql.port, int))

mdata = MetaData(
    "mysql://%s:%s@%s:%d/%s" % (
         CONFIG.mysql.user,
         CONFIG.mysql.pass,
         CONFIG.mysql.host,
         CONFIG.mysql.port,
         CONFIG.mysql.database,
     )
)

tables = []
for name in CONFIG.mysql.tables:
    tables.append(Table(name, mdata, autoload=True))

O que parece uma forma mais legível, expressiva e flexível de armazenar e buscar variáveis ​​globais dentro de um pacote.

A ideia mais idiota de todos os tempos? Qual é a melhor prática para lidar com essas situações? Qual é a sua maneira de armazenar e buscar nomes globais e variáveis ​​dentro de seu pacote?

Rigel Di Scala
fonte
3
Você já tomou uma decisão aqui que pode ou não ser boa. A configuração em si pode ser armazenada de maneiras diferentes, como JSON, XML, gramáticas diferentes para * nixes e Windows e assim por diante. Dependendo de quem escreve o arquivo de configuração (uma ferramenta, um ser humano, qual experiência?), Gramáticas diferentes podem ser preferíveis. Na maioria das vezes pode não ser uma boa ideia deixar o arquivo de configuração ser escrito na mesma linguagem que você usa para o seu programa, porque dá muito poder ao usuário (o que pode ser você, mas você mesmo pode não se lembrar de tudo o que pode dar errado alguns meses antes).
erikbwork de
4
Freqüentemente, acabo escrevendo um arquivo de configuração JSON. Ele pode ser lido em estruturas python facilmente e também pode ser criado por uma ferramenta. Parece ter a maior flexibilidade e o único custo são alguns aparelhos que podem incomodar o usuário. Porém, nunca escrevi um Egg. Talvez seja a forma padrão. Nesse caso, apenas ignore meu comentário acima.
erikbwork
1
Você pode usar "vars (self)" em vez de "self .__ dict __. Keys ()"
Karlisson
1
Possível duplicata de Qual é a prática recomendada para usar um arquivo de configurações em Python? Eles respondem "Muitas maneiras são possíveis, e já existe um segmento de bicicletas. Config.py é bom, a menos que você se preocupe com a segurança."
Nikana Reklawyks
Acabei usando python-box, veja esta resposta
evoluiu em

Respostas:

5

Eu fiz isso uma vez. Por fim, descobri que meu basicconfig.py simplificado é adequado para minhas necessidades. Você pode passar um namespace com outros objetos para referência, se necessário. Você também pode passar padrões adicionais de seu código. Ele também mapeia atributo e sintaxe de estilo de mapeamento para o mesmo objeto de configuração.

Keith
fonte
6
O basicconfig.pyarquivo referido parece ter sido movido para github.com/kdart/pycopia/blob/master/core/pycopia/…
Paul M Furley
Eu sei que isso tem alguns anos, mas sou um iniciante e acho que esse arquivo de configuração é essencialmente o que estou procurando (talvez muito avançado) e gostaria de entendê-lo melhor. Devo apenas passar a inicialização ConfigHoldercom um dicionário de configurações que gostaria de definir e passar entre os módulos?
Jinx
@Jinx Neste ponto, eu usaria (e estou usando) um arquivo YAML e PyYAML para configuração. Eu também uso um módulo de terceiros chamado confite ele suporta a fusão de múltiplas fontes. Faz parte de um novo módulo devtest.config .
Keith
56

Que tal usar apenas os tipos integrados como este:

config = {
    "mysql": {
        "user": "root",
        "pass": "secret",
        "tables": {
            "users": "tb_users"
        }
        # etc
    }
}

Você acessaria os valores da seguinte maneira:

config["mysql"]["tables"]["users"]

Se você está disposto a sacrificar o potencial de calcular expressões dentro de sua árvore de configuração, pode usar YAML e acabar com um arquivo de configuração mais legível como este:

mysql:
  - user: root
  - pass: secret
  - tables:
    - users: tb_users

e usar uma biblioteca como PyYAML para analisar e acessar convenientemente o arquivo de configuração

blubb
fonte
Mas normalmente você deseja ter arquivos de configuração diferentes e, portanto, não ter nenhum dado de configuração dentro de seu código. Portanto, ´config´ seria um arquivo JSON / YAML externo que você tem que carregar do disco toda vez que quiser acessá-lo, em todas as classes. Acredito que a questão seja "carregar uma vez" e ter acesso global aos dados carregados. Como você faria isso com a solução que sugeriu?
masi
3
se apenas algo existisse para manter os dados na memória ^^
cinático
16

Gosto desta solução para pequenas aplicações :

class App:
  __conf = {
    "username": "",
    "password": "",
    "MYSQL_PORT": 3306,
    "MYSQL_DATABASE": 'mydb',
    "MYSQL_DATABASE_TABLES": ['tb_users', 'tb_groups']
  }
  __setters = ["username", "password"]

  @staticmethod
  def config(name):
    return App.__conf[name]

  @staticmethod
  def set(name, value):
    if name in App.__setters:
      App.__conf[name] = value
    else:
      raise NameError("Name not accepted in set() method")

E então o uso é:

if __name__ == "__main__":
   # from config import App
   App.config("MYSQL_PORT")     # return 3306
   App.set("username", "hi")    # set new username value
   App.config("username")       # return "hi"
   App.set("MYSQL_PORT", "abc") # this raises NameError

.. você deve gostar porque:

  • usa variáveis ​​de classe (nenhum objeto para passar / nenhum singleton necessário),
  • usa tipos integrados encapsulados e parece (é) uma chamada de método App,
  • tem controle sobre a imutabilidade de configuração individual , os globais mutáveis ​​são o pior tipo de globais .
  • promove acesso / legibilidade convencional e bem nomeado em seu código-fonte
  • é uma classe simples, mas impõe acesso estruturado , uma alternativa é usar @property, mas isso requer mais código de manipulação variável por item e é baseado em objeto.
  • requer mudanças mínimas para adicionar novos itens de configuração e definir sua mutabilidade.

--Editar-- : Para aplicativos grandes, armazenar valores em um arquivo YAML (ou seja, propriedades) e lê-lo como dados imutáveis ​​é uma abordagem melhor (ou seja, a resposta de blubb / ohaal ). Para pequenas aplicações, a solução acima é mais simples.

pds
fonte
9

Que tal usar classes?

# config.py
class MYSQL:
    PORT = 3306
    DATABASE = 'mydb'
    DATABASE_TABLES = ['tb_users', 'tb_groups']

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306
Husky
fonte
8

Semelhante à resposta de blubb. Eu sugiro construí-los com funções lambda para reduzir o código. Como isso:

User = lambda passwd, hair, name: {'password':passwd, 'hair':hair, 'name':name}

#Col      Username       Password      Hair Color  Real Name
config = {'st3v3' : User('password',   'blonde',   'Steve Booker'),
          'blubb' : User('12345678',   'black',    'Bubb Ohaal'),
          'suprM' : User('kryptonite', 'black',    'Clark Kent'),
          #...
         }
#...

config['st3v3']['password']  #> password
config['blubb']['hair']      #> black

Mas isso cheira como se você pudesse querer fazer uma aula.

Ou, como MarkM observou, você pode usar namedtuple

from collections import namedtuple
#...

User = namedtuple('User', ['password', 'hair', 'name']}

#Col      Username       Password      Hair Color  Real Name
config = {'st3v3' : User('password',   'blonde',   'Steve Booker'),
          'blubb' : User('12345678',   'black',    'Bubb Ohaal'),
          'suprM' : User('kryptonite', 'black',    'Clark Kent'),
          #...
         }
#...

config['st3v3'].password   #> passwd
config['blubb'].hair       #> black
Cory-G
fonte
3
passé um nome de variável infeliz, pois também é uma palavra-chave.
Thomas Schreiter
Oh sim ... Acabei de reunir este exemplo idiota. Vou mudar o nome
Cory-G
Para esse tipo de abordagem, você pode considerar uma classe em vez de mkDictlambda. Se chamarmos nossa classe User, suas chaves de dicionário "config" seriam inicializadas de forma semelhante {'st3v3': User('password','blonde','Steve Booker')}. Quando seu "usuário" está em uma uservariável, você pode acessar suas propriedades como user.hairetc.
Andrew Palmer
Se você gosta desse estilo, também pode optar por usar Collections.namedtuple . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM
7

Uma pequena variação da ideia de Husky que uso. Faça um arquivo chamado 'globais' (ou o que você quiser) e defina várias classes nele, como:

#globals.py

class dbinfo :      # for database globals
    username = 'abcd'
    password = 'xyz'

class runtime :
    debug = False
    output = 'stdio'

Então, se você tiver dois arquivos de código c1.py e c2.py, ambos podem ter no topo

import globals as gl

Agora todos os códigos podem acessar e definir valores, como:

gl.runtime.debug = False
print(gl.dbinfo.username)

As pessoas esquecem que as classes existem, mesmo que nenhum objeto seja instanciado que seja membro dessa classe. E variáveis ​​em uma classe que não são precedidas por 'self'. são compartilhados por todas as instâncias da classe, mesmo se não houver nenhuma. Depois que 'depurar' for alterado por qualquer código, todos os outros códigos verão a alteração.

Ao importá-lo como gl, você pode ter vários arquivos e variáveis ​​que permitem acessar e definir valores em arquivos de código, funções, etc., mas sem perigo de colisão de namespace.

Isso não tem a verificação de erros inteligente de outras abordagens, mas é simples e fácil de seguir.

eSurfsnake
fonte
1
É desaconselhável nomear um módulo globals, uma vez que é uma função embutida que retorna um dict com cada símbolo no escopo global atual. Além disso, o PEP8 recomenda CamelCase (com todas as letras maiúsculas nas siglas) para as classes (ou seja DBInfo) e maiúsculas com sublinhados para as chamadas constantes (ou seja DEBUG).
Nuno André
1
Obrigado @ NunoAndré pelo comentário, até que o li estava a pensar que esta resposta faz alguma coisa estranha com globals, autor deveria mudar o nome
oglop
Essa abordagem é minha escolha. No entanto, vejo muitas abordagens que as pessoas dizem ser "as melhores". Você pode indicar algumas deficiências na implementação de config.py assim?
Yash Nag
5

Vamos ser honestos, provavelmente devemos considerar o uso de uma biblioteca mantida pela Python Software Foundation :

https://docs.python.org/3/library/configparser.html

Exemplo de configuração: (formato ini, mas JSON disponível)

[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes

[bitbucket.org]
User = hg

[topsecret.server.com]
Port = 50022
ForwardX11 = no

Exemplo de código:

>>> import configparser
>>> config = configparser.ConfigParser()
>>> config.read('example.ini')
>>> config['DEFAULT']['Compression']
'yes'
>>> config['DEFAULT'].getboolean('MyCompression', fallback=True) # get_or_else

Tornando-o acessível globalmente:

import configpaser
class App:
 __conf = None

 @staticmethod
 def config():
  if App.__conf is None:  # Read only once, lazy.
   App.__conf = configparser.ConfigParser()
   App.__conf.read('example.ini')
  return App.__conf

if __name__ == '__main__':
 App.config()['DEFAULT']['MYSQL_PORT']
 # or, better:
 App.config().get(section='DEFAULT', option='MYSQL_PORT', fallback=3306)
 ....

Desvantagens:

  • Estado mutável global não controlado .
pds
fonte
Não é útil usar o arquivo .ini se você precisar aplicar instruções if em seus outros arquivos para alterar a configuração. Seria melhor usar config.py em vez disso, mas se os valores não mudam e você apenas chama e usa, eu concordo com o uso do arquivo.ini.
Kourosh
3

verifique o sistema de configuração IPython, implementado via traitlets para a aplicação de tipo que você está fazendo manualmente.

Recorte e cole aqui para cumprir as diretrizes do SO para não apenas remover links, pois o conteúdo dos links muda com o tempo.

documentação de traitlets

Aqui estão os principais requisitos que queríamos que nosso sistema de configuração tivesse:

Suporte para informações de configuração hierárquica.

Integração total com analisadores de opção de linha de comando. Freqüentemente, você deseja ler um arquivo de configuração, mas então sobrescrever alguns dos valores com opções de linha de comando. Nosso sistema de configuração automatiza esse processo e permite que cada opção de linha de comando seja vinculada a um atributo específico na hierarquia de configuração que ele substituirá.

Arquivos de configuração que são códigos Python válidos. Isso realiza muitas coisas. Primeiro, torna-se possível colocar lógica em seus arquivos de configuração que define atributos com base em seu sistema operacional, configuração de rede, versão do Python, etc. Em segundo lugar, Python tem uma sintaxe super simples para acessar estruturas de dados hierárquicas, ou seja, acesso regular a atributos (Foo. Bar.Bam.name). Terceiro, usar Python torna mais fácil para os usuários importar atributos de configuração de um arquivo de configuração para outro. Quarto, embora o Python seja tipado dinamicamente, ele possui tipos que podem ser verificados em tempo de execução. Assim, um 1 em um arquivo de configuração é o inteiro '1', enquanto um '1' é uma string.

Um método totalmente automatizado para obter as informações de configuração para as classes que precisam delas no tempo de execução. Escrever código que segue uma hierarquia de configuração para extrair um determinado atributo é doloroso. Quando você tem informações de configuração complexas com centenas de atributos, você tem vontade de chorar.

Verificação e validação de tipo que não requerem que toda a hierarquia de configuração seja especificada estaticamente antes do tempo de execução. Python é uma linguagem muito dinâmica e você nem sempre sabe tudo o que precisa ser configurado quando um programa é iniciado.

Para conseguir isso, eles basicamente definem 3 classes de objetos e suas relações entre si:

1) Configuração - basicamente um mapa de cadeia / dicionário básico com alguns aprimoramentos para fusão.

2) Configurável - classe base para criar uma subclasse de todas as coisas que você deseja configurar.

3) Aplicativo - objeto que é instanciado para executar uma função de aplicativo específica, ou seu aplicativo principal para software de propósito único.

Em suas palavras:

Aplicação: Aplicação

Um aplicativo é um processo que realiza um trabalho específico. A aplicação mais óbvia é o programa de linha de comando ipython. Cada aplicativo lê um ou mais arquivos de configuração e um único conjunto de opções de linha de comando e, em seguida, produz um objeto de configuração mestre para o aplicativo. Este objeto de configuração é então passado para os objetos configuráveis ​​que o aplicativo cria. Esses objetos configuráveis ​​implementam a lógica real da aplicação e sabem como se configurar a partir do objeto de configuração.

Os aplicativos sempre têm um atributo de log que é um Logger configurado. Isso permite a configuração de registro centralizado por aplicativo. Configurável: Configurável

Um configurável é uma classe Python regular que serve como uma classe base para todas as classes principais em um aplicativo. A classe base Configurable é leve e faz apenas uma coisa.

Este Configurável é uma subclasse de HasTraits que sabe como se configurar. As características de nível de classe com metadados config = True tornam-se valores que podem ser configurados a partir da linha de comando e dos arquivos de configuração.

Os desenvolvedores criam subclasses configuráveis ​​que implementam toda a lógica do aplicativo. Cada uma dessas subclasses tem suas próprias informações de configuração que controlam como as instâncias são criadas.

jLi
fonte