我正在用 python 编写一个类,用于一些设置,如下所示:
class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)
def get_size_x_var(self):
return self._size_x
def _get_size_x(self):
return self._size_x.get()
def _set_size_x(self, value):
self._size_x.set(value)
size_x = property(_get_size_x, _set_size_x)
def get_size_y_var(self):
return self._size_y
def _get_size_y(self):
return self._size_y.get()
def _set_size_y(self, value):
self._size_y.set(value)
size_y = property(_get_size_y, _set_size_y)
def get_lock_ratio_var(self):
return self._lock_ratio
def _get_lock_ratio(self):
return self._lock_ratio.get()
def _set_lock_ratio(self, value):
self._lock_ratio.set(value)
lock_ratio = property(_get_lock_ratio, _set_lock_ratio)
如你所见,我添加了 block :
def get_something_var(self):
return self._something
def _get_something(self):
return self._something.get()
def _set_something(self, value):
self._something.set(value)
something = property(_get_something, _set_something)
对于每一个设置。
是否可以使用装饰器
自动执行此任务?
我想这样做(伪代码):
def my_settings_class(cls):
result = cls
for field in cls:
result.add_getter_setter_and_property( field )
return result
@my_settings_class
class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)
# Done !
这可能吗?
如果是,如何?
如何实现add_getter_setter_and_property()
方法?
编辑:
这里有一个非常相似的问题:Python Class Decorator
从那里的答案中,我怀疑有可能实现我所要求的目标,但是您能给我一些有关如何实现 add_getter_setter_and_property()
函数/方法的线索吗?
注意:
如果字符串(例如“size_x”)存在或来自默认值,则 _int()
、_bool()
函数仅从 kwargs 返回 8 个 tkinter Int/Bool-var值(例如 320)。
我的最终解决方案: 我想我已经找到了一个非常好的解决方案。我只需添加一次设置名称,这在我看来非常棒:-)
import tkinter as tk
def _add_var_getter_property(cls, attr):
""" this function is used in the settings_class decorator to add a
getter for the tk-stringvar and a read/write property to the class.
cls: is the class where the attributes are added.
attr: is the name of the property and for the get_XYZ_var() method.
"""
field = '_' + attr
setattr(cls, 'get_{}_var'.format(attr), lambda self: getattr(self, field))
setattr(cls, attr,
property(lambda self: getattr(self, field).get(),
lambda self, value: getattr(self, field).set(value)))
def settings_class(cls):
""" this is the decorator function for SettingsBase subclasses.
it adds getters for the tk-stringvars and properties. it reads the
names described in the class-variable _SETTINGS.
"""
for name in cls._SETTINGS:
_add_var_getter_property(cls, name)
return cls
class SettingsBase:
""" this is the base class for a settings class. it automatically
adds fields to the class described in the class variable _SETTINGS.
when you subclass SettingsBase you should overwrite _SETTINGS.
a minimal example could look like this:
@settings_class
class MySettings(SettingsBase):
_SETTINGS = {
'x': 42,
'y': 23}
this would result in a class with a _x tk-intvar and a _y tk-doublevar
field with the getters get_x_var() and get_y_var() and the properties
x and y.
"""
_SETTINGS = {}
def __init__(self, **kwargs):
""" creates the fields described in _SETTINGS and initialize
eighter from the kwargs or from the default values
"""
super().__init__()
fields = self._SETTINGS.copy()
if kwargs:
for key in kwargs:
if key in fields:
typ = type(fields[key])
fields[key] = typ(kwargs[key])
else:
raise KeyError(key)
for key in fields:
value = fields[key]
typ = type(value)
name = '_' + key
if typ is int:
var = tk.IntVar()
elif typ is str:
var = tk.StringVar()
elif typ is bool:
var = tk.BooleanVar()
elif typ is float:
var = tk.DoubleVar()
else:
raise TypeError(typ)
var.set(value)
setattr(self, name, var)
之后我的设置类看起来就像这样:
@settings_class
class _CanvasSettings(SettingsBase):
_SETTINGS = {
'size_x': 320,
'size_y': 240,
'lock_ratio': True
}
最佳答案
使用setattr
将函数和属性绑定(bind)为类对象的属性当然可以做你想做的事情:
def add_getter_setter_property(cls, attrib_name):
escaped_name = "_" + attrib_name
setattr(cls, "get_{}_var".format(attrib_name),
lambda self: getattr(self, escaped_name))
setattr(cls, attrib_name,
property(lambda self: getattr(self, escaped_name).get()
lambda self, value: getattr(self, escaped_name).set(value)))
这里我跳过为 property
使用的 getter
和 setter
方法命名。如果您确实愿意,可以将它们添加到类(class)中,但我认为这可能没有必要。
棘手的一点实际上可能是找到您需要将其应用到哪些属性名称。与您的示例不同,您无法迭代类对象来获取其属性。
最简单的解决方案(从实现的角度来看)是要求类在类变量中指定名称:
def my_settings_class(cls):
for field in cls._settings_vars:
add_getter_setter_and_property(cls, field)
return cls
@my_settings_class
class _CanvasSettings:
_settings_vars = ["size_x", "size_y", "lock_ratio"]
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs, 'lock_ratio', True)
更用户友好的方法可能会使用 dir
或 vars
来检查类变量并挑选出需要自动包装的变量。您可以使用 isinstance 来检查值是否具有特定类型,或者在属性名称中查找特定模式。我不知道什么最适合您的具体用途,所以我将其留给您。
关于python - 是否可以用装饰器修改类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37710747/