python yaml文件操作


一:yaml簡介及基礎語法

 yaml是專門用來寫配置文件的語言,非常簡潔和強大,遠比 JSON 格式方便。

1.1 yaml基礎語法規則

  • 大小寫敏感
  • 使用縮進表示層級關系
  • 不允許使用 TAB 鍵來縮進,只允許使用空格鍵來縮進
  • 縮進的空格數量不重要
  • 使用"#"來表示注釋

1.2 yaml 支持的數據結構有三種

  • 對象:鍵值對的集合,又稱為映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 數組:一組按次序排列的值,又稱為序列(sequence) / 列表(list)
  • 純量(scalars):單個的、不可再分的值

  1.2.1 對象

對象的一組鍵值對,使用:(冒號)結構表示。冒號之后必須有一個空格

#test.yml文件

animal: pets

#轉為python 如下:

{'animal': 'pets'}

yaml 也允許另一種寫法,將所有鍵值對寫成一個行內對象。

#test.yml文件
 
hash: { name: Steve, foo: bar } 

#轉為python如下:

{'hash': {'name': 'Steve', 'foo': 'bar'}}

  1.2.2 數組

一組連詞線開頭的行,構成一個數組。

#test.yml文件
- Cat
- Gog
- Goldfis

#轉為python 如下:

['Cat', 'Gog', 'Goldfis']

數據結構的子成員是一個數組,則可以在該項下面縮進一個空格。

#test.yml 文件
-
 - Cat
 - Dog
 - Goldfish

#轉為 python 如下:

[['Cat', 'Dog', 'Goldfish']]

數組也可以采用行內表示法。

#test.yml文件
animal: [Cat, Dog]

#轉為 python 如下;

{'animal': ['Cat', 'Dog']}

  1.2.3 復合結構

對象和數組可以結合使用,形成復合結構。

#test.yml文件
languahes:
  - JavaScript
  - java
  - Python
websites:
 YAML: yaml.org
 Ruby: ruby-lang.org
 Python: python.org
 Perl: use.perl.org

#轉為 python 如下;
{
    'languahes': ['JavaScript', 'java', 'Python'], 
    'websites': {'YAML': 'yaml.org', 'Ruby': 'ruby-lang.org', 'Python': 'python.org', 'Perl': 'use.perl.org'}
}

  1.2.4 純量

純量是最基本的、不可再分的值。

純量包含:字符串 整數 浮點數 布爾值 Null(用~表示) 時間 日期   其中:用~表示null ; 時間、日期采用IS08601格式 

#test.yml文件

name: 'xiaoli'
age: 22
weight: 57.30
isStudent: true
address: ~ 
time: 2001-12-14t21:59:43.10-05:00 
date: 1976-07-31

#轉為 python 如下:
{'name': 'xiaoli', 'age': 22, 'weight': 57.3, 'isStudent': True, 'address': None, 
'time': datetime.datetime(2001, 12, 14, 21, 59, 43, 100000, tzinfo=datetime.timezone(datetime.timedelta(-1, 68400))),
'date': datetime.date(1976, 7, 31)}

 yaml允許使用兩個感嘆號,強制轉換數據類型。

#test.yml文件

e: !!str 22
f: !!str true

#轉為 python 如下:
{'e': '22', 'f': 'true'}

  1.2.5 錨點&和引用*

在yaml文件中如何引用變量?當我們在一個yaml文件中寫很多測試數據時候,比如一些配置信息像用戶名,郵箱,數據庫配置等很多地方都會重復用到。
重復的數據,如果不設置變量,后續維護起來就很困難。
yaml文件里面也可以設置變量(錨點&),其它地方重復用到的話,可以用*引用 

#test.yml文件
defaults: &defaults
adapter: postgres
host: localhost

development:
database: myapp_development
<<: *defaults

test:
database: myapp_test
<<: *defaults

 等同於下面的代碼:

 
defaults:
  adapter: postgres
  host: localhost
 
development:
  database: myapp_development
  adapter: postgres
  host: localhost
 
test:
  database: myapp_test
  adapter: postgres
  host: localhost

&用來建立錨點(defaults),<<表示合並到當前數據,*用來引用錨點。

  1.2.6 *引用value值

上面的例子是對defaults整體的數據,引用到其它地方了,有時候我們只想引用其中的一個值

#test.yml 文件
defaults:
  adapter: postgres
  host: &host 127.0.0.1

development:
  database: myapp_development
  myhost: *host

#等同於下面的數據
defaults:
  adapter: postgres
  host: &host 127.0.0.1

development:
  database: myapp_development
  myhost: 127.0.0.1

二:yaml文件的讀寫

python本身並沒有自帶的處理yaml文件的庫,需要下載並安裝第三方庫PyYAML 或 ruamel.yaml ,這里我們先安裝PyYAML。

#安裝命令
 pip install pyyaml
# 下載速度慢的話加上清華鏡像源
pip install pyyaml -i https://pypi.tuna.tsinghua.edu.cn/simple

2.1 從yaml中讀取數據

# yaml文件,文件名為test.yml

os: Android
osVersion: 10
account:
  username: xiaoqq
  password: 123456
deviceName: null
appPackage: ~
bool1: True

讀取代碼:

import yaml
import os

file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
def read_yaml_data():
    with open(file_path, 'r', encoding='utf-8') as f:
        data = yaml.load(f, Loader=yaml.FullLoader)
        print(f'讀取的數據:{data}')
        print(f'數據類型為:{type(data)}')

if __name__ == '__main__':
    read_yaml_data()

讀取結果:

讀取的數據:{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
數據類型為:<class 'dict'>

從讀取結果可以看出:

1,讀取出來的數據不會改變原數據類型,即yaml里是什么數據類型,讀出來就是什么類型。

2,Loader=yaml.FullLoader參數不寫的話對結果不會有影響,但運行時會出現警告信息。

3,yaml.load(f.read(), Loader=yaml.FullLoader)也可以寫成yaml.load(f, Loader=yaml.FullLoader),讀取出來的結果相同。

注意:

pyyaml模塊在python中用於處理yaml格式數據,主要使用yaml.safe_dump()、yaml.safe_load()函數將python值和yaml格式數據相互轉換。當然也存在yaml.dump()、yaml.load()函數,同樣能實現數據轉換功能,只是官方不太推薦使用。官方給出的解釋,因為yaml.safe_dump()、yaml.safe_load() 能夠,而且yaml.safe_dump()、yaml.safe_load()比yaml.dump()、yaml.load()安全:

2.2 從yaml中讀取多組數據

yaml多組數據時,每組數據之間需要用3橫杠分隔’—’,如下:

#test.yml文件
os: Android
osVersion: 10
account:
  username: xiaoqq
  password: 123456
deviceName: null
appPackage: ~
bool1: True

---
os: ios
osVersion: 12
account1:
  username2: Lilei
  password2: 888888

從yaml中讀取多組數據時需要使用yaml.load_all()或者yaml.safe_load_all()方法,返回結果為一個生成器,需要使用for循環語句獲取每組數據。代碼如下:

def read_yaml_data():
    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'r', encoding='utf-8') as f:
        data = yaml.safe_load_all(f)
        print(f'讀取的數據:{data}')
        print(f'數據類型為:{type(data)}')
        for i in data:
            print(i)


if __name__ == '__main__':
    read_yaml_data()

讀取結果:

讀取的數據:<generator object load_all at 0x102b28258>
數據類型為:<class 'generator'>
{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
{'os': 'ios', 'osVersion': 12, 'account1': {'username2': 'Lilei', 'password2': 888888}}

2.3 單組數據寫入yaml文件

使用yaml.dump()或者yaml.safe_dump()方法,加入allow_unicode=True參數防止寫入的中文亂碼,如下

def write_yaml_data():
    data = {'hash': {'name': 'Steve', 'foo': '公寓'}}

    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'w', encoding='utf-8') as f:
        yaml.safe_dump(data, f, allow_unicode=True)


if __name__ == '__main__':
    write_yaml_data()

執行結果:

2.4 多組數據寫入yaml文件

使用yaml.dump_all()或者yaml.safe_dump_all()方法,如下:

def write_yaml_data():
    # 寫入多組數據
    data = {'hash': {'name': 'Steve', 'foo': '公寓'}}
    data1 = {'hash1': {'name1': 'admin',  "name": "流動人口社區"}}
    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'w', encoding='utf-8') as f:
        yaml.safe_dump_all(documents=[data,data1], stream=f, allow_unicode=True)

if __name__ == '__main__':
    write_yaml_data()

執行結果:

 2.5 ruamel.yaml 讀寫yaml文件

用yaml模塊寫入字典嵌套字典這種復雜的數據,會出現大括號{ },不是真正的yaml文件數據,可以用ruamel模塊就解決。

參考:https://www.cnblogs.com/yoyoketang/p/9255109.html
安裝方法:

pip install ruamel.yaml

2.5.1 用原生的yaml模塊寫入這種字典嵌套字典的復雜數據

import os
import yaml

# 將字典寫入到yaml
desired_caps = {
                'platformName': 'Android',
                'platformVersion': '7.0',
                'deviceName': 'A5RNW18316011440',
                'appPackage': 'com.tencent.mm',
                'appActivity': '.ui.LauncherUI',
                'automationName': 'Uiautomator2',
                'unicodeKeyboard': [True,"hh"],
                'resetKeyboard': True,
                'noReset': True,
                'chromeOptions': {'androidProcess': 'com.tencent.mm:tools'}
                }

curpath = os.path.dirname(os.path.realpath(__file__))
yamlpath = os.path.join(curpath, "caps.yaml")

# 寫入到yaml文件
with open(yamlpath, "w", encoding="utf-8") as f:
    yaml.dump(desired_caps, f)

運行結果:

由運轉結果,發現字典嵌套的字典,出現了大括號:{androidProcess: 'com.tencent.mm:tools'},(在pyyaml版本為6.0時 已經不存在該問題)這不是真正的yaml數據,不是我們想要的,解決辦法看下文

2.5.2 使用ruamel模塊 方法跟yaml差不多,只是在使用dump方法多個一個參數:Dumper=yaml.RoundTripDumper

import os
from ruamel import yaml


# 將字典寫入到yaml
desired_caps = {
                'platformName': 'Android',
                'platformVersion': '7.0',
                'deviceName': 'A5RNW18316011440',
                'appPackage': 'com.tencent.mm',
                'appActivity': '.ui.LauncherUI',
                'automationName': 'Uiautomator2',
                'unicodeKeyboard': True,
                'resetKeyboard': True,
                'noReset': True,
                'chromeOptions': {'androidProcess': 'com.tencent.mm:tools'}
                }

curpath = os.path.dirname(os.path.realpath(__file__))
yamlpath = os.path.join(curpath, "caps.yaml")

# 寫入到yaml文件
with open(yamlpath, "w", encoding="utf-8") as f:
    yaml.dump(desired_caps, f, Dumper=yaml.RoundTripDumper)

執行結果:

 2.6 ruamel.yaml 讀yaml文件

2.6.1.使用ruamel.yaml模塊也能讀yaml文件,使用方法相對於之前的yaml.load方法多加一個參數:Loader=yaml.Loader

def read_yaml_data():
    # 讀取單組數據
    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'r', encoding='utf-8') as f:
        data = yaml.load(f, Loader=yaml.Loader)
        print(f'讀取的數據:{data}')
        print(f'數據類型為:{type(data)}')
      
if __name__ == '__main__':
    read_yaml_data()

讀取結果:

讀取的數據:{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
數據類型為:<class 'dict'>

三:使用template替換yaml文件中的變量

在接口自動化測試的時候,yaml 文件一般放測試的數據或當配置文件使用,yaml 文件存放靜態的數據是沒問題的,python的數據類型基本上都是支持的。
有時候我們想在 yaml 文件中引用變量來讀取 python 代碼的設置值

3.1 template 使用

template 是字符串模板,用於替換字符串中的變量,是 string 的一個類引用變量有 2 種格式

  • $variable 使用 $變量名 引用變量
  • ${variable} 使用 ${變量名} 大括號包起來

第一種 $variable

from string import Template

tempTem = Template("My name is $name , i like $fancy")
d = {'name': 'admin', 'fancy': 'python'}

print(tempTem.substitute(d))

執行結果:

My name is admin, i like python

第二種 ${variable}

from string import Template

tempTemp1 = Template("My name is ${name} , i like ${fancy}")
d = {'name': 'admin', 'fancy': 'python'}
print(tempTemp1.substitute(d))

執行結果:

My name is admin, i like python

3.2 safe_substitute使用

上面的方式只能嚴格的匹配變量,當字符串中有$符號,不想匹配變量的時候,會報錯

from string import Template

tempTemplate = Template("$My name is ${name} , i like ${fancy}")
d = {'name': 'admin', 'fancy': 'python'}
print(tempTemplate.substitute(d))

這段,$符號加在My的前面,我只想讓它是一個普通的字符串,不想引用變量,就出現了報錯說找不到這個key

Traceback (most recent call last):
  File "E:/PycharmScripts/test_selenium_wire_yaml/test_yaml.py", line 25, in <module>
    print(tempTemp2.substitute(d))
  File "C:\Python37\lib\string.py", line 132, in substitute
    return self.pattern.sub(convert, self.template)
  File "C:\Python37\lib\string.py", line 125, in convert
    return str(mapping[named])
KeyError: 'My'

雖然字符串定義了多個變量,但是引用的時候只給了name這個值,也不影響運行,沒給值的當普通字符串出來,這樣就很完美了

tempTemplate = Template("$My name is ${name} , i like ${fancy}")
d = {'name': 'admin','fancy': 'python'}
print(tempTemplate.safe_substitute(d))

執行結果;

$My name is admin , i like python

3.3 yaml 文件引用變量

通過前面 Template 的基礎使用,已經掌握了基本的用法了,接下來在 yaml 文件中引用變量

request:
    headers:
         Content-Type: application/json
         User-Agent: python-requests/2.18.4
     json:
         username: $user
         password: $psw

python讀yaml文件代碼:

from string import Template
import yaml
with open("test.yml", encoding='utf-8') as fp:
    read_yml_str = fp.read()
    # print(read_yml_str)

    tempTemplate1 = Template(read_yml_str)
    new_yaml = tempTemplate1.safe_substitute({"user": "admin", "psw": "123456 "})
    print(f'Template補全的數據:\n{new_yaml}')

# yml 文件數據,轉 python 類型
yaml_data = yaml.safe_load(new_yaml)
print(f'yml文件轉python的數據:\n{yaml_data}')

執行結果:

'''
Template補全的數據:
request:
    headers:
         Content-Type: application/json
         User-Agent: python-requests/2.18.4
    json:
         username: admin
         password: 123456 

yml文件轉python的數據:
{
  'request': {'headers': {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4'},
  'json': {'username': 'admin', 'password': 123456}}
}
'''

四:yaml文件調用python外部函數

如何在 yaml文件中引用一個 python 的函數?

問題分析
其實yaml 和 json 文件本質上是一樣的,都是靜態的文件,是不能直接引用 python 的函數。
那這時候就有人問到了,那為什么 httprunner 框架可以在yaml文件中引用函數呢?

這是因為 httprunner 框架封裝過對 yaml 文件的讀取了,它是先讀取文件內容,正則提取到 ${} 括號里面的函數內容,再把函數的值替換過去
那么我們能不能實現這種效果呢?

當然是可以的,可以參考httprunner的實現,也可以用到 python 的模板 jinja2 來實現。
使用模板可以編寫出可讀性更好,更容易理解和維護的代碼,並且使用范圍非常廣泛,因此怎么使用模板主要取決於我們的想象力和創造力。
python的模板庫jinja2 功能是非常強大的

4.1 jinja2 模板庫

pip install jinja2

4.2 render 函數實現

在yaml文件中,通過 {{ 函數名稱() }} 來引用函數

寫個 render 函數讀取 yaml 文件內容

import jinja2
import os,random
def jinja2_template_render(tpl_path, **kwargs):
    path, filename = os.path.split(tpl_path)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 創建一個包加載器對象
    template = env.get_template(filename) # 獲取一個模板文件
    return template.render(**kwargs) # 進行模板渲染

讀取到的yaml文件本質上都是字符串來讀取的,通過jinja2 模板來讀取,會先把函數的值替換進去。最后再轉成python的dict結構

import jinja2
import os,random,yaml
def jinja2_template_render(tpl_path, **kwargs):
    path, filename = os.path.split(tpl_path)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 創建一個包加載器對象
    template = env.get_template(filename) # 獲取一個模板文件
    return template.render(**kwargs) # 進行模板渲染

# yaml 文件調用以下函數
def rand_str():
    return str(random.randint(1000000, 2000000))

if __name__ == '__main__':
    r = jinja2_template_render('test.yml',**{"user":"admin","pwd":1234,"rand_str":rand_str})
    print(f'jinja2渲染后的數據\n{r}')
    print(yaml.safe_load(r))

執行的結果:

'''
jinja2渲染后的數據:
username: admin
password: 1234
func: 1980646

{'username': 'admin', 'password': 1234, 'func': 1980646}
'''

上面讀取函數是寫死的,我們希望能自動加載類似於debugtalk.py的文件來自動加載函數

4.3 自動加載debug.py里面的函數

寫一個debug.py 文件,實現 yaml 文件里面定義的函數去替換值

#debug.py文件

import random

# yaml 文件調用以下函數
def rand_str():
    return str(random.randint(1000000, 2000000))

在run.py里面定義一個函數自動讀取debug.py里面的函數,生成dict 鍵值對格式

關鍵動態導入對象請參考:https://blog.csdn.net/edward_zcl/article/details/88809212

def all_functions():
    """加載debug.py模塊"""
    debug_module = importlib.import_module("debug")
    all_function = inspect.getmembers(debug_module, inspect.isfunction)
    print(dict(all_function))
    return dict(all_function)

函數返回 {'rand_str': <function rand_str at 0x0000000002E3F798>}

完整的run.py文件內容

import jinja2
import os,random,yaml
import importlib,inspect
def jinja2_template_render(tpl_path, **kwargs):
    path, filename = os.path.split(tpl_path)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 創建一個包加載器對象
    template = env.get_template(filename) # 獲取一個模板文件
    return template.render(**kwargs) # 進行模板渲染


def all_functions():
    """加載debug.py模塊"""
    debug_module = importlib.import_module("debug")
    all_function = inspect.getmembers(debug_module, inspect.isfunction)
    print(dict(all_function))
    return dict(all_function)



if __name__ == '__main__':
    temlplate_params = all_functions() #獲取調用函數
    temlplate_params["user"] = "admin"  #獲取user變量值
    temlplate_params["pwd"] = "1234" #獲取pwd變量值
    r = jinja2_template_render('test.yml',**temlplate_params)
    print(f'jinja2渲染后的數據\n{r}')
    print(yaml.safe_load(r))

執行結果:

'''
#all_functions()返回值
{'rand_str': <function rand_str at 0x0000000002E3F708>}

jinja2渲染后的數據
username: admin
password: 1234
func: 1730704

{'username': 'admin', 'password': 1234, 'func': 1730704}

'''

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM