Cách tốt nhất của Pythonic để cung cấp các biến cấu hình toàn cục trong config.py? [đóng cửa]


98

Trong nhiệm vụ bất tận của mình về những thứ đơn giản quá phức tạp, tôi đang nghiên cứu cách 'Pythonic' nhất để cung cấp các biến cấu hình toàn cục bên trong ' config.py ' điển hình được tìm thấy trong các gói trứng Python.

Cách truyền thống (aah, good ol ' #define !) Như sau:

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

Do đó, các biến toàn cục được nhập theo một trong những cách sau:

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

hoặc là:

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

Nó có ý nghĩa, nhưng đôi khi có thể hơi lộn xộn, đặc biệt là khi bạn đang cố nhớ tên của một số biến nhất định. Bên cạnh đó, việc cung cấp một đối tượng 'cấu hình' , với các biến là thuộc tính , có thể linh hoạt hơn. Vì vậy, dẫn đầu từ tệp bpython config.py, tôi đã nghĩ ra:

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)])

và 'config.py' nhập lớp và đọc như sau:

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'

và được sử dụng theo cách này:

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))

Đây có vẻ là một cách dễ đọc, biểu cảm và linh hoạt hơn để lưu trữ và tìm nạp các biến toàn cục bên trong một gói.

Ý tưởng tuyệt vời nhất bao giờ hết? Phương pháp tốt nhất để đối phó với những tình huống này là gì? Là những gì bạn cách lưu trữ và lấy tên toàn cầu và các biến bên trong gói của bạn?


3
Bạn đã đưa ra một quyết định ở đây có thể tốt hoặc có thể không tốt. Bản thân cấu hình có thể được lưu trữ theo nhiều cách khác nhau, như JSON, XML, các ngữ pháp khác nhau cho * nixes và Windows, v.v. Tùy thuộc vào người viết tệp cấu hình (công cụ, con người, nền tảng gì?) Các ngữ pháp khác nhau có thể phù hợp hơn. Thông thường, có thể không phải là ý kiến ​​hay nếu để tệp cấu hình được viết bằng cùng ngôn ngữ mà bạn sử dụng cho chương trình của mình, vì nó cung cấp quá nhiều quyền lực cho người dùng (có thể là chính bạn, nhưng chính bạn có thể không nhớ mọi thứ có thể đi sai một số tháng trước).
erikbwork

4
Thường thì tôi kết thúc bằng việc viết một tệp cấu hình JSON. Nó có thể được đọc thành cấu trúc python một cách dễ dàng và cũng có thể được tạo bởi một công cụ. Nó có vẻ linh hoạt nhất và chi phí duy nhất là một số niềng răng có thể gây khó chịu cho người dùng. Tôi chưa bao giờ viết một Egg. Có lẽ đó là cách tiêu chuẩn. Trong trường hợp đó, chỉ cần bỏ qua nhận xét của tôi ở trên.
erikbwork

1
Bạn có thể sử dụng "vars (self)" thay vì "self .__ dict __. Key ()"
Karlisson.

1
Có thể có bản sao của Phương pháp hay nhất sử dụng tệp cài đặt trong Python là gì? Họ trả lời "Nhiều cách có thể thực hiện được và đã tồn tại một chuỗi có bánh xe. Config.py là tốt trừ khi bạn quan tâm đến bảo mật."
Nikana Reklawyks

Tôi đã kết thúc sử dụng python-box, hãy xem câu trả lời
phát triển

Câu trả lời:


5

Tôi đã làm điều đó một lần. Cuối cùng, tôi thấy basicconfig.py đơn giản hóa của mình phù hợp với nhu cầu của tôi. Bạn có thể chuyển vào một không gian tên với các đối tượng khác để nó tham chiếu nếu bạn cần. Bạn cũng có thể chuyển thêm các giá trị mặc định khác từ mã của mình. Nó cũng ánh xạ thuộc tính và ánh xạ cú pháp kiểu đến cùng một đối tượng cấu hình.


6
Các basicconfig.pytập tin được gọi dường như đã chuyển đến github.com/kdart/pycopia/blob/master/core/pycopia/...
Paul M Furley

Tôi biết điều này đã được vài năm tuổi, nhưng tôi là người mới bắt đầu và tôi nghĩ rằng tệp cấu hình này về cơ bản là thứ tôi đang tìm kiếm (có thể quá nâng cao) và tôi muốn hiểu rõ hơn về nó. Tôi có chỉ chuyển khởi tạo ConfigHolderbằng một lệnh cấu hình mà tôi muốn đặt và chuyển giữa các mô-đun không?
Jinx

@Jinx Tại thời điểm này, tôi sẽ sử dụng (và hiện đang sử dụng) tệp YAML và PyYAML để cấu hình. Tôi cũng sử dụng mô-đun của bên thứ ba được gọi confitvà nó hỗ trợ hợp nhất nhiều nguồn. Nó là một phần của mô-đun devtest.config mới .
Keith

56

Bạn chỉ cần sử dụng các loại tích hợp sẵn như thế này:

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

Bạn sẽ truy cập các giá trị như sau:

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

Nếu bạn sẵn sàng hy sinh tiềm năng để tính toán các biểu thức bên trong cây cấu hình của mình, bạn có thể sử dụng YAML và kết thúc bằng một tệp cấu hình dễ đọc hơn như sau:

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

và sử dụng thư viện như PyYAML để phân tích cú pháp thông thường và truy cập tệp cấu hình


Nhưng thông thường bạn muốn có các tệp cấu hình khác nhau và do đó không có bất kỳ dữ liệu cấu hình nào bên trong mã của bạn. Vì vậy, ´config´ sẽ là một tệp JSON / YAML bên ngoài mà bạn phải tải từ đĩa mỗi khi bạn muốn truy cập nó, trong mọi lớp đơn lẻ. Tôi tin rằng câu hỏi là "tải một lần" và có quyền truy cập giống như toàn cầu vào dữ liệu đã tải. Bạn sẽ làm điều đó như thế nào với giải pháp mà bạn đề xuất?
masi

3
nếu chỉ muốn một cái gì đó tồn tại để giữ cho dữ liệu trong bộ nhớ ^^
cinatic

16

Tôi thích giải pháp này cho các ứng dụng nhỏ :

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")

Và cách sử dụng sau đó là:

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

.. bạn nên thích nó vì:

  • sử dụng các biến lớp (không có đối tượng để truyền xung quanh / không yêu cầu singleton),
  • sử dụng các kiểu tích hợp được đóng gói và trông giống như (là) một phương thức gọi App,
  • có quyền kiểm soát cấu hình cá nhân không thay đổi , globals có thể thay đổi là loại tồi tệ nhất của globals .
  • thúc đẩy khả năng truy cập / đọc thông thường và được đặt tên tốt trong mã nguồn của bạn
  • là một lớp đơn giản nhưng thực thi quyền truy cập có cấu trúc , một giải pháp thay thế là sử dụng @property, nhưng yêu cầu nhiều mã xử lý biến hơn cho mỗi mục và dựa trên đối tượng.
  • yêu cầu các thay đổi tối thiểu để thêm các mục cấu hình mới và thiết lập khả năng thay đổi của nó.

- Chỉnh sửa-- : Đối với các ứng dụng lớn, lưu trữ các giá trị trong tệp YAML (tức là thuộc tính) và đọc dữ liệu đó dưới dạng dữ liệu bất biến là một cách tiếp cận tốt hơn (tức là câu trả lời của blubb / ohaal ). Đối với các ứng dụng nhỏ, giải pháp trên đơn giản hơn.


9

Làm thế nào về việc sử dụng các lớp học?

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

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306

8

Tương tự như câu trả lời của blubb. Tôi đề nghị xây dựng chúng bằng các hàm lambda để giảm mã. Như thế này:

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

Tuy nhiên, điều này có vẻ như bạn có thể muốn tạo một lớp học.

Hoặc, như MarkM đã lưu ý, bạn có thể sử dụng 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

3
passlà một tên biến không may, vì nó cũng là một từ khóa.
Thomas Schreiter

Ồ đúng rồi ... Tôi chỉ tập hợp lại ví dụ ngu ngốc này. Tôi sẽ thay đổi tên
Cory-G

Đối với kiểu tiếp cận này, bạn có thể xem xét một lớp thay vì mkDictlambda. Nếu chúng tôi gọi lớp Usercủa mình, các khóa từ điển "cấu hình" của bạn sẽ được khởi tạo giống như vậy {'st3v3': User('password','blonde','Steve Booker')}. Khi "người sử dụng" của bạn đang ở trong một userbiến, bạn có thể truy cập vào thuộc tính của nó như user.hairvv
Andrew Palmer

Nếu bạn thích phong cách này, bạn cũng có thể chọn sử dụng collection.nametuple . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM

7

Một biến thể nhỏ về ý tưởng của Husky mà tôi sử dụng. Tạo một tệp có tên 'hình cầu' (hoặc bất cứ thứ gì bạn thích) và sau đó xác định nhiều lớp trong đó, chẳng hạn như:

#globals.py

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

class runtime :
    debug = False
    output = 'stdio'

Sau đó, nếu bạn có hai tệp mã c1.py và c2.py, cả hai đều có thể có ở trên cùng

import globals as gl

Giờ đây, tất cả mã có thể truy cập và đặt các giá trị, chẳng hạn như:

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

Mọi người quên các lớp tồn tại, ngay cả khi không có đối tượng nào được khởi tạo là thành viên của lớp đó. Và các biến trong một lớp không đứng trước 'self.' được chia sẻ trên tất cả các phiên bản của lớp, ngay cả khi không có phiên bản nào. Khi 'gỡ lỗi' được thay đổi bởi bất kỳ mã nào, tất cả các mã khác sẽ thấy thay đổi.

Bằng cách nhập nó dưới dạng gl, bạn có thể có nhiều tệp và biến như vậy cho phép bạn truy cập và đặt giá trị trên các tệp mã, hàm, v.v., nhưng không có nguy cơ xung đột không gian tên.

Điều này thiếu một số kiểm tra lỗi thông minh của các phương pháp khác, nhưng đơn giản và dễ làm theo.


1
Nó bị nhầm lẫn khi đặt tên cho một mô-đun globals, vì đó là một hàm tích hợp trả về một mệnh đề với mọi ký hiệu trong phạm vi toàn cầu hiện tại. Ngoài ra, PEP8 khuyến nghị CamelCase (với tất cả các chữ hoa trong các từ viết tắt) cho các lớp (tức là DBInfo) và chữ hoa với dấu gạch dưới cho cái gọi là hằng số (tức là DEBUG).
Nuno André

1
Cảm ơn @ NunoAndré đã nhận xét, cho đến khi tôi đọc nó, tôi đã nghĩ rằng câu trả lời này có gì đó kỳ lạ globals, tác giả nên đổi tên
oglop

Cách tiếp cận này là mục tiêu của tôi. Tuy nhiên, tôi thấy có rất nhiều cách tiếp cận mà mọi người nói là "tốt nhất". Bạn có thể nêu một số thiếu sót khi triển khai config.py như thế này không?
Yash Nag

5

Thành thật mà nói, có lẽ chúng ta nên cân nhắc sử dụng thư viện được duy trì bởi Python Software Foundation :

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

Ví dụ về cấu hình: (định dạng ini, nhưng có sẵn JSON)

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

[bitbucket.org]
User = hg

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

Ví dụ về mã:

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

Làm cho nó có thể truy cập toàn cầu:

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)
 ....

Nhược điểm:

  • Trạng thái có thể thay đổi toàn cầu không được kiểm soát .

Sẽ không hữu ích khi sử dụng tệp .ini nếu bạn cần áp dụng câu lệnh if trong các tệp khác của mình để thay đổi cấu hình. Sẽ tốt hơn nếu sử dụng config.py thay thế, nhưng nếu các giá trị không thay đổi và bạn chỉ cần gọi và sử dụng nó, tôi đồng ý với việc sử dụng tệp of.ini.
Kourosh

3

vui lòng kiểm tra hệ thống cấu hình IPython, được triển khai qua traitlets để biết việc thực thi kiểu bạn đang thực hiện theo cách thủ công.

Cắt và dán ở đây để tuân thủ các nguyên tắc SO để không chỉ bỏ liên kết khi nội dung của liên kết thay đổi theo thời gian.

tài liệu về đặc điểm

Dưới đây là các yêu cầu chính mà chúng tôi muốn hệ thống cấu hình của mình có:

Hỗ trợ thông tin cấu hình phân cấp.

Tích hợp đầy đủ với trình phân tích cú pháp tùy chọn dòng lệnh. Thông thường, bạn muốn đọc tệp cấu hình, nhưng sau đó ghi đè một số giá trị bằng các tùy chọn dòng lệnh. Hệ thống cấu hình của chúng tôi tự động hóa quá trình này và cho phép mỗi tùy chọn dòng lệnh được liên kết với một thuộc tính cụ thể trong cấu hình phân cấp mà nó sẽ ghi đè.

Các tệp cấu hình tự là mã Python hợp lệ. Điều này đạt được nhiều điều. Đầu tiên, có thể đặt logic trong các tệp cấu hình của bạn để đặt các thuộc tính dựa trên hệ điều hành, thiết lập mạng, phiên bản Python, v.v. Thứ hai, Python có cú pháp siêu đơn giản để truy cập cấu trúc dữ liệu phân cấp, cụ thể là truy cập thuộc tính thông thường (Foo. Thanh.Bam.name). Thứ ba, sử dụng Python giúp người dùng dễ dàng nhập các thuộc tính cấu hình từ tệp cấu hình này sang tệp cấu hình khác. Thứ tư, mặc dù Python được nhập động, nhưng nó có các kiểu có thể được kiểm tra trong thời gian chạy. Do đó, 1 trong một tệp cấu hình là số nguyên '1', trong khi '1' là một chuỗi.

Một phương pháp hoàn toàn tự động để lấy thông tin cấu hình đến các lớp cần nó trong thời gian chạy. Viết mã đi qua một hệ thống phân cấp cấu hình để trích xuất một thuộc tính cụ thể là rất khó. Khi bạn có thông tin cấu hình phức tạp với hàng trăm thuộc tính, điều này khiến bạn muốn khóc.

Nhập kiểm tra và xác thực không yêu cầu toàn bộ cấu hình phân cấp được chỉ định tĩnh trước thời gian chạy. Python là một ngôn ngữ rất năng động và không phải lúc nào bạn cũng biết mọi thứ cần được cấu hình khi một chương trình khởi động.

Để làm rõ điều này, về cơ bản họ xác định 3 lớp đối tượng và mối quan hệ của chúng với nhau:

1) Cấu hình - về cơ bản là một ChainMap / chính tả cơ bản với một số cải tiến để hợp nhất.

2) Có thể cấu hình - lớp cơ sở để phân lớp tất cả những thứ bạn muốn cấu hình.

3) Ứng dụng - đối tượng được khởi tạo để thực hiện một chức năng ứng dụng cụ thể hoặc ứng dụng chính của bạn cho phần mềm mục đích duy nhất.

Theo cách nói của họ:

Ứng dụng: ứng dụng

Ứng dụng là một quá trình thực hiện một công việc cụ thể. Ứng dụng rõ ràng nhất là chương trình dòng lệnh ipython. Mỗi ứng dụng đọc một hoặc nhiều tệp cấu hình và một tập hợp các tùy chọn dòng lệnh, sau đó tạo ra một đối tượng cấu hình chính cho ứng dụng. Đối tượng cấu hình này sau đó được chuyển cho các đối tượng có thể cấu hình mà ứng dụng tạo ra. Các đối tượng có thể cấu hình này thực hiện logic thực tế của ứng dụng và biết cách tự cấu hình cho đối tượng cấu hình.

Các ứng dụng luôn có một thuộc tính log là Logger được cấu hình. Điều này cho phép cấu hình ghi nhật ký tập trung cho mỗi ứng dụng. Có thể cấu hình: Có thể cấu hình

Có thể định cấu hình là một lớp Python thông thường đóng vai trò là lớp cơ sở cho tất cả các lớp chính trong một ứng dụng. Lớp cơ sở có thể cấu hình nhẹ và chỉ thực hiện một việc.

Có thể cấu hình này là một lớp con của HasTraits biết cách tự cấu hình. Các đặc điểm cấp độ lớp với cấu hình siêu dữ liệu = True trở thành giá trị có thể được cấu hình từ dòng lệnh và tệp cấu hình.

Các nhà phát triển tạo các lớp con có thể định cấu hình thực hiện tất cả các logic trong ứng dụng. Mỗi lớp con này có thông tin cấu hình riêng để kiểm soát cách tạo các thể hiện.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.