[英]Is it possible to modify a class with a decorator
我正在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)
如你所見,我添加了塊:
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)
對於每一個設置。
是否可以使用decorator
自動執行此任務?
我想這樣做(偽代碼):
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類裝飾器
從那里的答案我懷疑有可能像我要求的那樣實現,但你能告訴我如何實現add_getter_setter_and_property()
函數/方法嗎?
注意:
如果字符串(fe'size_x')存在,則_int()
, _bool()
函數僅從kwargs返回_bool()
Int / Bool-var eighter或者從默認值(fe 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
}
這個類的裝飾員。
def add_get_set(cls):
for prop in cls.PROPERTIES:
# Note cannot be "lambda self: getattr(self, prop)" because of scope prop changes to be the last item in PROPERTIES
setattr(cls, "get"+prop, lambda self, attr=prop: getattr(self, attr))
return cls
@add_get_set
class _CanvasSettings:
PROPERTIES = ["_size_x", "_size_y", "_lock_ratio"]
def __init__(self, **kwargs):
super().__init__()
for prop in self.PROPERTIES:
setattr(self, prop, 0)
c = _CanvasSettings()
print(c.get_size_y())
您可以將函數設置為變量
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)
for variable in ["_size_x", "_size_y", "_lock_ratio"]:
setattr(self, "get"+variable, lambda: getattr(self, variable))
# bind the method (Not sure if binding the method gets you anything)
#setattr(self, "get"+variable, (lambda self: getattr(self, variable)).__get__(self, self.__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)
for variable in dir(self):
if variable.startswith("_") and not variable.startswith("__"):
self.__dict__["get"+variable] = lambda: getattr(self, variable)
使用setattr
將函數和屬性綁定為類對象的屬性當然可以做你想要的事情:
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
方法命名。 如果你真的想要,可以將它們添加到課堂中,但我認為這可能是不必要的。
棘手的一點可能實際上是找到你需要應用它的屬性名稱。 與您的示例不同,您不能迭代類對象以獲取其屬性。
最簡單的解決方案(從實現的角度來看)是要求類在類變量中指定名稱:
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
來檢查類變量並挑選出需要自動vars
行的變量。 您可以使用isinstance
檢查值是否具有特定類型,或者在屬性名稱中查找特定模式。 我不知道什么是最適合您的具體用途,所以我會留給您。
作為自動化制作屬性的替代方法,您可以重載__getattr__
和__setattr__
以檢測私有字段何時可用適當的getter或setter方法:
class Field: # so I could test it.
def __init__(self,args,name,default):
self.name = name
self.value = default
def get(self):
return self.value
def set(self,value):
self.value = value
class CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = Field(kwargs, 'size_x', 320)
self._size_y = Field(kwargs, 'size_y', 240)
self._lock_ratio = Field(kwargs, 'lock_ratio', True)
def __getattr__(self, attr):
private_name = "_" + attr
field = object.__getattribute__(self, private_name) #this will fail for non-special attributes
getter = getattr(field,"get",None)
if getter is None:
raise AttributeError("Private member did not have getter") #may want to change the error handling
else:
return getter()
def __setattr__(self,attr, value):
private_name = "_" + attr
try:
field = getattr(self,private_name)
except AttributeError:
# if there is no private field or there is but no setter
# resort back to defaualt behaviour.
return super().__setattr__(attr,value)
else:
setter = getattr(field, "set", None)
if setter is None:
raise AttributeError("private field does not have a setter")
setter(value)
然后當你嘗試獲取或設置thing.size_x
,它將首先查找thing._size_x
並檢查一個合適的方法,這是一個演示:
>>> thing = CanvasSettings()
>>> thing._size_x.value
320
>>> thing.size_x
320
>>> thing.size_x = 5
>>> 5 == thing.size_x == thing._size_x.value
True
每次檢索屬性時檢查現有字段可能會對性能造成損害,但如果您有許多具有適合此模型的私有字段的類,我只想提供此選項。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.