如何动态地向类添加属性?

2024-12-31 08:37:00
admin
原创
102
摘要:问题描述:目标是创建一个行为类似于数据库结果集的模拟类。因此,例如,如果数据库查询使用字典表达式返回,{'ab':100, 'cd':200}那么我希望看到:>>> dummy.ab 100 起初我以为也许我可以这样做:ks = ['ab', 'cd'] vs = [12, 34] clas...

问题描述:

目标是创建一个行为类似于数据库结果集的模拟类。

因此,例如,如果数据库查询使用字典表达式返回,{'ab':100, 'cd':200}那么我希望看到:

>>> dummy.ab
100

起初我以为也许我可以这样做:

ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
    def __init__(self, ks, vs):
        for i, k in enumerate(ks):
            self[k] = vs[i]
            setattr(self, k, property(lambda x: vs[i], self.fn_readyonly))

    def fn_readonly(self, v)
        raise "It is ready only"

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

而是c.ab返回一个属性对象。

用替换该setattr线k = property(lambda x: vs[i])根本没有用。

那么在运行时创建实例属性的正确方法是什么?

PS 我知道在如何__getattribute__使用该方法?中提出了一种替代方法。


解决方案 1:

我想我应该进一步阐述这个问题,现在我长大了,更聪明了,知道发生了什么。迟做总比不做好。

可以动态地向类添加属性。但问题在于:您必须将其添加到类中

>>> class Foo(object):
...     pass
... 
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4

A实际上是描述符property的简单实现。它是一个为给定类上的给定属性提供自定义处理的对象。有点像从中分解出一棵大树的方法。if`__getattribute__`

foo.b当我在上面的示例中请求时,Python 会看到b类上的定义实现了描述符协议__get__——这仅意味着它是一个具有、__set__或方法的对象__delete__。描述符声称负责处理该属性,因此 Python 调用Foo.b.__get__(foo, Foo),并将返回值作为属性的值传回给您。在的情况下property,这些方法中的每一个都只调用您传递给构造函数的fgetfset或。fdel`property`

描述符实际上是 Python 公开其整个 OO 实现管道的方式。事实上,还有另一种比 更常见的描述符property

>>> class Foo(object):
...     def bar(self):
...         pass
... 
>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>>
>>> Foo().bar.__get__
<method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>

谦逊方法只是另一种描述符。它将__get__调用实例作为第一个参数;实际上,它执行以下操作:

def __get__(self, instance, owner):
    return functools.partial(self.function, instance)

无论如何,我怀疑这就是为什么描述符只适用于类的原因:它们首先是支持类的东西的形式化。它们甚至是规则的例外:您显然可以将描述符分配给类,而类本身就是的实例type!事实上,尝试读取Foo.bar仍然会调用property.__get__;描述符在作为类属性访问时返回自身只是惯用做法。

我认为几乎所有 Python 的 OO 系统都可以用 Python 来表达,这非常酷。:)

哦,如果你有兴趣的话,不久前我写了一篇关于描述符的冗长的博客文章。

解决方案 2:

目标是创建一个行为类似于数据库结果集的模拟类。

那么您想要的是一本可以将 a['b'] 拼写为 ab 的字典?

这很简单:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

解决方案 3:

您无需为此使用属性。只需覆盖__setattr__以使其变为只读即可。

class C(object):
    def __init__(self, keys, values):
        for (key, value) in zip(keys, values):
            self.__dict__[key] = value

    def __setattr__(self, name, value):
        raise Exception("It is read only!")

哒哒。

>>> c = C('abc', [1,2,3])
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.d
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'd'
>>> c.d = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It is read only!
>>> c.a = 'blah'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It is read only!

解决方案 4:

看来您可以使用 更简单地解决这个问题namedtuple,因为您提前知道了整个字段列表。

from collections import namedtuple

Foo = namedtuple('Foo', ['bar', 'quux'])

foo = Foo(bar=13, quux=74)
print foo.bar, foo.quux

foo2 = Foo()  # error

如果您确实需要编写自己的 setter,那么您必须在类级别进行元编程;property()这对实例不起作用。

解决方案 5:

如何动态地向 python 类添加属性?

假设您有一个对象,想要向其添加属性。通常,当我需要开始管理对具有下游用途的代码中的属性的访问时,我会使用属性,以便我可以保持一致的 API。现在我通常会将它们添加到定义对象的源代码中,但让我们假设您没有该访问权限,或者您需要真正以编程方式动态选择您的函数。

创建类

使用基于文档property的示例,让我们创建一个具有“隐藏”属性的对象类并创建它的一个实例:

class C(object):
    '''basic class'''
    _x = None

o = C()

在 Python 中,我们期望有一种显而易见的做事方式。但是,在这种情况下,我将展示两种方式:使用装饰器符号和不使用装饰器符号。第一种,不使用装饰器符号。这可能对 getter、setter 或 deleter 的动态赋值更有用。

动态(又名 Monkey Patching)

让我们为我们的班级创建一些:

def getx(self):
    return self._x

def setx(self, value):
    self._x = value

def delx(self):
    del self._x

现在我们将它们分配给属性。请注意,我们可以在这里以编程方式选择我们的函数,回答动态问题:

C.x = property(getx, setx, delx, "I'm the 'x' property.")

用法和样例:

>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
>>> help(C.x)
Help on property:

    I'm the 'x' property.

装饰器

我们可以使用装饰器符号执行与上面相同的操作,但在这种情况下,我们必须将所有方法命名为相同的名称(我建议将其与属性保持相同),因此编程分配并不像使用上述方法那样简单:

@property
def x(self):
    '''I'm the 'x' property.'''
    return self._x

@x.setter
def x(self, value):
    self._x = value

@x.deleter
def x(self):
    del self._x

并将属性对象及其提供的设置器和删除器分配给该类:

C.x = x

用法和样例:

>>> help(C.x)
Help on property:

    I'm the 'x' property.

>>> o.x
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None

解决方案 6:

这是一个解决方案:

  • 允许将属性名称指定为字符串,以便它们可以来自某些外部数据源,而不是全部在程序中列出。

  • 在定义类时添加属性,而不是每次创建对象时添加。

定义类之后,只需执行以下操作即可动态地向其添加属性:

setattr(SomeClass, 'propertyName', property(getter, setter))

这是一个完整的示例,在 Python 3 中测试:

#!/usr/bin/env python3

class Foo():
  pass

def get_x(self):
  return 3

def set_x(self, value):
  print("set x on %s to %d" % (self, value))

setattr(Foo, 'x', property(get_x, set_x))

foo1 = Foo()
foo1.x = 12
print(foo1.x)

解决方案 7:

对于那些来自搜索引擎的人来说,以下是我在谈论动态属性时所寻找的两件事:

class Foo:
    def __init__(self):
        # we can dynamically have access to the properties dict using __dict__
        self.__dict__['foo'] = 'bar'

assert Foo().foo == 'bar'


# or we can use __getattr__ and __setattr__ to execute code on set/get
class Bar:
    def __init__(self):
        self._data = {}
    def __getattr__(self, key):
        return self._data[key]
    def __setattr__(self, key, value):
        self._data[key] = value

bar = Bar()
bar.foo = 'bar'
assert bar.foo == 'bar'

__dict__如果您想放置动态创建的属性,这很有用。__getattr__只在需要值时执行某些操作(例如查询数据库)很有用。set/get 组合非常适合简化对存储在类中的数据的访问(如上例所示)。

如果您只想要一个动态属性,请查看property()内置函数。

解决方案 8:

您可以使用以下代码通过字典对象更新类属性:

class ExampleClass():
    def __init__(self, argv):
        for key, val in argv.items():
            self.__dict__[key] = val

if __name__ == '__main__':
    argv = {'intro': 'Hello World!'}
    instance = ExampleClass(argv)
    print instance.intro

解决方案 9:

我在 Stack Overflow 的这个帖子上问了一个类似的问题,如何创建一个可以创建简单类型的类工厂。结果得到的答案是这个类工厂的一个工作版本。以下是答案的片段:

def Struct(*args, **kwargs):
    def init(self, *iargs, **ikwargs):
        for k,v in kwargs.items():
            setattr(self, k, v)
        for i in range(len(iargs)):
            setattr(self, args[i], iargs[i])
        for k,v in ikwargs.items():
            setattr(self, k, v)

    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>>

您可以使用它的一些变体来创建默认值,这是您的目标(该问题中也有一个与此相关的答案)。

解决方案 10:

不确定我是否完全理解了这个问题,但是您可以在运行时使用__dict__类的内置功能修改实例属性:

class C(object):
    def __init__(self, ks, vs):
        self.__dict__ = dict(zip(ks, vs))


if __name__ == "__main__":
    ks = ['ab', 'cd']
    vs = [12, 34]
    c = C(ks, vs)
    print(c.ab) # 12

解决方案 11:

property()您无法在运行时向实例添加新内容,因为属性是数据描述符。相反,您必须动态创建新类或重载,__getattribute__以便处理实例上的数据描述符。

解决方案 12:

这与 OP 想要的有点不同,但我绞尽脑汁才找到一个可行的解决方案,所以我把这个答案放在这里供大家参考

我需要一种方法来指定动态 setter 和 getter。

class X:
    def __init__(self, a=0, b=0, c=0):
        self.a = a
        self.b = b
        self.c = c

    @classmethod
    def _make_properties(cls, field_name, inc):
        _inc = inc

        def _get_properties(self):
            if not hasattr(self, '_%s_inc' % field_name):
                setattr(self, '_%s_inc' % field_name, _inc)
                inc = _inc
            else:
                inc = getattr(self, '_%s_inc' % field_name)

            return getattr(self, field_name) + inc

        def _set_properties(self, value):
            setattr(self, '_%s_inc' % field_name, value)

        return property(_get_properties, _set_properties)

我提前知道了我的字段,所以我将创建我的属性。注意:您不能在每个实例上执行此操作,这些属性将存在于类中!!!

for inc, field in enumerate(['a', 'b', 'c']):
    setattr(X, '%s_summed' % field, X._make_properties(field, inc))

现在让我们测试一下这一切……

x = X()
assert x.a == 0
assert x.b == 0
assert x.c == 0

assert x.a_summed == 0  # enumerate() set inc to 0 + 0 = 0
assert x.b_summed == 1  # enumerate() set inc to 1 + 0 = 1
assert x.c_summed == 2  # enumerate() set inc to 2 + 0 = 2

# we set the variables to something
x.a = 1
x.b = 2
x.c = 3

assert x.a_summed == 1  # enumerate() set inc to 0 + 1 = 1
assert x.b_summed == 3  # enumerate() set inc to 1 + 2 = 3
assert x.c_summed == 5  # enumerate() set inc to 2 + 3 = 5

# we're changing the inc now
x.a_summed = 1 
x.b_summed = 3 
x.c_summed = 5

assert x.a_summed == 2  # we set inc to 1 + the property was 1 = 2
assert x.b_summed == 5  # we set inc to 3 + the property was 2 = 5
assert x.c_summed == 8  # we set inc to 5 + the property was 3 = 8

是否令人困惑?是的,很抱歉我无法想出任何有意义的现实世界例子。此外,这不适合轻松的人。

解决方案 13:

实现的最佳方式是定义__slots__。这样您的实例就不能具有新属性。

ks = ['ab', 'cd']
vs = [12, 34]

class C(dict):
    __slots__ = []
    def __init__(self, ks, vs): self.update(zip(ks, vs))
    def __getattr__(self, key): return self[key]

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

打印12

    c.ab = 33

得出:AttributeError: 'C' object has no attribute 'ab'

解决方案 14:

这只是另一个如何实现预期效果的例子

class Foo(object):

    _bar = None

    @property
    def bar(self):
        return self._bar

    @bar.setter
    def bar(self, value):
        self._bar = value

    def __init__(self, dyn_property_name):
        setattr(Foo, dyn_property_name, Foo.bar)

现在我们可以做类似的事情了:

>>> foo = Foo('baz')
>>> foo.baz = 5
>>> foo.bar
5
>>> foo.baz
5

解决方案 15:

虽然给出了很多答案,但我找不到一个让我满意的。我想出了自己的解决方案,它适用property于动态情况。回答原始问题的来源:

#!/usr/local/bin/python3

INITS = { 'ab': 100, 'cd': 200 }

class DP(dict):
  def __init__(self):
    super().__init__()
    for k,v in INITS.items():
        self[k] = v 

def _dict_set(dp, key, value):
  dp[key] = value

for item in INITS.keys():
  setattr(
    DP,
    item,
    lambda key: property(
      lambda self: self[key], lambda self, value: _dict_set(self, key, value)
    )(item)
  )

a = DP()
print(a)  # {'ab': 100, 'cd': 200}
a.ab = 'ab100'
a.cd = False
print(a.ab, a.cd) # ab100 False

解决方案 16:

如果要求根据某些实例属性动态生成属性,那么下面的代码会很有用:

import random  

class Foo:
    def __init__(self, prop_names: List[str], should_property_be_zero: bool = False) -> None:
        self.prop_names = prop_names
        self.should_property_be_zero = should_property_be_zero
        
    def create_properties(self):
        for name in self.prop_names:
            setattr(self.__class__, name, property(fget=lambda self: 0 if self.should_property_be_zero else random.randint(1, 100)))

需要注意的重点是使用setattr(self.__class__, name, ...)而不是setattr(self, name, ...)

使用示例:

In [261]: prop_names = ['a', 'b']

In [262]: ff = Foo(prop_names=prop_names, should_property_be_zero=False)

In [263]: ff.create_properties()

In [264]: ff.a
Out[264]: 10

In [265]: ff.b
Out[265]: 37

In [266]: ft = Foo(prop_names=prop_names, should_property_be_zero=True)

In [267]: ft.create_properties()

In [268]: ft.a
Out[268]: 0

In [269]: ft.b
Out[269]: 0

设置该属性将会AttributeError: can't set attribute按预期引发:

In [270]: ff.a = 5
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-270-5f9cad5b617d> in <module>
----> 1 ff.a = 5

AttributeError: can't set attribute

In [271]: ft.a = 5
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-271-65e7b8e25b67> in <module>
----> 1 ft.a = 5

AttributeError: can't set attribute

解决方案 17:

这似乎有效(但请参见下文):

class data(dict,object):
    def __init__(self,*args,**argd):
        dict.__init__(self,*args,**argd)
        self.__dict__.update(self)
    def __setattr__(self,name,value):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
    def __delattr__(self,name):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)

如果您需要更复杂的行为,请随意编辑您的答案。

编辑

对于大型数据集来说,以下内容可能更节省内存:

class data(dict,object):
    def __init__(self,*args,**argd):
        dict.__init__(self,*args,**argd)
    def __getattr__(self,name):
        return self[name]
    def __setattr__(self,name,value):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
    def __delattr__(self,name):
        raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)

解决方案 18:

为了回答问题的主要内容,您需要从字典中获得一个只读属性作为不可变的数据源:

目标是创建一个行为类似于数据库结果集的模拟类。

例如,如果数据库查询使用字典表达式返回,
{'ab':100, 'cd':200}那么我将看到

>>> dummy.ab
100

我将演示如何使用namedtuple模块collections来实现这一点:

import collections

data = {'ab':100, 'cd':200}

def maketuple(d):
    '''given a dict, return a namedtuple'''
    Tup = collections.namedtuple('TupName', d.keys()) # iterkeys in Python2
    return Tup(**d)

dummy = maketuple(data)
dummy.ab

返回100

解决方案 19:

class atdict(dict):
  def __init__(self, value, **kwargs):
    super().__init__(**kwargs)
    self.__dict = value

  def __getattr__(self, name):
    for key in self.__dict:
      if type(self.__dict[key]) is list:
        for idx, item in enumerate(self.__dict[key]):
          if type(item) is dict:
            self.__dict[key][idx] = atdict(item)
      if type(self.__dict[key]) is dict:
        self.__dict[key] = atdict(self.__dict[key])
    return self.__dict[name]



d1 = atdict({'a' : {'b': [{'c': 1}, 2]}})

print(d1.a.b[0].c)

输出为:

>> 1

解决方案 20:

扩展kjfletch的想法

# This is my humble contribution, extending the idea to serialize
# data from and to tuples, comparison operations and allowing functions
# as default values.

def Struct(*args, **kwargs):
    FUNCTIONS = (types.BuiltinFunctionType, types.BuiltinMethodType, \n                 types.FunctionType, types.MethodType)
    def init(self, *iargs, **ikwargs):
        """Asume that unamed args are placed in the same order than
        astuple() yields (currently alphabetic order)
        """
        kw = list(self.__slots__)

        # set the unnamed args
        for i in range(len(iargs)):
            k = kw.pop(0)
            setattr(self, k, iargs[i])

        # set the named args
        for k, v in ikwargs.items():
            setattr(self, k, v)
            kw.remove(k)

        # set default values
        for k in kw:
            v = kwargs[k]
            if isinstance(v, FUNCTIONS):
                v = v()
            setattr(self, k, v)

    def astuple(self):
        return tuple([getattr(self, k) for k in self.__slots__])

    def __str__(self):
        data = ['{}={}'.format(k, getattr(self, k)) for k in self.__slots__]
        return '<{}: {}>'.format(self.__class__.__name__, ', '.join(data))

    def __repr__(self):
        return str(self)

    def __eq__(self, other):
        return self.astuple() == other.astuple()

    name = kwargs.pop("__name__", "MyStruct")
    slots = list(args)
    slots.extend(kwargs.keys())
    # set non-specific default values to None
    kwargs.update(dict((k, None) for k in args))

    return type(name, (object,), {
        '__init__': init,
        '__slots__': tuple(slots),
        'astuple': astuple,
        '__str__': __str__,
        '__repr__': __repr__,
        '__eq__': __eq__,
    })


Event = Struct('user', 'cmd', \n               'arg1', 'arg2',  \n               date=time.time, \n               __name__='Event')

aa = Event('pepe', 77)
print(aa)
raw = aa.astuple()

bb = Event(*raw)
print(bb)

if aa == bb:
    print('Are equals')

cc = Event(cmd='foo')
print(cc)

输出:

<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
Are equals
<Event: user=None, cmd=foo, arg1=None, arg2=None, date=1550051403.7938335>

解决方案 21:

对我有用的是:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x

    x=property(g,s,d)


c = C()
c.x="a"
print(c.x)

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x)

输出

a
aa

解决方案 22:

kjfletch对匿名类型对象的看法的详细版本。

def construct(*slot_names, type_name: str = None, **initilized_slots):
""" If type_name is specified, creates a type named :type_name: with the specified slot names and values, then returns an instance of it; if no :type_name: is given, constructs an object of the default type.
 'Construct' is used if the :name: is None"""

    class __construct__:
        def __init__(self, *slot_values, **slots):
            self.__dict__.update(initilized_slots)
            for i, name in enumerate(slot_names):
                if i < len(slot_values):
                    setattr(self, name, slot_values[i])
            self.__dict__.update(slots)

        def __repr__(self):
            return '(' + ', '.join(f'{s}={self.__dict__[s]}' for s in initilized_slots.keys()) + ')'

    initilized_slots.update(dict((k, None) for k in slot_names))
    if type_name is None:
        return __construct__(initilized_slots)
    return type(type_name, (__construct__,), {})

优点:

  • 比 更不臃肿namedtuple

  • linter 将对象理解为协议的一个实例,因此不会发出警告

缺点:

  • 酸洗失败

  • 如果你想要的话,没有 eq 等可以添加

在此处输入图片描述

3

解决方案 23:

我知道这是一个老问题,但这里有一种修改类源并插入受保护的类变量和新类属性的方法。

其背后的想法是在运行时动态修改已安装模块中预定义类的源代码。

from inspect import getsource


class DummyClass:
    def __init__(self):
        pass


def generate_new_source(insert_vars: dict):
    source = getsource(DummyClass).splitlines(keepends = True)
    for key, value in reversed(list(insert_vars.items())):
        for index, line in enumerate(source.copy()):
            if '__init__' in line:
                source.insert(index + 1, f'        self._{key} = {value}
')

    return ''.join(source)


def insert_new_properties(insert_props: list):
    source = '''
@property
def {prop}(self):
    return self._{prop}
'''

    for insert_prop in insert_props:
        exec(str.format(source, prop = insert_prop))
        setattr(DummyClass, insert_prop, eval(insert_prop))


exec(generate_new_source(insert_vars = {'ab': 100, 'cd': 200}))
insert_new_properties(insert_props = ['ab', 'cd'])

dummy = DummyClass()

# noinspection PyUnresolvedReferences
print(dummy.__dict__, dummy.ab, dummy.cd, sep = ', ')

返回:

>>> {'_ab': 100, '_cd': 200}, 100, 200

解决方案 24:

我最近遇到了类似的问题,我想到的解决方案用途__getattr__以及__setattr__我希望它处理的属性,其他一切都传递给原始内容。

class C(object):
    def __init__(self, properties):
        self.existing = "Still Here"
        self.properties = properties

    def __getattr__(self, name):
        if "properties" in self.__dict__ and name in self.properties:
            return self.properties[name] # Or call a function, etc
        return self.__dict__[name]

    def __setattr__(self, name, value):
        if "properties" in self.__dict__ and name in self.properties:
            self.properties[name] = value
        else:
            self.__dict__[name] = value

if __name__ == "__main__":
    my_properties = {'a':1, 'b':2, 'c':3}
    c = C(my_properties)
    assert c.a == 1
    assert c.existing == "Still Here"
    c.b = 10
    assert c.properties['b'] == 10

解决方案 25:

以下是以编程方式创建属性对象的简单示例。

#!/usr/bin/python3

class Counter:
    def __init__(self):
        cls = self.__class__
        self._count = 0
        cls.count = self.count_ref()

    def count_get(self):
        print(f'count_get: {self._count}')
        return self._count

    def count_set(self, value):
        self._count = value
        print(f'count_set: {self._count}')

    def count_del(self):
        print(f'count_del: {self._count}')

    def count_ref(self):
        cls = self.__class__
        return property(fget=cls.count_get, fset=cls.count_set, fdel=cls.count_del)

counter = Counter()

counter.count
for i in range(5):
    counter.count = i
del counter.count

'''
output
======
count_get: 0
count_set: 0
count_set: 1
count_set: 2
count_set: 3
count_set: 4
count_del: 4
'''

解决方案 26:

动态附加属性的唯一方法是使用新属性创建一个新类及其实例。

class Holder: p = property(lambda x: vs[i], self.fn_readonly)
setattr(self, k, Holder().p)

解决方案 27:

许多提供的答案要求每个属性有很多行,即/和/或 - 我认为这是一个丑陋或乏味的实现,因为多个属性需要重复,等等。我更喜欢把事情简化,直到它们无法再简化,或者直到这样做没有多大用处。

简而言之:在完成的作品中,如果我重复两行代码,我通常会将其转换为单行辅助函数,依此类推...我简化数学或奇数参数,例如(start_x,start_y,end_x,end_y)为(x,y,w,h),即 x,y,x + w,y + h(有时需要最小值/最大值或如果 w / h 为负数并且实现不喜欢它,我将从 x / y 和 abs w / h 中减去。等等。)。

覆盖内部 getter / setter 是一种可行的方法,但问题是您需要对每个类都执行此操作,或者将类作为该基类的父类...这对我来说不起作用,因为我更喜欢自由选择继承的子类 / 父类、子节点等。

我已经创建了一个解决方案,它可以回答该问题,而无需使用 Dict 数据类型来提供数据,因为我发现输入数据等很繁琐...

我的解决方案要求您在类上方添加 2 行额外代码来为要添加属性的类创建一个基类,然后每行添加 1 行,您可以选择添加回调来控制数据,在数据发生变化时通知您,限制可以根据值和/或数据类型设置的数据,等等。

您还可以选择使用 _object.x、_object.x = value、_object.GetX( )、_object.SetX( value ),它们的处理方式相同。

此外,值是分配给类实例的唯一非静态数据,但实际属性被分配给类,这意味着您不想重复的事情不需要重复...您可以分配一个默认值,以便 getter 每次都不需要它,虽然有一个选项可以覆盖默认的默认值,还有另一个选项,因此 getter 通过覆盖默认返回来返回原始存储的值(注意:此方法意味着仅在分配值时分配原始值,否则为 None - 当值被重置时,它会分配 None,等等。)

还有许多辅助函数 - 第一个添加的属性会向类中添加 2 个左右的辅助函数,用于引用实例值...它们是 ResetAccessors( _key, .. ) 重复的可变参数(可以使用第一个命名参数重复所有参数)和 SetAccessors( _key, _value ),可以选择将更多函数添加到主类以提高效率 - 计划中的函数是:一种将访问器分组在一起的方法,因此如果您倾向于每次重置几个访问器,您可以将它们分配给一个组并重置该组,而不是每次重复命名键,等等。

实例/原始存储值存储在类中。,__class。引用保存属性的静态变量/值/函数的访问器类。_class。是属性本身,在设置/获取等期间通过实例类访问时调用。

Accessor class._ 指向类,但因为它是内部的,所以需要在类中分配,这就是我选择使用 __Name = AccessorFunc( ... ) 来分配它的原因,每个属性一行,有许多可选参数可供选择(使用键控可变参数,因为它们更容易、更有效地识别和维护)...

如上所述,我还创建了很多函数,其中一些函数使用访问器函数信息,因此不需要调用它(因为目前有点不方便 - 现在您需要使用 _class。 .FunctionName(_class_instance,args) - 我使用堆栈/跟踪来获取实例引用,通过添加运行此位马拉松的函数来获取值,或者通过将访问器添加到对象并使用self(命名为this以指出它们是用于实例并保留对自身的访问,AccessorFunc类引用,以及函数定义中的其他信息)。

虽然还没有完全完成,但这是一个很棒的立足点。注意:如果您不使用 Name = AccessorFunc( ... ) 来创建属性,您将无法访问 键,即使我在 init 函数中定义了它。如果您这样做,那么就没有问题。

另:请注意,Name 和 Key 是不同的...Name 是“正式的”,用于函数名称创建,而 key 用于数据存储和访问。例如,_class.x 其中小写的 x 是 key,名称将是大写的 X,以便 GetX() 是函数而不是 Getx(),这看起来有点奇怪。这允许 self.x 工作并且看起来合适,但也允许 GetX() 并且看起来合适。

我有一个示例类,其中的键/名称相同,但显示不同。为了输出数据创建了很多辅助函数(注意:并非所有这些都是完整的),这样您就可以看到发生了什么。

当前使用 key: x, name: X 的函数列表输出为:

这绝不是一个完整的列表 - 在发布时有一些内容尚未列入其中......

_instance.SetAccessors( _key, _value [ , _key, _value ] .. )                   Instance Class Helper Function: Allows assigning many keys / values on a single line - useful for initial setup, or to minimize lines.    In short: Calls this.Set<Name>( _value ) for each _key / _value pairing.
_instance.ResetAccessors( _key [ , _key ] .. )                                 Instance Class Helper Function: Allows resetting many key stored values to None on a single line.                                           In short: Calls this.Reset<Name>() for each name provided.


Note: Functions below may list self.Get / Set / Name( _args ) - self is meant as the class instance reference in the cases below - coded as this in AccessorFuncBase Class.

this.GetX( _default_override = None, _ignore_defaults = False )                 GET:            Returns    IF ISSET: STORED_VALUE .. IF IGNORE_DEFAULTS: None  .. IF PROVIDED: DEFAULT_OVERRIDE ELSE: DEFAULT_VALUE       100
this.GetXRaw( )                                                                 RAW:            Returns    STORED_VALUE                                                                                                     100
this.IsXSet( )                                                                  ISSET:          Returns    ( STORED_VALUE != None )                                                                                         True

this.GetXToString( )                                                            GETSTR:         Returns    str( GET )                                                                                                       100
this.GetXLen( _default_override = None, _ignore_defaults = False )              LEN:            Returns    len( GET )                                                                                                       3
this.GetXLenToString( _default_override = None, _ignore_defaults = False )      LENSTR:         Returns    str( len( GET ) )                                                                                                3
this.GetXDefaultValue( )                                                        DEFAULT:        Returns    DEFAULT_VALUE                                                                                                    1111

this.GetXAccessor( )                                                            ACCESSOR:       Returns    ACCESSOR_REF ( self.__<key> )                                                                                    [ AccessorFuncBase ] Key: x : Class ID: 2231452344344 : self ID: 2231448283848        Default: 1111       Allowed Types: {"<class 'int'>": "<class 'type'>", "<class 'float'>": "<class 'type'>"}     Allowed Values: None
this.GetXAllowedTypes( )                                                        ALLOWED_TYPES:  Returns    Allowed Data-Types                                                                                               {"<class 'int'>": "<class 'type'>", "<class 'float'>": "<class 'type'>"}
this.GetXAllowedValues( )                                                       ALLOWED_VALUES: Returns    Allowed Values                                                                                                   None

this.GetXHelpers( )                                                             HELPERS:        Returns    Helper Functions String List - ie what you're reading now...                                                     THESE ROWS OF TEXT
this.GetXKeyOutput( )                                                           Returns information about this Name / Key                                                                                                   ROWS OF TEXT
this.GetXGetterOutput( )                                                        Returns information about this Name / Key                                                                                                   ROWS OF TEXT

this.SetX( _value )                                                             SET:            STORED_VALUE Setter - ie Redirect to __<Key>.Set                                                                            N / A
this.ResetX( )                                                                  RESET:          Resets STORED_VALUE to None                                                                                                 N / A

this.HasXGetterPrefix( )                                                        Returns Whether or Not this key has a Getter Prefix...                                                                                      True
this.GetXGetterPrefix( )                                                        Returns Getter Prefix...                                                                                                                    Get

this.GetXName( )                                                                Returns Accessor Name - Typically Formal / Title-Case                                                                                       X
this.GetXKey( )                                                                 Returns Accessor Property Key - Typically Lower-Case                                                                                        x
this.GetXAccessorKey( )                                                         Returns Accessor Key - This is to access internal functions, and static data...                                                             __x
this.GetXDataKey( )                                                             Returns Accessor Data-Storage Key - This is the location where the class instance value is stored..                                         _x

输出的一些数据是:

这是使用 Demo 类创建的一个全新类,除了名称(因此可以输出)之外没有分配任何数据,即 _foo(我使用的变量名)...

_foo         --- MyClass: ---- id( this.__class__ ): 2231452349064 :::: id( this ): 2231448475016

    Key       Getter Value        | Raw Key   Raw / Stored Value       | Get Default Value             Default Value            | Get Allowed Types             Allowed Types                                                              | Get Allowed Values            Allowed Values                                                                                                                                                                                                                   |

    Name:     _foo                | _Name:    _foo                     | __Name.DefaultValue( ):       AccessorFuncDemoClass    | __Name.GetAllowedTypes( )     <class 'str'>                                                              | __Name.GetAllowedValues( )    Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    x:        1111                | _x:       None                     | __x.DefaultValue( ):          1111                     | __x.GetAllowedTypes( )        (<class 'int'>, <class 'float'>)                                           | __x.GetAllowedValues( )       Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    y:        2222                | _y:       None                     | __y.DefaultValue( ):          2222                     | __y.GetAllowedTypes( )        (<class 'int'>, <class 'float'>)                                           | __y.GetAllowedValues( )       Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    z:        3333                | _z:       None                     | __z.DefaultValue( ):          3333                     | __z.GetAllowedTypes( )        (<class 'int'>, <class 'float'>)                                           | __z.GetAllowedValues( )       Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    Blah:     <class 'int'>       | _Blah:    None                     | __Blah.DefaultValue( ):       <class 'int'>            | __Blah.GetAllowedTypes( )     <class 'str'>                                                              | __Blah.GetAllowedValues( )    Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    Width:    1                   | _Width:   None                     | __Width.DefaultValue( ):      1                        | __Width.GetAllowedTypes( )    (<class 'int'>, <class 'bool'>)                                            | __Width.GetAllowedValues( )   Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    Height:   0                   | _Height:  None                     | __Height.DefaultValue( ):     0                        | __Height.GetAllowedTypes( )   <class 'int'>                                                              | __Height.GetAllowedValues( )  (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)                                                                                                                                                                                                   |
    Depth:    2                   | _Depth:   None                     | __Depth.DefaultValue( ):      2                        | __Depth.GetAllowedTypes( )    Saved Value Restricted to Authorized Values ONLY                           | __Depth.GetAllowedValues( )   (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)                                                                                                                                                                                                   |


this.IsNameSet( ):    True      this.GetName( ):     _foo                     this.GetNameRaw( ):    _foo                     this.GetNameDefaultValue( ):    AccessorFuncDemoClass    this.GetNameLen( ):    4    this.HasNameGetterPrefix( ):    <class 'str'>                                this.GetNameGetterPrefix( ):    None
this.IsXSet( ):       False     this.GetX( ):        1111                     this.GetXRaw( ):       None                     this.GetXDefaultValue( ):       1111                     this.GetXLen( ):       4    this.HasXGetterPrefix( ):       (<class 'int'>, <class 'float'>)             this.GetXGetterPrefix( ):       None
this.IsYSet( ):       False     this.GetY( ):        2222                     this.GetYRaw( ):       None                     this.GetYDefaultValue( ):       2222                     this.GetYLen( ):       4    this.HasYGetterPrefix( ):       (<class 'int'>, <class 'float'>)             this.GetYGetterPrefix( ):       None
this.IsZSet( ):       False     this.GetZ( ):        3333                     this.GetZRaw( ):       None                     this.GetZDefaultValue( ):       3333                     this.GetZLen( ):       4    this.HasZGetterPrefix( ):       (<class 'int'>, <class 'float'>)             this.GetZGetterPrefix( ):       None
this.IsBlahSet( ):    False     this.GetBlah( ):     <class 'int'>            this.GetBlahRaw( ):    None                     this.GetBlahDefaultValue( ):    <class 'int'>            this.GetBlahLen( ):    13   this.HasBlahGetterPrefix( ):    <class 'str'>                                this.GetBlahGetterPrefix( ):    None
this.IsWidthSet( ):   False     this.GetWidth( ):    1                        this.GetWidthRaw( ):   None                     this.GetWidthDefaultValue( ):   1                        this.GetWidthLen( ):   1    this.HasWidthGetterPrefix( ):   (<class 'int'>, <class 'bool'>)              this.GetWidthGetterPrefix( ):   None
this.IsDepthSet( ):   False     this.GetDepth( ):    2                        this.GetDepthRaw( ):   None                     this.GetDepthDefaultValue( ):   2                        this.GetDepthLen( ):   1    this.HasDepthGetterPrefix( ):   None                                         this.GetDepthGetterPrefix( ):   (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
this.IsHeightSet( ):  False     this.GetHeight( ):   0                        this.GetHeightRaw( ):  None                     this.GetHeightDefaultValue( ):  0                        this.GetHeightLen( ):  1    this.HasHeightGetterPrefix( ):  <class 'int'>                                this.GetHeightGetterPrefix( ):  (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

这是在以相同顺序为所有 _foo 属性(名称除外)分配以下值之后的结果:'string',1.0,True,9,10,False

this.IsNameSet( ):    True      this.GetName( ):     _foo                     this.GetNameRaw( ):    _foo                     this.GetNameDefaultValue( ):    AccessorFuncDemoClass    this.GetNameLen( ):    4    this.HasNameGetterPrefix( ):    <class 'str'>                                this.GetNameGetterPrefix( ):    None
this.IsXSet( ):       True      this.GetX( ):        10                       this.GetXRaw( ):       10                       this.GetXDefaultValue( ):       1111                     this.GetXLen( ):       2    this.HasXGetterPrefix( ):       (<class 'int'>, <class 'float'>)             this.GetXGetterPrefix( ):       None
this.IsYSet( ):       True      this.GetY( ):        10                       this.GetYRaw( ):       10                       this.GetYDefaultValue( ):       2222                     this.GetYLen( ):       2    this.HasYGetterPrefix( ):       (<class 'int'>, <class 'float'>)             this.GetYGetterPrefix( ):       None
this.IsZSet( ):       True      this.GetZ( ):        10                       this.GetZRaw( ):       10                       this.GetZDefaultValue( ):       3333                     this.GetZLen( ):       2    this.HasZGetterPrefix( ):       (<class 'int'>, <class 'float'>)             this.GetZGetterPrefix( ):       None
this.IsBlahSet( ):    True      this.GetBlah( ):     string Blah              this.GetBlahRaw( ):    string Blah              this.GetBlahDefaultValue( ):    <class 'int'>            this.GetBlahLen( ):    11   this.HasBlahGetterPrefix( ):    <class 'str'>                                this.GetBlahGetterPrefix( ):    None
this.IsWidthSet( ):   True      this.GetWidth( ):    False                    this.GetWidthRaw( ):   False                    this.GetWidthDefaultValue( ):   1                        this.GetWidthLen( ):   5    this.HasWidthGetterPrefix( ):   (<class 'int'>, <class 'bool'>)              this.GetWidthGetterPrefix( ):   None
this.IsDepthSet( ):   True      this.GetDepth( ):    9                        this.GetDepthRaw( ):   9                        this.GetDepthDefaultValue( ):   2                        this.GetDepthLen( ):   1    this.HasDepthGetterPrefix( ):   None                                         this.GetDepthGetterPrefix( ):   (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
this.IsHeightSet( ):  True      this.GetHeight( ):   9                        this.GetHeightRaw( ):  9                        this.GetHeightDefaultValue( ):  0                        this.GetHeightLen( ):  1    this.HasHeightGetterPrefix( ):  <class 'int'>                                this.GetHeightGetterPrefix( ):  (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

_foo         --- MyClass: ---- id( this.__class__ ): 2231452349064 :::: id( this ): 2231448475016

    Key       Getter Value        | Raw Key   Raw / Stored Value       | Get Default Value             Default Value            | Get Allowed Types             Allowed Types                                                              | Get Allowed Values            Allowed Values                                                                                                                                                                                                                   |

    Name:     _foo                | _Name:    _foo                     | __Name.DefaultValue( ):       AccessorFuncDemoClass    | __Name.GetAllowedTypes( )     <class 'str'>                                                              | __Name.GetAllowedValues( )    Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    x:        10                  | _x:       10                       | __x.DefaultValue( ):          1111                     | __x.GetAllowedTypes( )        (<class 'int'>, <class 'float'>)                                           | __x.GetAllowedValues( )       Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    y:        10                  | _y:       10                       | __y.DefaultValue( ):          2222                     | __y.GetAllowedTypes( )        (<class 'int'>, <class 'float'>)                                           | __y.GetAllowedValues( )       Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    z:        10                  | _z:       10                       | __z.DefaultValue( ):          3333                     | __z.GetAllowedTypes( )        (<class 'int'>, <class 'float'>)                                           | __z.GetAllowedValues( )       Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    Blah:     string Blah         | _Blah:    string Blah              | __Blah.DefaultValue( ):       <class 'int'>            | __Blah.GetAllowedTypes( )     <class 'str'>                                                              | __Blah.GetAllowedValues( )    Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    Width:    False               | _Width:   False                    | __Width.DefaultValue( ):      1                        | __Width.GetAllowedTypes( )    (<class 'int'>, <class 'bool'>)                                            | __Width.GetAllowedValues( )   Saved Value Restrictions Levied by Data-Type                                                                                                                                                                                     |
    Height:   9                   | _Height:  9                        | __Height.DefaultValue( ):     0                        | __Height.GetAllowedTypes( )   <class 'int'>                                                              | __Height.GetAllowedValues( )  (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)                                                                                                                                                                                                   |
    Depth:    9                   | _Depth:   9                        | __Depth.DefaultValue( ):      2                        | __Depth.GetAllowedTypes( )    Saved Value Restricted to Authorized Values ONLY                           | __Depth.GetAllowedValues( )   (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)                                                                                                                                                                                                   |

请注意,由于数据类型或值限制,某些数据未分配 - 这是设计使然。setter 禁止分配不良数据类型或值,甚至禁止将其分配为默认值(除非您覆盖默认值保护行为)

代码没有发布在这里,因为在示例和解释之后我没有空间......也因为它会改变。

请注意:在发布此信息时,文件很乱 - 这会改变。但是,如果你在 Sublime Text 中运行并编译它,或者从 Python 运行它,它将编译并输出大量信息 - AccessorDB 部分尚未完成(它将用于更新 Print Getters 和 GetKeyOutput 辅助函数,并更改为实例函数,可能放入单个函数并重命名 - 查找它..)

下一步:运行它并不需要所有东西 - 底部的很多注释内容是为了提供用于调试的更多信息 - 下载时可能不存在。如果存在,您应该能够取消注释并重新编译以获取更多信息。

我正在寻找一种解决方法来满足 MyClassBase:pass,MyClass(MyClassBase):... - 如果您知道解决方案 - 请发布。

类中唯一需要的是 __ 行 - str用于调试, init也是如此- 它们可以从演示类中删除,但您需要注释掉或删除下面的某些行( _foo / 2 / 3 )..

顶部的 String、Dict 和 Util 类是我的 Python 库的一部分 - 它们并不完整。我从库中复制了一些我需要的东西,并创建了一些新的东西。完整的代码将链接到完整的库并将其包括在内,同时提供更新的调用和删除代码(实际上,剩下的唯一代码将是 Demo 类和打印语句 - AccessorFunc 系统将被移动到库中)...

部分文件:

##
## MyClass Test AccessorFunc Implementation for Dynamic 1-line Parameters
##
class AccessorFuncDemoClassBase( ):
    pass
class AccessorFuncDemoClass( AccessorFuncDemoClassBase ):
    __Name      = AccessorFuncBase( parent = AccessorFuncDemoClassBase, name = 'Name',      default = 'AccessorFuncDemoClass',  allowed_types = ( TYPE_STRING ),                    allowed_values = VALUE_ANY,                 documentation = 'Name Docs',        getter_prefix = 'Get',  key = 'Name',       allow_erroneous_default = False,    options = { } )
    __x         = AccessorFuncBase( parent = AccessorFuncDemoClassBase, name = 'X',         default = 1111,                     allowed_types = ( TYPE_INTEGER, TYPE_FLOAT ),       allowed_values = VALUE_ANY,                 documentation = 'X Docs',           getter_prefix = 'Get',  key = 'x',          allow_erroneous_default = False,    options = { } )
    __Height    = AccessorFuncBase( parent = AccessorFuncDemoClassBase, name = 'Height',    default = 0,                        allowed_types = TYPE_INTEGER,                       allowed_values = VALUE_SINGLE_DIGITS,       documentation = 'Height Docs',      getter_prefix = 'Get',  key = 'Height',     allow_erroneous_default = False,    options = { } )

这种美妙之处在于,使用 AccessorFuncs / 回调 / 数据类型 / 值强制等动态添加属性来创建新类变得非常容易。

目前,链接位于(此链接应反映对文档的更改。): https: //www.dropbox.com/s/6gzi44i7dh58v61/dynamic_properties_accessorfuncs_and_more.py ?dl=0

另外:如果您不使用 Sublime Text,我推荐它而不是 Notepad++、Atom、Visual Code 和其他程序,因为它具有适当的线程实现,使用起来速度更快……我还在为它开发一个类似 IDE 的代码映射系统 - 请查看:https ://bitbucket.org/Acecool/acecoolcodemappingsystem/src/master/ (首先在包管理器中添加 Repo,然后安装插件 - 当 1.0.0 版本准备就绪时,我会将其添加到主插件列表中……)

我希望这个解决方案有所帮助...并且,一如既往:

仅仅因为它有效,并不意味着它是正确的 - Josh 'Acecool' Moser

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用