【问题标题】:How to check instantiated class attributes types?如何检查实例化的类属性类型?
【发布时间】:2018-05-15 00:05:36
【问题描述】:

我正在尝试检查我实例化的对象是否基本上实例化了子对象,以便我可以依次继续这种方式,直到我完成检查/错误检查输入/填充我的对象。目标是在不知道属性是什么的情况下填充该对象的信息,因为它们是由其他代码自动生成的,因此它们可能会发生更改,恕不另行通知。我正在尝试使用递归函数来做到这一点。举个例子可能会更清楚一点:

### content of B.py:
class B:
    def __init__(self):
        self.Ba = ''
        self.Bb = 0


### content of A.py
from B import B

class A:
    def __init__(self):
        self.B = B()
        self.Aa = ''
        self.Ab = False


### content of SDK.py
from A import A
primitiveTypes = (int, str, bool, type(None))

class G:
    def createIt(self, **options)
        fillMe = A()
        fillMe = self.fillWithData(fillMe, options)
        #Do stuff with fillMe (post in JSON format to REST API...)

    def fillWithData(self, obj, options)
        for a in dir(obj):
            if not callable(a) and not a.startswith('_'):
                if isinstance(a, primitiveTypes):
                    obj.__dict__.update({a: options.get(a)})
                else:
                    #This never gets executed because isinstance always returns true -> because it evaluates A.B as type string instead of being of type B
                    obj.__dict__.update({a: self.fillWithData(a, options)})

### content of test.py
from SDK import G

g = G()
g.createIt(Aa='Aa', Ab=True, Ba='Ba', Bb=1) #Make correct input succeed
g.createIt(Aa=1, Ab='', Ba=None, Bb=False) #Make incorrect input fail

编辑:我删除了退货声明,感谢您消除了我的疑虑。

解释为什么我需要所有这些不同的文件;我目前正在编写一个 SDK,以便人们可以使用它与 REST API 交互(创建用户、登录、添加内容、修改内容、删除内容 - 角色、权限等内容)所以我们有代码C# 定义模型和枚举(无方法),由代码生成器转换为 python。在上面的示例中,模型将是 A.py 和 B.py。我将它们导入 SDK (G.py),所以我有 test.py 来使用 SDK 方法。

所以问题是:isinstance 总是返回 true - 因为它认为对象 b 是 String 类型,而实际上它应该是 B 类型。

【问题讨论】:

  • 您不需要G.fillWithData() 中的return obj,因为您对返回值所做的只是将其分配给G.creatIt() 中名为fillMe 的局部变量,这与它(所以它被丢弃了)。在您问题的最后两行代码中,尚不清楚其中一个是正确的,而另一个是正确的。请edit您提出问题并描述每种情况下结果的正确性和错误性。
  • @martineau 实际上,self.fillWithData(a, options)} 被递归调用,并且返回值将在fillWithDataelse 正文中使用,尽管正如您所说,fillMe 没有使用,所以它可能应该是一个实例变量?或者也许所有的G 根本不需要是一个类。
  • @juanpa.arrivillaga:即使递归调用,初始调用者也不会使用最终返回值。
  • 你为什么要迭代 dir 开始?为什么不直接vars__dict__
  • @martineau 是的,不过,我怀疑这是一个错字或错误,因为那样整个事情就毫无意义

标签: python object types attributes instance


【解决方案1】:

所以,有几点。首先,与其创建一个“原始类型”元组(注意,Python 没有原始类型),枚举你想要处理的类型更有意义,所以:

In [1]: class B:
   ...:     def __init__(self):
   ...:         self.Ba = ''
   ...:         self.Bb = 0
   ...:

In [2]: class A:
   ...:     def __init__(self):
   ...:         self.B = B()
   ...:         self.Aa = ''
   ...:         self.Ab = False
   ...:

In [3]: registered_types = A, B

其次,不需要 G 类,它不保存任何状态,只会使事情复杂化。如果您的实际用例可以通过成为一个类来改进,请继续调整它,但为了清楚起见,我将只使用一个普通函数:

In [5]: SENTINEL = object()

In [6]: def initialize(obj, **options):
    ...:     for name, subobj in vars(obj).items():
    ...:         if isinstance(subobj, registered_types):
    ...:             filled = initialize(subobj, **options)
    ...:             setattr(obj, name, filled)
    ...:         else:
    ...:             val = options.get(name, SENTINEL)
    ...:             if val is not SENTINEL:
    ...:                 setattr(obj, name, val)
    ...:     return obj
    ...:

注意,我使用 vars 检查 obj 的实例属性,所有 vars(obj) 所做的只是返回 obj.__dict__,这是您想要的,而不是检查 if x.startswith('_') 等 @987654328 返回的内容@ (实际上并不打算在生产中使用,而是更多地用于调试)。另外,我迭代了varsitems,因为那个(和dir)返回一个dict 对象,并且直接迭代它会迭代总是的键字符串 这是你之前处理的。另请注意,我创建了一个任意的SENTINEL 对象只是为了检查而不是None,这可能是一个有效的目标。另外,我使用 setattr 而不是更丑的 obj.__dict__.update(...) 构造。

最后,结果:

In [7]: a = A()

In [8]: a = initialize(a, Aa='Aa', Ab=True, Ba='Ba', Bb=1)

In [9]: a.Aa, a.Ab, a.B.Ba, a.B.Bb
Out[9]: ('Aa', True, 'Ba', 1)

【讨论】:

  • 谢谢,我确实也想过这样尝试。虽然,我认为问题仍然会出现。即 isinstance() 将始终将属性评估为字符串,而不是 register_types。我一定是做错了什么。
【解决方案2】:

感谢@juanpa.arrivillaga,解决这个问题的关键是使用价值而不是关键(现在听起来很明显,哈哈)这是我们最终解决它的方法:

def fillWithData(self, obj, options):
keysToDelete = []
for name, subObj in vars(obj).items():
    if isinstance(subObj, primitiveTypes):
        obj.__dict__.update({name: options.get(name)})
        if obj.__dict__[name] is None: #To remove keys with empty values later
            keysToDelete.append(name)
    elif not isinstance(subObj, types.FunctionType) and not isinstance(subObj, Enum) and not name.startswith('_'):
        obj.__dict__.update({name: self.fillData(subObj, options)})
for k in keysToDelete: #Remove keys with empty values
    del obj.__dict__[k]
return obj

下一步......清理这个东西,所以我实际上并没有用空值填充对象,然后删除它们......;)->也许将来会在另一个问题中呵呵:P

【讨论】:

  • 是的,我正要回复您在我的回答中留下的关于键/字符串问题的评论:)。一个建议,就风格而言,我认为使用 getattrsetattrdelattr 等内置函数而不是直接操作 obj.__dict__ 看起来更简洁,IMO 也更具可读性。跨度>
  • 是的,谢谢,我实际上在我的实际代码中更改了它?
猜你喜欢
  • 2022-06-15
  • 2015-03-07
  • 2022-10-18
  • 1970-01-01
  • 2016-03-16
  • 2020-05-20
  • 2012-08-11
  • 1970-01-01
  • 2022-08-18
相关资源
最近更新 更多