首页 > 解决方案 > 在 Python 中动态创建具有特定类型的变量

问题描述

在读取带有值的文本文件时,我想动态创建变量。

我将值存储在列表列表中:

values = list(content[startline].split() for i in range(_n_lines))

content是行列表

变量名称存储在元组的元组中,具体取决于我正在阅读的块:

variable_names = (
    ('_idx_brg', 'stn', 'stn_rel', '_n_lines', '_brg_type'),
    ('D', 'L', 'Cb', 'visc'),
    ('stiff', 'damp'),
    ('damp_model', ''))

默认情况下,我将值转换为浮点数:

for irow,row in enumerate(variable_names):
    for icol,col in enumerate(row):
        if col:
            val = float(values[irow][icol])
            setattr(self, col, val)

这是我的问题:

在某些情况下,我需要不同的类型,并且我想避免使用另一个列表。有没有一种简洁的方法来为每个变量提供一个类型?我想过将信息放入variable_names,但这对我来说似乎是错误的。

我很乐意提供任何建议。也适用于我已经在使用的部分。

*编辑@Rory

这是所述示例的示例输入文本块

6 28 0 4 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Some comments here
12.527 4.6 0.0365 3.5 0 0 0 0 0 0
0 0  0  0 0 0 0 0 0 0
0  0  0  0 0 0 0 0 0 0
0  0  0  0 0 0 0 0 0 0

输入文件有几个像这样的块,当然还有一些具有另一种格式。块的识别在脚本的其他地方完成。

如您所见,我并不总是阅读整个区块。

标签: python

解决方案


好吧,无需深入了解嵌套的细节,您可以使用元组将变量类型附加到名称。

我在你的两个变量名上做了这个:('_idx_brg',str), ('stn','int')

而不是使用zip,您需要将它连接到您的嵌套元组,并且您还需要添加错误处理,以防文件中的字符串值不符合预期的变量类型。

import builtins
import pdb

def set_attr(tgt, names, values):

    try:
        for name, value in zip(names, values):
            cls_ = None
            if isinstance(name, str):
                setattr(tgt, name, float(value))
            elif isinstance(name, tuple):
                name, cls_ = name
                if callable(cls_):
                    setattr(tgt, name, cls_(value))
                elif isinstance(cls_, str):
                    cls_ = globals().get(cls_) or getattr(builtins, cls_)
                    setattr(tgt, name, cls_(value))
                else:
                    raise ValueError("variable types have to be a string or callable like `int`,`float`, etc")
    except (ValueError,TypeError,AttributeError) as e: 
        print(f"  somethings wrong:\n{dict(exception=e, name=name, cls_=cls_, value=value)}")
        #raise 

    #pragma: no cover pylint: disable=unused-variable
    except (Exception,) as e: 
        if 1: 
            pdb.set_trace()
        raise

class Foo:
    pass

variable_names = ('_idx_brg', 'stn', 'stn_rel', '_n_lines', '_brg_type')
values = (1.0, 1, 1.2, 1.3, 1.4, 1.5)

foo = Foo()

print("\n\nsetting for foo")
set_attr(foo, variable_names, values) 

print("\n\nfoo:", vars(foo))

variable_names2 = (('_idx_brg',str), ('stn','int'), 'stn_rel', '_n_lines', ('_brg_type','xxx'))

bar = Foo()

print("\n\nsetting for bar:")
set_attr(bar, variable_names2, values) 

print("\n\nbar:", vars(bar))

输出:



setting for foo


foo: {'_idx_brg': 1.0, 'stn': 1.0, 'stn_rel': 1.2, '_n_lines': 1.3, '_brg_type': 1.4}


setting for bar:
  somethings wrong:
{'exception': AttributeError("module 'builtins' has no attribute 'xxx'"), 'name': '_brg_type', 'cls_': 'xxx', 'value': 1.4}


bar: {'_idx_brg': '1.0', 'stn': 1, 'stn_rel': 1.2, '_n_lines': 1.3}
                              

你甚至可以建立自己的课程。

class Myclass:
   def __init__(self, value):
      self.value = value

#part of your name/type tuples...
(('somevar', Myclass), ('_idx_brg',str)...)

编辑重新。yaml:

我没有对此进行测试,因此您可能需要进行一些调整,尤其是围绕确切的 yaml 来获得包含嵌套varnames字典的字典。

---
varnames:
  _idx_brg: str
  stn : int
from yaml import safe_load as yload
with open("myconfig.yaml") as fi:
  config = yload(li)

mapping = {}

#the yaml is all strings right now
# map it to actual types/classes
for name, type_ in config["varnames"].items():
    cls_ = globals().get(type_) or getattr(builtins, type_)
    mapping[name] = cls_

#using it
for name, value in zip(names, values):

    #fall back to `float` if there is no special-case for this varname
    cls_ = mapping.get(name, float)
    setattr(tgt, name, cls_(value))

现在,这确实依赖于具有相同类型的给定变量名称的所有实例,无论数据层次结构中的哪个位置,但这只是最佳实践。

另一件事是,如果我有一个区域对我来说看起来有点可疑/脆弱,那就是您的复杂嵌套与值和名称的元组,不知何故需要始终保持同步。比您加载文本数据(其格式不受您控制)的基本要求要多得多,然后以不同的方式对其进行格式化。不知何故,我会努力让你的名字更自然地与数据一起流动。也许尝试通过记录类型识别传入数据,然后为其分配一个映射类?实际上,与您正在做的事情相同,只是不依赖于复杂的嵌套。

或者,也许,从您关于行、列的评论开始,您也可以将所有这些都放入 yaml 配置文件中,将其加载到映射数据结构中并显式使用索引而不是嵌套循环?可能会使您的代码更易于推理和调整数据更改。

Python 数据解析领域也有一些有趣的东西,比如Pydantic。可能有帮助,也可能没有帮助。


推荐阅读