【问题标题】:python 3.8 - dynamically set properties to a class when it's initializedpython 3.8 - 在初始化时动态设置类的属性
【发布时间】:2020-12-28 04:02:25
【问题描述】:

我正在尝试创建一个类,其实例可以具有任意数量的属性。这可以在 init 方法中使用**kwargs 轻松实现。然后,对于它的每个属性,我需要计算实例整个生命周期中的访问次数。

如果我对每个属性都有一个 property,那么我可以在 gettersetter 方法中为访问的该属性增加一个计数器给定的时间。

问题是我需要动态创建这些属性,因为我不知道我的类的实例将具有哪些属性。

我相信这可以通过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 的属性_x 的值为5,以及属性_y 的值为6xy 显示为 property objects,所以一切似乎都正常。但它并没有结束。

确实,最终的打印语句会输出这个:

<property object at 0x7fbb241d4b30> <property object at 0x7fbb241d4b80>

而我期望的输出是:5 6,即_x_y 属性的值。

【问题讨论】:

  • 问题是property()的用法和你尝试做的不一样。通常它是 class 中的x = property(getter, setter) 直接 而不是方法中的self.x = property(getter, setter)
  • 使用类而不是实例似乎有效:setattr(CountAttr, kwarg, property(getter, setter))

标签: python python-3.x properties attributes metaclass


【解决方案1】:

如果你愿意使用全局变量,你可以使用全局字典来做到这一点:

_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

【讨论】:

  • 好吧,虽然我不想涉及全局变量,但这很好。我不明白为什么self._count[key] += 1 不起作用
  • 这会重置所创建类的每个实例的所有计数器。
  • @jsbueno 是的,但有办法绕过它。
  • @dc_Bita98 这是我的错,我不太愿意覆盖__getattribute__,所以我不知道该怎么做,也没有得到RecursionError(来自调用self._count )。但是,@jsbueno 的解决方案很聪明,您可以合并它或直接使用他们的解决方案。
【解决方案2】:

Python 中对象中的所有属性访问,无论属性如何实际存储在内存中(或者即使它是计算出来的),都通过 __setattr____getattribute__ 类中的两个特殊方法。

就是这样 - 只需将您的计数器代码放在这两个方法中,使用单独的名称作为实例属性来存储计数器数据本身,然后继续 - 无需摆弄元类。您只需要一个带有此计数器代码的基类(或 mixin)。例如,如果您不希望对参数的初始设置进行计数,则可以从“-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)

【讨论】:

  • 谢谢。完美运行,但我想使用属性来添加一个级别的信息隐藏,从而防止直接访问“私人”属性_x
  • 一个属性只在类级别起作用 - 要使用每个实例具有不同属性集的属性,您还需要为每个实例创建一个 新类,否则所有属性都会累积为父类的属性。
【解决方案3】:

正如@Wups 所说,设置类属性而不是实例属性有助于解决这种情况。 Here 他们展示了一个示例,如何使用 property() 函数设置封装属性。 value 是一个类属性,其值是 property() 返回的值。因此,在您的循环中,您可以创建此类属性,并使用适当的 setter 和 getter 作为值来传递 property()。我也不会在循环中为每个 kwarg 重复声明 setter 和 getter,因为您可以声明一次,但由于每个 kwarg 的名称不同,您可以将它们封装在外部函数中,该函数将这个名称作为参数,然后返回一个具有正确名称的属性集的函数。

这个例子似乎可以正常工作,正如你所希望的那样:

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

【讨论】:

  • 我在 getter 和 setter 方法中添加了 counter 逻辑,现在可以按我的意愿工作。谢谢
  • 正如我在回答中所评论的那样:如果这对您有用,那么可以 - 但您必须注意属性是在类上注册的,并且对于创建的每个实例都是累积的。
  • 实际上 - 这段代码被破坏了:在新类中重新使用相同名称的属性将覆盖在前一个实例中创建的property - 而前一个实例设置器代码将停止工作.示例代码中的 settr 和 getter 是空的,但是只要你附上一个计数器,你就会明白我的意思了。
  • (抱歉:-1 代码损坏 - 我会关注修复)
  • @jsbueno 可能不理解你,但不,它不理解。这 inst1 = SomeClass(r=2, s=4) print(inst1.r, inst1.s) print(inst1.count) inst2 = SomeClass(r=1, s=5) print(inst1.count) print(inst1 .r,inst1.s)打印(inst1.count)inst1.r = 6打印(inst1.r,inst1.s)打印(inst1.count)inst2.s = 77打印(inst1.count)打印(inst1.r , inst1.s) print(inst2.r, inst2.s) 给出以下输出 2 4 {'r': 1, 's': 1} {'r': 1, 's': 1} 2 4 {' r': 2, 's': 2} 6 4 {'r': 4, 's': 3} {'r': 4, 's': 3} 6 4 1 77 第一次没有被覆盖,计数很好。
猜你喜欢
  • 2012-12-15
  • 1970-01-01
  • 2017-09-29
  • 1970-01-01
  • 2021-09-29
  • 1970-01-01
  • 1970-01-01
  • 2012-11-25
  • 1970-01-01
相关资源
最近更新 更多