python 3.8 - 初始化时将属性动态设置为 class
python 3.8 - dynamically set properties to a class when it's initialized
我正在尝试创建一个 class 其实例可以具有任意数量的属性。使用 init 方法中的 **kwargs
可以很容易地实现这一点。然后,对于它的每个属性,我需要计算实例整个生命周期中的访问次数。
如果每个属性都有一个 属性,那么我可以在两个 getter[=53= 中增加一个计数器] 和 setter 在给定时间访问的属性的方法。
问题是我需要动态创建这些属性,因为我不知道 class 的实例将具有哪些属性。
我相信这可以通过 metaclasses
解决。虽然,我正试图在没有 的情况下 解决这个问题。
到目前为止我已经写了这段代码:
class CountAttr:
def __init__(self, **kwargs):
# here I init the counter for each keyword argument
self.__count = {attr: 0 for attr in kwargs}
# then I add the attribute to my instance
for kwarg in kwargs:
object.__setattr__(self, '_' + kwarg, kwargs[kwarg])
def getter(self):
self.__count[kwarg] +=1
return object.__getattribute__(self, '_' + kwarg)
def setter(self, value):
self.__count[kwarg] +=1
object.__setattr__(self, '_' + kwarg, kwargs[kwarg], value)
#here comes the issue: the property doesn't work
object.__setattr__(self, kwarg, property(getter, setter))
@property
def count(self):
return self.__count
test = CountAttr(x=5, y=6)
print(test.x, test.y)
调试我的代码,我评估 test
具有值为 5
的属性 _x
,以及值为 [=19] 的属性 _y
=]. x
和 y
显示为 property objects
,因此似乎一切正常。但它并没有结束。
的确,最后的打印语句会输出这个:
<property object at 0x7fbb241d4b30> <property object at 0x7fbb241d4b80>
虽然我期望的输出是:5 6
,即 _x
和 _y
属性的值。
正如@Wups 指出的那样,设置 class 属性而不是实例属性有助于解决这种情况。 Here 他们展示了一个示例,如何使用 属性() 函数设置封装属性。 value 是一个 class 属性,其值是 属性() returns。因此,在您的循环中,您可以创建此 class 属性传递 属性() 并使用适当的 setter 和 getter 作为值。我也不会在循环中为每个 kwarg 重复声明 setters 和 getter,因为你可以声明它们一次,但由于每个 kwarg 的名称不同,你可以将它们封装在外部函数中,它将此名称作为参数,然后 return 一个具有属性集专有名称的函数。
这个例子似乎在工作,正如你所希望的那样:
class SomeClass:
def __init__(self, **kwargs):
for item in kwargs:
setattr(SomeClass, item, property(self.getter(item),
self.setter(item, kwargs[item])))
setattr(self, '__'+item, kwargs[item])
def setter(self, name: str, value):
def inner_setter(self, value):
setattr(self, '__'+name, value)
return inner_setter
def getter(self, name: str):
def inner_getter(self):
return getattr(self, '__'+name)
return inner_getter
inst = SomeClass(r=2, s=4)
print(inst.r, inst.s)
输出:
2 4
如果你愿意使用全局变量,你可以使用全局字典:
_count = {}
class CountAttr:
def __init__(self, **kw):
for i, j in kw.items():
object.__setattr__(self, i, j)
global _count
_count = {i: 0 for i in kw.keys()}
def __getattribute__(self, key):
global _count
if key in _count:
_count[key] += 1
return object.__getattribute__(self, key)
def __setattr__(self, key, val):
global _count
_count[key] += 1
return object.__setattr__(self, key, val)
@property
def count(self):
return _count # expose count dict interface
用法:
>>> test = CountAttr(x=5, y=6)
>>> test.x
5
>>> test.y
6
>>> test.count['x']
1
>>> test.count['y']
1
按照@jsbueno 的回答,您可以将 count
设置为实例,只要您在 __init__
:
中小心排序即可
class CountAttr:
def __init__(self, **kw):
self.count = {i: 0 for i in kw.keys()} # If <- isn't first
for i, j in kw.items(): # you will get an
setattr(self, i, j) # `AttributeError`.
super().__init__()
def __getattribute__(self, key):
if key != 'count':
counter = self.count
counter[key] += 1
return super().__getattribute__(key)
def __setattr__(self, key, val):
if key != 'count':
counter = self.count
counter[key] += 1
return super().__setattr__(key, val
Python 中对象中的所有属性访问,无论属性实际如何存储在内存中(或者即使它是计算出来的),都通过 class 中的两个特殊方法 __setattr__
和 __getattribute__
.
就是这样 - 只需将你的计数器代码放在这两个方法中,使用单独的名称作为实例属性来存储计数器数据本身,然后继续 - 无需摆弄元class。您只需要一个带有此计数器代码的基础(或混合)class。如果您不想对参数的初始设置进行计数,则可以从“-1”开始计数以进行写入,例如。
class CounterBase:
def __init__(self, **kwargs):
self._counters = {}
for name, value in kwargs.items():
setattr(self, name, value)
super().__init__()
def __getattribute__(self, name):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__getattribute__(name)
def __setattr__(self, name, value):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__setattr__(name, value)
我正在尝试创建一个 class 其实例可以具有任意数量的属性。使用 init 方法中的 **kwargs
可以很容易地实现这一点。然后,对于它的每个属性,我需要计算实例整个生命周期中的访问次数。
如果每个属性都有一个 属性,那么我可以在两个 getter[=53= 中增加一个计数器] 和 setter 在给定时间访问的属性的方法。
问题是我需要动态创建这些属性,因为我不知道 class 的实例将具有哪些属性。
我相信这可以通过 metaclasses
解决。虽然,我正试图在没有 的情况下 解决这个问题。
到目前为止我已经写了这段代码:
class CountAttr:
def __init__(self, **kwargs):
# here I init the counter for each keyword argument
self.__count = {attr: 0 for attr in kwargs}
# then I add the attribute to my instance
for kwarg in kwargs:
object.__setattr__(self, '_' + kwarg, kwargs[kwarg])
def getter(self):
self.__count[kwarg] +=1
return object.__getattribute__(self, '_' + kwarg)
def setter(self, value):
self.__count[kwarg] +=1
object.__setattr__(self, '_' + kwarg, kwargs[kwarg], value)
#here comes the issue: the property doesn't work
object.__setattr__(self, kwarg, property(getter, setter))
@property
def count(self):
return self.__count
test = CountAttr(x=5, y=6)
print(test.x, test.y)
调试我的代码,我评估 test
具有值为 5
的属性 _x
,以及值为 [=19] 的属性 _y
=]. x
和 y
显示为 property objects
,因此似乎一切正常。但它并没有结束。
的确,最后的打印语句会输出这个:
<property object at 0x7fbb241d4b30> <property object at 0x7fbb241d4b80>
虽然我期望的输出是:5 6
,即 _x
和 _y
属性的值。
正如@Wups 指出的那样,设置 class 属性而不是实例属性有助于解决这种情况。 Here 他们展示了一个示例,如何使用 属性() 函数设置封装属性。 value 是一个 class 属性,其值是 属性() returns。因此,在您的循环中,您可以创建此 class 属性传递 属性() 并使用适当的 setter 和 getter 作为值。我也不会在循环中为每个 kwarg 重复声明 setters 和 getter,因为你可以声明它们一次,但由于每个 kwarg 的名称不同,你可以将它们封装在外部函数中,它将此名称作为参数,然后 return 一个具有属性集专有名称的函数。
这个例子似乎在工作,正如你所希望的那样:
class SomeClass:
def __init__(self, **kwargs):
for item in kwargs:
setattr(SomeClass, item, property(self.getter(item),
self.setter(item, kwargs[item])))
setattr(self, '__'+item, kwargs[item])
def setter(self, name: str, value):
def inner_setter(self, value):
setattr(self, '__'+name, value)
return inner_setter
def getter(self, name: str):
def inner_getter(self):
return getattr(self, '__'+name)
return inner_getter
inst = SomeClass(r=2, s=4)
print(inst.r, inst.s)
输出:
2 4
如果你愿意使用全局变量,你可以使用全局字典:
_count = {}
class CountAttr:
def __init__(self, **kw):
for i, j in kw.items():
object.__setattr__(self, i, j)
global _count
_count = {i: 0 for i in kw.keys()}
def __getattribute__(self, key):
global _count
if key in _count:
_count[key] += 1
return object.__getattribute__(self, key)
def __setattr__(self, key, val):
global _count
_count[key] += 1
return object.__setattr__(self, key, val)
@property
def count(self):
return _count # expose count dict interface
用法:
>>> test = CountAttr(x=5, y=6)
>>> test.x
5
>>> test.y
6
>>> test.count['x']
1
>>> test.count['y']
1
按照@jsbueno 的回答,您可以将 count
设置为实例,只要您在 __init__
:
class CountAttr:
def __init__(self, **kw):
self.count = {i: 0 for i in kw.keys()} # If <- isn't first
for i, j in kw.items(): # you will get an
setattr(self, i, j) # `AttributeError`.
super().__init__()
def __getattribute__(self, key):
if key != 'count':
counter = self.count
counter[key] += 1
return super().__getattribute__(key)
def __setattr__(self, key, val):
if key != 'count':
counter = self.count
counter[key] += 1
return super().__setattr__(key, val
Python 中对象中的所有属性访问,无论属性实际如何存储在内存中(或者即使它是计算出来的),都通过 class 中的两个特殊方法 __setattr__
和 __getattribute__
.
就是这样 - 只需将你的计数器代码放在这两个方法中,使用单独的名称作为实例属性来存储计数器数据本身,然后继续 - 无需摆弄元class。您只需要一个带有此计数器代码的基础(或混合)class。如果您不想对参数的初始设置进行计数,则可以从“-1”开始计数以进行写入,例如。
class CounterBase:
def __init__(self, **kwargs):
self._counters = {}
for name, value in kwargs.items():
setattr(self, name, value)
super().__init__()
def __getattribute__(self, name):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__getattribute__(name)
def __setattr__(self, name, value):
if name != "_counters":
_counters = self._counters
_counters[name] = _counters.get(name, 0) + 1
return super().__setattr__(name, value)