[Рецепт] Создание вложенных словарей для тестовых нужд без особых проблем, пример на Python


(Mykhailo Poliarush) #1

В поддержку новой инициативы Code Recipes. Искренне надеюсь на вашу помощь и всяческую поддержку в виде новых code recipes!

Если Вы программируете на Python, то знаете что словарь является очень важной структурой. И не только является важным для самого языка программирования, а и для автоматизации тестирования на этом языке. Ведь очень часто приходиться описывать данные в формате ключ: значение. Такие данные могут быть большие по объему описываемых данных, а также не существовать в момент обращения к словарю . И если использовать стандартные механизмы словаря, то описать вложенные подсловари неудобно, а тем более если вы хотите создавать структуру данных программно в режиме runtime.

Пример, нам надо создать структуру:

{
    "server": {
        "host": "127.0.0.1",
        "port": "22"
    },
    "configuration": {
        "ssh": {
            "access": "true",
            "login": "some",
            "password": "some"
        }
    }
}

Стандартный способ решения этой задачи

data = {}
# some logic here
print data["server"]  # will raise exception due to 'KeyError: 'server''
# some logic here
data["server"] = {
    "host": "127.0.0.1",
    "port": "22"
}
# some logic here
if data["configuration"]["ssh"]["login"]: # will raise exception here
    pass
 
# some logic here
data["configuration"] = {
    "ssh": {
    "access": "true",
    "login": "some",
    "password": "some"
    }
}

Немного улучшенный вариант

У словаря можно использовать get метод, чтобы получить значение и не выдавать исключение и возвращать, дефолтное значение. Но если в случае с data["server"] это сработает, то с data["configuration"]["ssh"]["login"] не сработает, так как возвращаемый объект будет типа None

data = {}
# some logic here
print data.get("server")
# some logic here
data["server"] = {
    "host": "127.0.0.1",
    "port": "22"
}
# some logic here
if data.get("configuration").get("ssh").get("login"):
    # will raise exception
    # AttributeError: 'NoneType' object has no attribute 'get'
    pass
 
# some logic here
data["configuration"] = {
    "ssh": {
    "access": "true",
    "login": "some",
    "password": "some"
    }
}

Но если задать дефолтные значения для data.get("configuration").get("ssh").get("login") чтобы не получать ошибку AttributeError: 'NoneType' object has no attribute 'get', то можно получить нормальный результат

data = {}
# some logic here
print data.get("server")
# some logic here
data["server"] = {
    "host": "127.0.0.1",
    "port": "22"
}
# some logic here
if data.get("configuration", {}).get("ssh", {}).get("login", {}):
    # will raise exception
    # AttributeError: 'NoneType' object has no attribute 'get'
    pass
 
# some logic here
data["configuration"] = {
    "ssh": {
    "access": "true",
    "login": "some",
    "password": "some"
    }
}

Готовый рецепт, усовершенствованный способ

А можно использовать возможности defaultdict

from collections import defaultdict
_default_data = lambda: defaultdict(_default_data)
data = _default_data()
# some logic here
print data["server"]
# some logic here
data["server"]["host"] = "127.0.0.1"
data["server"]["port"] = "22"
# some logic here
if data["configuration"]["ssh"]["login"]:
    print ("some logic") 
# some logic here
data["configuration"]["ssh"]["access"] = "true"
data["configuration"]["ssh"]["login"] = "some"
data["configuration"]["ssh"]["password"] = "some"

import json
print json.dumps(data, indent=2)
defaultdict(<function <lambda> at 0x02565930>, {})
{
  "configuration": {
    "ssh": {
      "access": "true", 
      "login": "some", 
      "password": "some"
    }
  }, 
  "server": {
    "host": "127.0.0.1", 
    "port": "22"
  }
}

Результат получается без всяких исключений и сложностей, и мы можем задавать любую структуру из подвложенных словарей без фактического определения самих объектов, а также получаем возможность проверки словаря без исключений и удобную стандартную форму записи словаря.

Все варианты кода можно посмотреть на нашей общем репозитории примеров https://github.com/atinfo/at.info-knowledge-base/tree/master/programming/python/code%20recipes/generate%20nested%20dicts

Ну и кто хочет учиться python и автоматизации, милости прошу на http://lessons2.ru


Готовые рецепты или активизация сообщества автоматизаторов на #atinfo
Дайджест полезных ссылок для тестировщиков-автоматизаторов #030
(Dmytro Makhno) #2

Спасибо, запомню…

Снипет прямо напомнил код chef-reciepies (на ruby), где подобный вид очень удобен для задания атрибутов.


(Ilya Pas2shkov) #3

Есть ещё вариант записать

import json
sample = """{ "configuration": { "ssh": { "access": %s, "login": %s, "password": %s } },"server": {"host":%s,"port": %s}} """  % ("true", "some", "some", "127.0.01", "22")
json_sample = json.dumps(sample)
print json.loads(json_sample) # ибо нужен словарь    

{ "configuration": {"ssh": {"access": "true","login": "some","password": "some" }},"server": {"host": "127.0.0.1","port": "22" } }

Такой способ быстрее, особенно, если дело касается большого количества подобных объектов.

Также для большого количества таких json’чиков рекомендую ujson, он побыстрее, чем json, а методы load(s) и dump(s) те же, что весьма удобно


(Mykhailo Poliarush) #4

Так и есть, ruby и python во многом идут нога в ногу

Что под этим подразумевается? Что-то я не сильно понял вашего комментария.


(Ilya Pas2shkov) #5

Я отредактировал ответ, ноут сел невовремя.


(Mykhailo Poliarush) #6

Если мы говорим о создание словаря с заведомо известными данными, то да, всех этих махинаций не надо, и тем более json

Но если в последствии нам нужно обратиться к несуществующему ключу или сложенному словарю print json.loads(json_sample)["configuration"]["new nested dict"] то будет исключение или необходимо задавать новые значения с вложенными словарями после уже загруженного json json.loads(json_sample)["configuration"]["new nested dict"]["new nested dict"] = 123 то тоже будет исключение.

Я конкретно эту проблему решал.


(Ilya Pas2shkov) #7
sample = """{ "configuration": { "ssh": { "access": "%s", "login": "%s", "password": "%s" } },"server": {"host":"%s","port": "%s"} }"""  % ("true", "some", "some", "127.0.01", str(new_item))
json_sample = json.loads(sample) 

Тогда print json_sample[“configuration”][“new nested dict”] , конечно, ругнётся на отсутствие ключа KeyError’ом, так как его нет, пока его не зададут(что вполне можно сделать), но в следующем случае, можно схитрить:

json_sample["configuration"]["new nested dict"] = dict()
json_sample["configuration"]["new nested dict"]["new nested dict"] = 123

И всё добавится.

    print json.dumps(json_sample)

{"configuration": {"new nested dict": 123, "ssh": {"access": "true", "login": "some", "password": "some"}, "new nested dic": {"new nested dict": 123}}, "server": {"host": "127.0.01", "port": "{'port': 22}"}}

И редактировать его ничего не мешает, в таком случае.


(Mykhailo Poliarush) #8

Да и так можно сделать, я об этом и не спорю. Просто если мне надо будет добавить 10 вложенных словарей, то это +10 лишних строчек кода. А так можно обойтись одной строкой и все сработает. Имхо, все индивидуально для каждого случая. Мой рецепт хорошо работает для моей ситуации, я его показал, а дальше хотите пользуйтесь хотите нет :smile:


(Ilya Pas2shkov) #9

Убедили. Просто не сразу понял, о чём вы.