前言
从Flaskl转到Sanic,在学习过程中遇到一些问题,我将尽力把它说明清楚,预计会形成一个系列文章。
学习Sanic遇到的问题系列之一:app.update_config()
在使用Flask,教程中使用配置类如下:
# config.py
import os
class Config:
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@staticmethod
def init_app(app):
pass
class DevConfig(Config):
DEBUG = True
DB_URL = 'mysql://test:Tes@*.*.*.*:3306/test'
class ProConfig(Config):
DEBUG = False
DB_URL = 'mysql://production:production@*.*.*.*:3306/production'
config = {
'development': DevConfig,
'production': ProConfig,
'default': DevConfig
}
使用的时候如下:
app = Flask(__name__)
app.config.from_object(DevConfig)
不同环境使用不同的配置,一些相同的配置可以放在Config基类中(比如BASE_DIR)。
在Sanic中,app.update_config()也可以传入一个类,但是却出了意外:
app = Sanic(__name__)
app.update_config(Devconfig) # 这时配置中没有父类的BASE_DIR
看看sanic中update_config()的代码,它是通过__dict__来获取属性,这时父类的属性是读不到:
def update_config(self, config: Union[bytes, str, dict, Any]):
# config是bytes,str,Path类型的处理
if isinstance(config, (bytes, str, Path)):
config = load_module_from_file_location(location=config)
# 不管是传入类,还是对象,都是通过__dict__来获取属性,这时其父类的属性是读不到的
if not isinstance(config, dict):
cfg = {}
if not isclass(config):
cfg.update(
{
key: getattr(config, key)
for key in config.__class__.__dict__.keys() # 通过对象的类的__dict__得到类属性
}
)
config = dict(config.__dict__) # 直接使用__dict__,得到属性
config.update(cfg)
config = dict(filter(lambda i: i[0].isupper(), config.items()))
self.update(config)
而flask的app.config.from_object(),是采用dir(object)来获得属性,它会把父类的属性也读出来,然后用getattr(obj, key)读到属性值:
def from_object(self, obj):
if isinstance(obj, string_types):
obj = import_string(obj)
for key in dir(obj): # 使用dir(obj),会把父类的属性也读出来
if key.isupper(): # 判断一下key是否是大写
self[key] = getattr(obj, key)
问题就出现在是使用__dict__还是使用dir(object)。这两者的区别如下:
__dict__:查看类中所有属性,是一个字典。
- 需要注意的一点是,该属性可以用类名或者类的实例对象来调用,用类名直接调用__dict__,会输出该由类中所有类属性组成的字典;而使用类的实例对象调用__dict__,会输出由类中所有实例属性组成的字典。
- 对于具有继承关系的父类和子类来说,父类有自己的__dict__,同样子类也有自己的__dict__,它不会包含父类的__dict__。
dir()函数:查看对像内所有属性及方法的名称列表。
- 如果dir(类):返回类属性、方法名称。
- dir(对象):返回类属性、实例属性和方法名称。
怎么解决这个问题呢,参照Flask的办法,使用dir(object)来获取config类(对象)的属性,并转换成字典后做为参数传入update_config()。
- 在app.update_config()时做转换
conf = config[config_name]
app.update_config({key: getattr(conf, key) for key in dir(conf) if key.isupper()})
print(app.config.BASE_DIR)
- 在基类Config中加入类方法to_dict()
# config.py
import os
class Config:
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
@staticmethod
def init_app(app):
pass
# 加入一个类方法,返回类的所有名称为大写的类属性
@classmethod
def to_dict(cls):
return {key: getattr(cls, key) for key in dir(cls) if key.isupper()}
class DevConfig(Config):
DEBUG = True
ACCESS_LOG = False
DB_URL = 'mysql://test:Tes@*.*.*.*:3306/test'
class ProConfig(Config):
DEBUG = False
ACCESS_LOG = False
DB_URL = 'mysql://production:production@*.*.*.*:3306/production'
config = {
'development': DevConfig,
'production': ProConfig,
'default': DevConfig
}
# app.py
from sanic import Sanic
form config import config
app = Sanic(__name__)
conf_name = 'development'
# 调用config类的类方法to_dict()
app.update_config(config[conf_name].to_dict())
另外,在sanic的update_config()中,使用了isclass()方法来判断传入的是一个类,还是对象。
def isclass(object):
return isinstance(object, type)
很简单的只使用了isinstance()来判断。在python中,【一切皆对象(object)】。
conf = DevConfig()
print(isinstance(conf, object)) # True
print(isinstance(conf(), object)) # True
print(isinstance(conf, type)) # True
print(isinstance(conf(), type)) # False
conf、conf()都是object,但conf(类)是type,conf()(对象)不是type。