我有这个:
user_dir: /home/user
user_pics: /home/user/pics
我如何使用 user_dir
存储用户照片?如果我必须指定其他属性,这将不太符合DRY原则。
我有这个:
user_dir: /home/user
user_pics: /home/user/pics
我如何使用 user_dir
存储用户照片?如果我必须指定其他属性,这将不太符合DRY原则。
user_dir: &user_home /home/user
user_pics: *user_home
我认为你无法进行字符串拼接,因此这个方法不会起作用:
user_dir: &user_home /home/user
user_pics: *user_home/pics
令人惊讶的是,由于YAML锚点和引用的目的是将重复内容从YAML数据文件中分离出来,竟然没有一种内置的方法可以使用引用来连接字符串。您的用例是从多个部分构建路径名,这是一个很好的示例。一定有很多类似的用途。
幸运的是,通过用户定义标签,可以很容易地将字符串连接添加到YAML中。
用户定义标签是标准的YAML功能-YAML 1.2规范表示YAML模式允许“使用任意显式标签”。对于这些自定义标签的处理程序需要在每个目标语言中以自定义方式实现。在Python中的实现如下:
## in your python code
import yaml
## define custom tag handler
def join(loader, node):
seq = loader.construct_sequence(node)
return ''.join([str(i) for i in seq])
## register the tag handler
yaml.add_constructor('!join', join)
## using your sample data
yaml.load("""
user_dir: &DIR /home/user
user_pics: !join [*DIR, /pics]
""")
这将导致:
{'user_dir': '/home/user', 'user_pics': '/home/user/pics'}
您可以向数组中添加更多项,例如" "
或"-"
,如果字符串应该被分隔。
其他编程语言也可以做类似的事情,这取决于它们的解析器能够做什么。
有几条评论是“这似乎是错误的,因为YAML是一种标准的、实现中立的语言”。实际上,YAML不是这样的。YAML是一个框架,用于将YAML模式(由标记组成)映射到特定于实现的数据类型,例如int
如何映射到Python、Javascript、C++等。有多个标准的YAML模式,哪些模式受解析器支持是一个实现决策。当有用时,您可以创建具有附加自定义标记的模式,当然这需要额外的解析器实现。添加自定义标记是否是一个好主意取决于您的用例。YAML具有这种能力;是否以及如何应用它取决于您。请谨慎使用 :)
yaml
软件包的源代码。它只是使用其公共API来扩展YAML规范允许的方式。 - Ken Williamsos.path.join
:user_dir: &home /home/user
user_pics: !!python/object/apply:os.path.join [*home, pics]
使用string.join
方法(为了完整起见——该方法具有灵活性,可用于多种形式的字符串拼接:
user_dir: &home /home/user
user_pics: !!python/object/apply:string.join [[*home, pics], /]
default: &default_path "you should not use paths in config"
pictures:
- *default_path
- pics
在我看来,YAML本身没有定义如何做到这一点。
好消息是,YAML的使用者可能能够理解变量。
你将使用你的YAML做什么?
yaml.load()
中添加Loader=yaml.Loader
。最终,代码会像这样:import yaml
## define custom tag handler
def join(loader, node):
seq = loader.construct_sequence(node)
return ''.join([str(i) for i in seq])
## register the tag handler
yaml.add_constructor('!join', join)
## using your sample data
yaml.load("""
user_dir: &DIR /home/user
user_pics: !join [*DIR, /pics]
""", Loader=yaml.Loader)
请参考这个 GitHub 问题进行进一步讨论。
string.join()
在Python3中不可用,但是你可以像这样定义一个!join
:
import functools
import yaml
class StringConcatinator(yaml.YAMLObject):
yaml_loader = yaml.SafeLoader
yaml_tag = '!join'
@classmethod
def from_yaml(cls, loader, node):
return functools.reduce(lambda a, b: a.value + b.value, node.value)
c=yaml.safe_load('''
user_dir: &user_dir /home/user
user_pics: !join [*user_dir, /pics]''')
print(c)
const yourYaml = `
user_dir: &user_home /home/user
user_pics: !join [*user_home, '/pics']
`;
const JoinYamlType = new jsyaml.Type('!join', {
kind: 'sequence',
construct: (data) => data.join(''),
})
const schema = jsyaml.DEFAULT_SCHEMA.extend([JoinYamlType]);
console.log(jsyaml.load(yourYaml, { schema }));
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
要在Javascript / NodeJS中使用yaml,我们可以使用js-yaml:
import jsyaml from 'js-yaml';
// or
const jsyaml = require('js-yaml');
ruamel.yaml
实现join标签的示例。from ruamel.yaml import YAML
class JoinTag:
"""a tag to join strings in a list"""
yaml_tag = u'!join'
@classmethod
def from_yaml(cls, constructor, node):
seq = constructor.construct_sequence(node)
return ''.join([str(i) for i in seq])
@classmethod
def to_yaml(cls, dumper, data):
# do nothing
return dumper.represent_sequence(cls.yaml_tag, data)
@classmethod
def register(cls, yaml: YAML):
yaml.register_class(cls)
if __name__ == '__main__':
import io
f = io.StringIO('''\
base_dir: &base_dir /this/is/a/very/very/long/path/
data_file: !join [*base_dir, data.csv]
''')
yaml = YAML(typ='safe')
JoinTag.register(yaml)
print(yaml.load(f))
输出将是:
{'base_dir': '/this/is/a/very/very/long/path/', 'data_file': '/this/is/a/very/very/long/path/data.csv'}
yaml
文件支持变量替换,但默认情况下采用惰性执行的方法。
yaml
文件中的变量替换语法为
# this is test.yaml file and its contents.
server:
host: localhost
port: 80
client:
url: http://${server.host}:${server.port}/
server_port: ${server.port}
# relative interpolation
description: Client of ${.url}
from omegaconf import OmegaConf
conf = Omegaconf.load("test.yaml")
print(f"type: {type(conf).__name__}, value: {repr(conf)}")
print(f"url: {conf.client.url}\n")
print(f"server_port: {conf.client.server_port}\n")
print(f"description: {conf.client.description}\n")
输出:
type: DictConfig, value: {'server': {'host': 'localhost', 'port': 80}, 'client': {'url': 'http://${server.host}:${server.port}/', 'server_port': '${server.port}', 'description': 'Client of ${.url}'}}
url: http://localhost:80/
server_port: 80
description: Client of http://localhost:80/
注意现在当我们访问和打印变量的值时,变量已被替换。
但是当我们想要将整个字典作为参数传递时,我们应该使用这种方法:
from omegaconf import OmegaConf
conf = Omegaconf.load("test.yaml")
conf = OmegaConf.to_container(conf, resolve=True)
print(conf)
# output
type: dict, value: {'server': {'host': 'localhost', 'port': 80}, 'client': {'url': 'http://localhost:80/', 'server_port': 80, 'description': 'Client of http://localhost:80/'}}