我正在寻找一种从instance-resp访问配置文件项的方法。类绑定变量。为此,我创建了以下模块:
from ..lib.files import ConfigFile
from abc import abstractmethod
__all__ = ['ClassConfig',
'InstanceConfig',
'Configurable']
class ConfigEntry():
"""
A Config entry
"""
__value = None
def __init__(self, value=None):
"""
Initializes the
"""
self.__value = value
def __set__(self, __, value):
self.__value = value
@property
def value(self):
"""
Returns the value
"""
return self.__value
class ClassConfig(ConfigEntry):
"""
A class config entry
"""
def __get__(self, obj, cls):
"""
Returns its value, when called by a class, else itself
"""
if obj == None:
return self.value
else:
return self
class InstanceConfig(ConfigEntry):
"""
An instance config entry
"""
def __get__(self, obj, cls):
"""
Returns its value, when called by an instance, else itself
"""
if obj != None:
return self.value
else:
return self
class Configurable():
"""
Configuration file binding
"""
__SUFFIX = '.conf'
__TYPES = {int: 'int',
float: 'float',
str: 'str',
bool: 'bool'}
__file_ = None
__lbi = '['
__lei = ']'
__ls = ','
__ts = '←'
__loaded = False
def __init__(self, path, suffix=None):
"""
Initialize the config file
"""
# Initializes instance methods
self.__setinstattr()
suffix = suffix if suffix != None else self.__SUFFIX
self.__file_ = ConfigFile(path + suffix)
self.load()
def __setinstattr(self):
"""
Set instance attributes
"""
self.__fields = self.__inst___fields
self._file = self.__inst____file
self._force_load = self.__inst__force_load
self.load = self.__inst_load
self.store = self.__inst_store
@staticmethod
def __filter(attrs):
return [a for a in attrs
if a == a.upper()
and not a.startswith('_')]
@staticmethod
def __encode(val):
"""
Encode a value
"""
t = type(val)
if t == list:
return Configurable.__lbi +
Configurable.__ls.join([Configurable.__encode(i)
for i in val])
+ Configurable.__lei
elif val == None:
return None
else:
return Configurable.__ts.join([str(val),
Configurable.__TYPES.get(t, '?')])
@staticmethod
def __decode(val):
"""
Decode a value
"""
def det_type(token):
"""
Determine the type of a token
"""
t = token.strip().split(Configurable.__ts)
if len(t) == 2:
raw_val = t[0]
tpe = t[1]
if tpe == Configurable.__TYPES[str]:
return str(raw_val)
elif tpe == Configurable.__TYPES[int]:
return int(raw_val)
elif tpe == Configurable.__TYPES[float]:
return float(raw_val)
elif tpe == Configurable.__TYPES[bool]:
return True if raw_val.lower() in ['1',
'true',
't'] else False
else:
try:
return int(raw_val)
except:
try:
return float(raw_val)
except:
return raw_val
return token
def str2list(s):
"""
Try to parse a list from a string
"""
def getlist(val):
"""
Get a list from a reversed character list of a string
"""
result = []
token = ''
while val:
c = val.pop()
if c == Configurable.__lei:
token = Configurable.__lei
result = [getlist(val)] + result
elif c == Configurable.__lbi:
if (not Configurable.__lbi in token) and (not Configurable.__lei in token):
result = [det_type(token)] + result
token = c
return result
elif c == Configurable.__ls:
if (not Configurable.__lbi in token) and (not Configurable.__lei in token):
result = [det_type(token)] + result
token = ''
else:
token = c + token
if token:
result = [det_type(token)] + result
return result
l = []
for char in s:
l.append(char)
l = getlist(l)
if len(l) == 0:
return l
return l.pop()
return str2list(val)
@classmethod
def __fields(cls):
"""
Get fields for an instance
"""
result = {}
class Subclass(cls):
def __init__(self):
pass
instance = Subclass()
attrs = Configurable.__filter(dir(instance))
for a in attrs:
aval = getattr(instance, a)
if isinstance(aval, ClassConfig):
value = getattr(cls, a)
result[a] = value
return result
def __inst___fields(self):
"""
Get fields of an instance
"""
result = {}
cls = self.__class__
attrs = Configurable.__filter(dir(cls))
for a in attrs:
val = getattr(cls, a)
if isinstance(val, InstanceConfig):
value = getattr(self, a)
result[a] = value
return result
@classmethod
@abstractmethod
def _file(cls):
"""
Returns the file
XXX: Implement by calling
super()._file(static_path)
"""
pass
@classmethod
def _file_path(cls, path, suffix=None):
"""
Returns the file relative to a path
"""
suffix = suffix if suffix != None else cls.__SUFFIX
f = ConfigFile(path + suffix)
f.create()
return f
def __inst____file(self):
"""
Returns the file
"""
return self.__file_
@classmethod
def load(cls):
"""
Loads the config file content, if not yet done into the class
"""
if not cls.__loaded:
return cls._force_load()
return True
def __inst_load(self):
"""
Loads the config file content, if not yet done into the instance
"""
if not self.__loaded:
return self._force_load()
return True
@classmethod
def _force_load(cls):
"""
Loads the config file's content to the class
"""
if cls._file().exists:
data = cls._file().dict()
for field in Configurable.__filter(data):
setattr(cls, field,
Configurable.__decode(data[field]))
cls.__loaded = True
return True
return False
def __inst__force_load(self):
"""
Loads the config file's content to the instance
"""
if self._file().exists:
data = self._file().dict()
for field in Configurable.__filter(data):
setattr(self, field,
Configurable.__decode(data[field]))
self.__loaded = True
return True
return False
@classmethod
def store(cls):
"""
Writes class config to file
"""
result = True
content = cls.__fields()
if not cls._file().exists:
cls._file().create()
for new_field in content:
set_result = cls._file().set(new_field,
Configurable.__encode(content[new_field]))
result = False if not set_result else result
return result
def __inst_store(self):
"""
Writes instance config to file
"""
result = True
content = self.__fields()
if not self._file().exists:
self._file().create()
for new_field in content:
set_result = self._file().set(new_field,
Configurable.__encode(content[new_field]))
result = False if not set_result else result
return result
Configurable
类现在由几个子类继承,这些子类可能具有全局配置(类绑定的东西)和用户依赖的配置(实例绑定的东西
class Spam(Configurable):
EGGS = InstanceConfig('foo')
GLOBAL_EGGS = ClassConfig('bar')
现在我面临的问题是,每次在多个实例上按顺序执行load()
时,InstanceConfigEntry都会复制上一个实例的值:
class RETARD(Daemon):
"""
Real Estate Translation, Archiving and Redirection Daemon
"""
__source = None # The source interface instance
__targets = [] # The target interface instances
__locked = False # System locked state flag
__start_time = None # Start time of loop
__sleeping = 0 # Remaining time to sleep
#===========================================================================
# Default customer config
#===========================================================================
SOURCE = InstanceConfig('') # Name of the source interface
TARGETS = InstanceConfig([]) # Names of the target interfaces
INTERVAL = InstanceConfig(120.0) # Loop interval
DEBUG = InstanceConfig(False) # Print the import config?
def __init__(self, customer):
"""
Constructor
"""
print('SOURCE1: ' + str(self.SOURCE))
super().__init__(customer)
print('SOURCE2: ' + str(self.SOURCE))
self.__load()
print('SOURCE3: ' + str(self.SOURCE))
# Disable logger on high level to prevent PyXB
# from printing messages to the terminal
logging.disable(9999)
<SNIP>
这样加载时(守护进程包含四个不同的实例):
daemons = []
for customer in customers:
daemons.append(RETARD(customer))
它将产生以下输出:
SOURCE1:
SOURCE2: IS24
SOURCE3: IS24
SOURCE1: IS24
SOURCE2: is24
SOURCE3: is24
SOURCE1: is24
SOURCE2: infobase
SOURCE3: infobase
SOURCE1: infobase
SOURCE2: infobase
SOURCE3: infobase
我不理解这种行为,因为我没有在任何地方更改类的属性,只更改了实例的属性。如何避免实例将其更改的属性发送到下一个实例?
我没有意识到的问题是,InstanceConfig
和ClassConfig
条目当时绑定到类,模块被加载。当我在运行时从一个实例中将其他内容分配给相应的属性时,它当然只是更改了静态类绑定*Config
实例的内容。我通过默认值解决了这个问题,如果它们不包含在相应的配置文件中,比如:
from ..lib.files import ConfigFile
from abc import abstractmethod
__all__ = ['ClassConfig',
'InstanceConfig',
'Configurable']
class ConfigEntry():
"""
A Config entry
"""
__value = None
__default = None
def __init__(self, default=None):
"""
Initializes the
"""
self.__default = default
self.__value = default
def __set__(self, __, value):
"""
Sets the value
"""
self.__value = value
@property
def value(self):
"""
Returns the value
"""
return self.__value
@property
def default(self):
"""
Access default value
"""
return self.__default
class ClassConfig(ConfigEntry):
"""
A class config entry
"""
def __get__(self, obj, cls):
"""
Returns its value, when called by a class, else itself
"""
if obj == None:
return self.value
else:
return self
class InstanceConfig(ConfigEntry):
"""
An instance config entry
"""
def __get__(self, obj, cls):
"""
Returns its value, when called by an instance, else itself
"""
if obj != None:
return self.value
else:
return self
class Configurable():
"""
Configuration file binding
"""
__SUFFIX = '.conf'
__TYPES = {int: 'int',
float: 'float',
str: 'str',
bool: 'bool'}
__file_ = None
__lbi = '[' # List begin identifier
__lei = ']' # List end identifier
__ls = ',' # List separator
__ts = '←' # Type separator
__loaded = False
def __init__(self, path, suffix=None):
"""
Initialize the config file
"""
# Initializes instance methods
self.__setinstattr()
suffix = suffix if suffix != None else self.__SUFFIX
self.__file_ = ConfigFile(path + suffix)
self.load()
def __setinstattr(self):
"""
Set instance attributes
"""
self.__fields = self.__inst___fields
self._file = self.__inst____file
self._force_load = self.__inst__force_load
self.load = self.__inst_load
self.store = self.__inst_store
@staticmethod
def __filter(attrs):
return [a for a in attrs
if a == a.upper()
and not a.startswith('_')]
@staticmethod
def __encode(val):
"""
Encode a value
"""
t = type(val)
if t == list:
return Configurable.__lbi +
Configurable.__ls.join([Configurable.__encode(i)
for i in val])
+ Configurable.__lei
elif val == None:
return None
else:
return Configurable.__ts.join([str(val),
Configurable.__TYPES.get(t, '?')])
@staticmethod
def __decode(val):
"""
Decode a value
"""
def det_type(token):
"""
Determine the type of a token
"""
t = token.strip().split(Configurable.__ts)
if len(t) == 2:
raw_val = t[0]
tpe = t[1]
if tpe == Configurable.__TYPES[str]:
return str(raw_val)
elif tpe == Configurable.__TYPES[int]:
return int(raw_val)
elif tpe == Configurable.__TYPES[float]:
return float(raw_val)
elif tpe == Configurable.__TYPES[bool]:
return True if raw_val.lower() in ['1',
'true',
't'] else False
else:
try:
return int(raw_val)
except:
try:
return float(raw_val)
except:
return raw_val
return token
def str2list(s):
"""
Try to parse a list from a string
"""
def getlist(val):
"""
Get a list from a reversed character list of a string
"""
result = []
token = ''
while val:
c = val.pop()
if c == Configurable.__lei:
token = Configurable.__lei
result = [getlist(val)] + result
elif c == Configurable.__lbi:
if (not Configurable.__lbi in token) and (not Configurable.__lei in token):
result = [det_type(token)] + result
token = c
return result
elif c == Configurable.__ls:
if (not Configurable.__lbi in token) and (not Configurable.__lei in token):
result = [det_type(token)] + result
token = ''
else:
token = c + token
if token:
result = [det_type(token)] + result
return result
l = []
for char in s:
l.append(char)
l = getlist(l)
if len(l) == 0:
return l
return l.pop()
return str2list(val)
@classmethod
def __fields(cls):
"""
Get fields for an instance
"""
result = {}
class Subclass(cls):
def __init__(self):
pass
instance = Subclass()
attrs = Configurable.__filter(dir(instance))
for a in attrs:
aval = getattr(instance, a)
if isinstance(aval, ClassConfig):
result[a] = aval
return result
def __inst___fields(self):
"""
Get fields of an instance
"""
result = {}
cls = self.__class__
attrs = Configurable.__filter(dir(cls))
for a in attrs:
val = getattr(cls, a)
if isinstance(val, InstanceConfig):
result[a] = val
return result
@classmethod
@abstractmethod
def _file(cls):
"""
Returns the file
XXX: Implement by calling
super()._file(static_path)
"""
pass
@classmethod
def _file_path(cls, path, suffix=None):
"""
Returns the file relative to a path
"""
suffix = suffix if suffix != None else cls.__SUFFIX
f = ConfigFile(path + suffix)
f.create()
return f
def __inst____file(self):
"""
Returns the file
"""
return self.__file_
@classmethod
def load(cls):
"""
Loads the config file content, if not yet done into the class
"""
if not cls.__loaded:
return cls._force_load()
return True
def __inst_load(self):
"""
Loads the config file content, if not yet done into the instance
"""
if not self.__loaded:
return self._force_load()
return True
@classmethod
def _force_load(cls):
"""
Loads the config file's content to the class
"""
if cls._file().exists:
data = cls._file().dict()
else:
data = {}
fields = cls.__fields()
for field in fields:
val = data.get(field)
if val == None:
val = fields[field].default
else:
val = Configurable.__decode(val)
setattr(cls, field, val)
cls.__loaded = True
return True
def __inst__force_load(self):
"""
Loads the config file's content to the instance
"""
if self._file().exists:
data = self._file().dict()
else:
data = {}
fields = self.__fields()
for field in fields:
val = data.get(field)
if val == None:
val = fields[field].default
else:
val = Configurable.__decode(val)
setattr(self, field, val)
self.__loaded = True
return True
@classmethod
def store(cls):
"""
Writes class config to file
"""
result = True
fields = cls.__fields()
if not cls._file().exists:
cls._file().create()
for field in fields:
val = fields[field].value
set_result = cls._file().set(field,
Configurable.__encode(val))
result = False if not set_result else result
return result
def __inst_store(self):
"""
Writes instance config to file
"""
result = True
fields = self.__fields()
if not self._file().exists:
self._file().create()
for field in fields:
val = fields[field].value
set_result = self._file().set(field,
Configurable.__encode(val))
result = False if not set_result else result
return result