۱۶ ترفند دیکشنری پایتون برای توسعه دهندگان با مهارت متوسط | راهنمای کاربردی

۵۵۷ بازدید
آخرین به‌روزرسانی: ۲۸ خرداد ۱۴۰۱
زمان مطالعه: ۷ دقیقه
۱۶ ترفند دیکشنری پایتون برای توسعه دهندگان با مهارت متوسط | راهنمای کاربردی

دیکشنری‌ها قلب و جان پایتون محسوب می‌شوند. در این مقاله در مورد 16 ترفند دیکشنری پایتون صحبت خواهیم کرد که به توسعه‌دهندگان با سطح مهارت متوسط کمک می‌کنند تا کدهای خود را به میزان زیادی ارتقا بخشیده و فرایند کدنویسی خود را به‌مراتب آسان‌تر و سریع‌تر انجام دهند. قطعه کدهایی که در این مقاله ارائه شده‌اند، همگی با پایتون نسخه 3.7 تست شده‌اند.

پرینت همه جفت‌های کلید: مقدار در دیکشنری‌های تودرتو

این یک راه‌حل بازگشتی برای پرینت کردن همه جفت‌های کلید: مقدار در یک دیکشنری است که وجود لیست‌ها، مجموعه‌ها و چندتایی‌ها را در یک مقدار نیز مدیریت می‌کند:

1import requests
2import json
3
4
5def recursive_key_values(dictionary):
6    """recursive_key_values.
7        Print all keys and values anywhere in a dictionary
8    Args:
9        dictionary: any dictionary
10    Returns:
11        tuple:
12    """
13    for key, value in dictionary.items():
14        i = 0
15        if type(value) is str:
16            yield (key, value)
17        elif type(value) is dict:
18            yield from recursive_key_values(value)
19        elif type(value) in (list, tuple, set):
20            for seq_item in value:
21                yield from recursive_key_values({f"{key}_{str(i)}": seq_item})
22                i = i + 1
23        else:
24            yield (key, str(value))
25
26
27mydata: dict = {
28    "key2": {
29        "key2.0": "value1",
30        "key2.1":...,
31        "key2.2": {
32            "key2.2.0": "value2"
33        }
34    },
35    "key1": {
36        "key1.0": "value3"
37    },
38}
39mydata["key3"] = {1, 2, 3} # set
40mydata["key4"] = [4, 5, 6] # list
41mydata["key5"] = (7, 8, 9) # tuple
42
43if __name__ == "__main__":
44    for key, value in recursive_key_values(mydata):
45        print(f"{key}: {value}")

خروجی کد فوق چنین است:

16 ترفند دیکشنری پایتون

مرتب‌سازی یک دیکشنری

از پایتون نسخه 3.6 به بعد، دیکشنری‌ها به صورت پیش‌فرض ترتیب درج را حفظ می‌کنند. با این حال هنوز از امکان مرتب‌سازی پشتیبانی نمی‌کنند.

امکان دریافت یک دنباله تکرارپذیر از جفت‌های کلید: مقدار دیکشنری به صورت دنباله‌ای از چندتایی‌ها وجود دارد. سپس می‌توانیم این لیست از چندتایی‌ها را مجدداً به یک دیکشنری تبدیل کنیم تا ترتیب درج حفظ شود.

1"""
2Sorting a dictionary to an Iterable of tuples.
3https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_area
4"""
5
6countries: dict = {
7    "Taiwan": 36193,
8    "Canada": 9984670,
9    "United States": 9525067,
10    "Russia": 17098246,
11    "Argentina": 2780400,
12    "Zambia": 752612,
13    "China": 9596961,
14}
15
16assert dict(sorted(countries.items(), key=lambda kv: kv[1], reverse=False)) == {
17    "Taiwan": 36193,
18    "Zambia": 752612,
19    "Argentina": 2780400,
20    "United States": 9525067,
21    "China": 9596961,
22    "Canada": 9984670,
23    "Russia": 17098246,
24}
25
26assert dict(sorted(countries.items(), key=lambda kv: kv[1], reverse=True)) == {
27    "Russia": 17098246,
28    "Canada": 9984670,
29    "China": 9596961,
30    "United States": 9525067,
31    "Argentina": 2780400,
32    "Zambia": 752612,
33    "Taiwan": 36193,
34}
35
36assert dict(sorted(countries.items(), key=lambda kv: kv[0], reverse=False)) == {
37   "Argentina": 2780400,
38   "Canada": 9984670,
39   "China": 9596961,
40   "Russia": 17098246,
41   "Taiwan": 36193,
42   "United States": 9525067,
43   "Zambia": 752612,
44}
45
46assert dict(sorted(countries.items(), key=lambda kv: kv[0], reverse=True)) == {
47    "Zambia": 752612,
48    "United States": 9525067,
49    "Taiwan": 36193,
50    "Russia": 17098246,
51    "China": 9596961,
52    "Canada": 9984670,
53    "Argentina": 2780400,
54}

ادغام دو دیکشنری

از پایتون نسخه 3.9 به بعد، عملگر ادغام | برای دیکشنری‌ها ارائه شده است. تا این زمان می‌توانستید از روش ادغام کپی سطحی استفاده کنید.

1def merge_dictionaries(left: dict, right: dict) -> dict:
2    """Merge two dictionaries using a shallow copy."""
3    temp: dict = left.copy()
4    temp.update(right)
5    return temp
6
7
8countries: dict = {
9    "Taiwan": 36193,
10    "Canada": 9984670,
11    "United States": 9525067,
12    "Russia": 17098246,
13    "Argentina": 2780400,
14    "Zambia": 752612,
15    "China": 9596961,
16}
17
18cities: dict = {
19    "Toronto": ["Canada", 6082000],
20    "New York City": ["United States", 18819000],
21    "Moscow": ["Russia", 12410000],
22    "Buenos Aires": ["Argentina", 14967000],
23    "Shanghai": ["China", 25582000],
24    "Lusaka": ["Zambia", 1747152],
25    "Taipei": ["Taiwan", 2646204],
26}
27
28assert merge_dictionaries(countries, cities) == {
29    "Taiwan": 36193,
30    "Canada": 9984670,
31    "United States": 9525067,
32    "Russia": 17098246,
33    "Argentina": 2780400,
34    "Zambia": 752612,
35    "China": 9596961,
36    "Toronto": ["Canada", 6082000],
37    "New York City": ["United States", 18819000],
38    "Moscow": ["Russia", 12410000],
39    "Buenos Aires": ["Argentina", 14967000],
40    "Shanghai": ["China", 25582000],
41    "Lusaka": ["Zambia", 1747152],
42    "Taipei": ["Taiwan", 2646204],
43}

ساخت دیکشنری از دو لیست

ما می‌توانیم دو لیست را از یک دنباله از چندتایی‌ها zip کنیم و آن را به سازنده dict()‎ بفرستیم:

1def dict_from_two_lists(keys: list, values: list) -> dict:
2    """
3    Args:
4        keys: The list of keys for the dictionary
5        values: The list of values for the dictionary
6    Returns: A dictionary of key:value pairs
7    """
8    if len(keys) != len(values):
9        raise ValueError("Lists must be of same length")
10
11    return dict(zip(keys, values))
12
13
14assert dict_from_two_lists(["first", "second", "third"],
15                           ["primary", "secondary", "tertiary"]) == {
16                               "first": "primary",
17                               "second": "secondary",
18                               "third": "tertiary",
19                           }

دریافت همه آیتم‌ها بر اساس کلید در یک دیکشنری تودرتو

به این منظور دوباره از روش بازگشتی استفاده می‌کنیم. توجه کنید که کتابخانه‌های خوبی در پایتون وجود دارند که می‌توانید به روش عبارت‌های مسیر عمل کنید:

1import json
2import requests
3
4
5def get_values_by_key(dictionary: dict, key: str) -> dict:
6    """get_values_by_key.
7    Args:
8        dictionary (dict): dictionary
9        key (str): key
10    Returns:
11        dict:
12    """
13    if isinstance(dictionary, dict):
14        for k, v in dictionary.items():
15            if k == key:
16                yield v
17            elif isinstance(v, dict):
18                for result in get_values_by_key(v, key):
19                    yield result
20            elif type(v) in (list, tuple):
21                for d in v:
22                    for seq in get_values_by_key(d, key):
23                        if type(seq) in (list, tuple):
24                            for inner_item in seq:
25                                yield inner_item
26                        else:
27                            yield seq
28
29
30dict1 = dict(
31    json.loads(
32        requests.get("http://ergast.com/api/f1/2004/1/results.json").text))
33
34assert "http://en.wikipedia.org/wiki/McLaren" in list(
35    get_values_by_key(dict1, "url"))

دریافت همه آیتم‌ها بر اساس مقدار در دیکشنری تودرتو

طرز کار این مثال همانند ترفند قبلی است. توجه داشته باشید که ما از yield برای ساخت یک تکرارکننده استفاده کرده‌ایم. خط 32 همه اجراهای منفرد yield را به یک لیست کاهش می‌دهد:

1import json
2import requests
3
4
5def get_key_by_value(dictionary: dict, val: str) -> object:
6    """get_values_by_key.
7    Args:
8        dictionary (dict): dictionary
9        val (str): value
10    Returns:
11        dict:
12    """
13    if isinstance(dictionary, dict):
14        for k, v in dictionary.items():
15            if val == v:
16                yield k
17            elif isinstance(v, dict):
18                for result in get_key_by_value(v, val):
19                    yield result
20            elif isinstance(v, list):
21                for list_item in v:
22                    for result in get_key_by_value(list_item, val):
23                        yield result
24
25
26dict1 = dict(
27    json.loads(
28        requests.get("http://ergast.com/api/f1/2004/1/results.json").text))
29
30assert "raceName" in list(get_key_by_value(dict1, "Australian Grand Prix"))

ایجاد دیکشنری با استفاده از خلاصه‌سازی دیکشنری

«خلاصه‌سازی دیکشنری» (Dictionary Comprehension) از سازنده {key: value} با یک حلقه for روی یک دنباله استفاده می‌کند:

1asci_uppercase: dict = {i: chr(+i) for i in range(65, 91, 1)}
2digits: dict = {i: chr(+i) for i in range(48, 58, 1)}
3asci_lowercase: dict = {i: chr(+i) for i in range(97, 123, 1)}
4asci_punctuation: dict = {i: chr(+i) for i in range(32, 48, 1)}
5asci_punctuation.update({i: chr(+i) for i in range(123, 127, 1)})
6asci_punctuation.update({i: chr(+i) for i in range(91, 97, 1)})
7asci_punctuation.update({i: chr(+i) for i in range(58, 65, 1)})
8asci_extended: dict = {i: chr(+i) for i in range(128, 255, 1)}
9asci_system: dict = {i: hex(i) for i in range(0, 32, 1)}
10
11ascii_chars: dict = {}
12ascii_chars.update({"asci_punctuation": asci_punctuation})
13ascii_chars.update({"asci_lowercase": asci_lowercase})
14ascii_chars.update({"asci_uppercase": asci_uppercase})
15ascii_chars.update({"digits": digits})
16ascii_chars.update({"asci_extended": asci_extended})
17ascii_chars.update({"asci_system": asci_system})
18
19print(ascii_chars)

استفاده از مقادیر پیش‌فرض برای آیتم‌های جدید

Defaultdict در مواردی که بخواهیم مقادیر را به یک کلید موجود الحاق کنیم به طوری که بازنویسی نشود، بسیار مفید خواهد بود. به طور معمول خط شماره 9 یک خطا ایجاد می‌کند، زیرا مقدار موجود در این کلید نمی‌تواند یک لیست باشد و باید None باشد. از این رو نمی‌توانستیم از append استفاده کنیم.

1from collections import defaultdict as dd
2
3s = [("John", "Male"), ("John", "48"), ("John", "Married"), ("Jane", "Female"),
4     ("Jane", "25")]
5
6dict1: dict = dd(list)
7
8for k, v in s:
9    dict1[k].append(v)
10
11assert dict1["John"] == ["Male", "48", "Married"]
12assert dict1["Jane"] == ["Female", "25"]

تبدیل لیستی از چندتایی‌ها به دیکشنری

در این مورد نیز از یک defaultdict استفاده می‌کنیم، اما این بار مقدار پیش‌فرض یک دیکشنری است.

1from collections import defaultdict as dd
2
3s = [("John", "Male", 25), ("Fred", "Female", 48), ("Sam", "Female", 41),
4     ("Jane", "Female", 25)]
5
6dict1 = dd(dict)
7
8for name, gender, age in s:
9    dict1[name]["age"] = age
10    dict1[name]["gender"] = gender
11
12assert dict1["John"] == {"age": 25, "gender": "Male"}
13assert dict1["Fred"] == {"age": 48, "gender": "Female"}
14assert dict1["Sam"] == {"age": 41, "gender": "Female"}
15assert dict1["Jane"] == {"age": 25, "gender": "Female"

ایجاد یک دیکشنری از فایل CSV با عناوین ستون

به این منظور از اختراع مجدد چرخ خودداری کرده و از ماژول CSV کمک می‌گیریم.

1from collections import defaultdict as dd
2import csv
3import requests
4
5url: str = "https://data.london.gov.uk/download/london-borough-profiles/c1693b82-68b1-44ee-beb2-3decf17dc1f8/london-borough-profiles.csv "
6
7boroughs = (requests.get(url).text).split("\n")
8reader = csv.DictReader(boroughs, dialect="excel")
9dict1 = dd(dict)
10
11for row in reader:
12    dict1[row["Code"]] = row
13
14assert dict1["E09000001"]["Area_name"] == "City of London"
15assert dict1["E09000032"]["Inner/_Outer_London"] == "Inner London"

حذف آیتم از دیکشنری

با استفاده از متد pop()‎ می‌توانید یک آیتم دیکشنری را حذف کرده و مقدار آن را بازیابی کنید.

1digits: dict = {i: chr(+i) for i in range(48, 58, 1)}
2key = 50
3
4try:
5    val = digits.pop(key)
6except KeyError:
7    print (f"The item with key {key} did not exist.")
8else:
9    print(f"Deleted item with key {key} with value {val}.")

ایجاد کپی عمیق از دیکشنری

با استفاده از تابع deepcopy در ماژول copy می‌توانید یک کپی واقعی از یک دیکشنری بسازید که شامل اشیای تغییرپذیر مانند یک لیست باشد.

1import copy
2
3original = {}
4original["object1"] = dict({"a": {"b": [1, 2, 3]}})
5
6dict_shallow = original.copy()
7dict_deep = copy.deepcopy(original)
8
9# change the mutable object in original and dict_shallow
10original["object1"]["a"]["b"] = [3, 4, 5]
11
12assert id(original["object1"]) == id(dict_shallow["object1"])
13assert id(original["object1"]) != id(dict_deep["object1"])

معکوس ساختن کلیدها و مقادیر

این ترفند نیز ممکن است یک روز به کارتان بیاید. در این راه‌حل از یک خلاصه‌سازی دیکشنری استفاده شده است.

1def invert_dictionary(input:dict)->dict:
2    return {v: k for k, v in dict1.items()}
3
4dict1 = {"a": 1, "b": 2, "c": 3}
5
6assert invert_dictionary(dict1) == {1: "a", 2: "b", 3: "c"}

ذخیره چندین دیکشنری در یک فایل

به جای استفاده از pickle برای ذخیره یک شیء در یک فایل می‌توانید از shelve برای ذخیره چند شیء در فایل استفاده کنید.

1import shelve
2
3dict1 = {"a": 1, "b": 2, "c": 3}
4dict2 = {1: "a", 2: "b", 3: "c"}
5dict3 = {}
6dict4 = {}
7
8with shelve.open("dictionary-shelf", "c") as shelf:
9    shelf["first"] = dict1
10    shelf["second"] = dict2
11
12with shelve.open("dictionary-shelf", "r") as shelf:
13    dict3 = shelf["first"]
14    dict4 = shelf["second"]
15
16assert dict1 == dict3
17assert dict2 == dict4
18assert dict1 != dict2
19assert dict3 != dict4

تبدیل دیکشنری به JSON

کاری آسان‌تر از این در پایتون وجود ندارد. مثال‌هایی از این مورد را در ترفندهای شماره 6 و 10 این مطلب مشاهده کردیم. به جز در مواردی که دیکشنری شما شامل کلاس‌های تعریف شده از سوی کاربر، اشیای datetime، مجموعه (set) یا دیگر اشیای پایتون باشد، می‌توانید از این روش استفاده کنید.

در این حالت یک زیرکلاس از شیء JSONEncoder به دست می‌آید که مورد خاص ما را به روش متفاوتی مدیریت می‌کند. در خط 12 از «نوع‌بندی اردکی» (duck typing) استفاده کرده‌ایم. قصد ما این است که ببینیم آیا شیء به صورت یک دنباله تکرارپذیر عمل می‌کند یا نه تا در این صورت آن را به یک لیست تبدیل کنیم. اگر شیئی یک تکرارپذیر (iterable) نداشته باشد، یک استثنا ایجاد می‌شود که در خط 16 نادیده گرفته شده است.

1import json
2from datetime import datetime
3
4
5class PythonObjectEncoder(json.JSONEncoder):
6
7    def default(self, an_object_value):
8
9        if isinstance(an_object_value, str):
10            return an_object_value
11
12        try:
13            iterable = iter(an_object_value)
14            return list(iterable)
15        except TypeError:
16            pass  # this object is not iterable
17
18        if isinstance(an_object_value, datetime):
19            return an_object_value.isoformat()
20
21        elif hasattr(an_object_value, "__class__"):
22            return an_object_value.__dict__
23
24        return super().default(an_object_value)
25
26
27def dict_to_json(the_dict: dict) -> str:
28
29    return PythonObjectEncoder().encode(the_dict)
30
31
32class SimpleClass:
33    def __init__(self):
34        self.instance_value = 10
35
36
37b = SimpleClass()
38
39dict1: dict = {}
40dict1["string"] = "Hello, World!"
41dict1["datetime"] = datetime(2030, 12, 31)
42dict1["nothing"] = None
43dict1["set"]: set = {0, 1, 2, 3}
44dict1["tuple"] = ("apples", "fresh", "green")
45dict1["list"] = ["a", "b", "c"]
46dict1["simple_class"] = SimpleClass()
47
48assert (
49        dict_to_json(dict1) ==
50            (
51                '{"string": "Hello, World!",' 
52                ' "datetime": "2030-12-31T00:00:00",'
53                ' "nothing": null,'
54                ' "set": [0, 1, 2, 3],'
55                ' "tuple": ["apples", "fresh", "green"],'
56                ' "list": ["a", "b", "c"],'
57                ' "simple_class": {"instance_value": 10}}'
58            )
59    )

حذف چندین آیتم از دیکشنری در طی تکرار

در پایتون 3 متد items()‎ یک تکرارکننده (iterator) بازگشت می‌دهد. بنابراین تلاش برای حذف یک آیتم منجر به ایجاد یک «خطای زمان اجرا» (RuntimeError) می‌شود. ما با یادداشت‌برداری موقت از همه آیتم‌هایی که قرار است حذف شوند، می‌توانیم از بروز این خطا جلوگیری کنیم. در مثال زیر همه آیتم‌هایی که مقدارشان عدد زوج است حذف می‌شوند.

1def remove_even_items(the_dict: dict):
2
3    # collect the keys of all the items to remove
4    delete_these = set(k for k, v in the_dict.items() if v % 2 == 0)
5
6    for delete_this in delete_these:
7        del the_dict[delete_this]
8
9    return the_dict
10
11
12dict1: dict = dict(enumerate(range(13)))
13assert remove_even_items(dict1) == {1: 1, 3: 3, 5: 5, 7: 7, 9: 9, 11: 11}

سخن پایانی

امیدواریم این مطلب مورد توجه شما قرار گرفته باشد. هر گونه دیدگاه یا پیشنهاد خود را در مورد موضوع این مطلب در بخش نظرات این نوشته با ما و دیگر خوانندگان مجله فرادرس در میان بگذارید.

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
python-in-plain-english
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *