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

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

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

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

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

import requests
import json


def recursive_key_values(dictionary):
    """recursive_key_values.
        Print all keys and values anywhere in a dictionary
    Args:
        dictionary: any dictionary
    Returns:
        tuple:
    """
    for key, value in dictionary.items():
        i = 0
        if type(value) is str:
            yield (key, value)
        elif type(value) is dict:
            yield from recursive_key_values(value)
        elif type(value) in (list, tuple, set):
            for seq_item in value:
                yield from recursive_key_values({f"{key}_{str(i)}": seq_item})
                i = i + 1
        else:
            yield (key, str(value))


mydata: dict = {
    "key2": {
        "key2.0": "value1",
        "key2.1":...,
        "key2.2": {
            "key2.2.0": "value2"
        }
    },
    "key1": {
        "key1.0": "value3"
    },
}
mydata["key3"] = {1, 2, 3} # set
mydata["key4"] = [4, 5, 6] # list
mydata["key5"] = (7, 8, 9) # tuple

if __name__ == "__main__":
    for key, value in recursive_key_values(mydata):
        print(f"{key}: {value}")

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

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

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

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

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

"""
Sorting a dictionary to an Iterable of tuples.
https://en.wikipedia.org/wiki/List_of_countries_and_dependencies_by_area
"""

countries: dict = {
    "Taiwan": 36193,
    "Canada": 9984670,
    "United States": 9525067,
    "Russia": 17098246,
    "Argentina": 2780400,
    "Zambia": 752612,
    "China": 9596961,
}

assert dict(sorted(countries.items(), key=lambda kv: kv[1], reverse=False)) == {
    "Taiwan": 36193,
    "Zambia": 752612,
    "Argentina": 2780400,
    "United States": 9525067,
    "China": 9596961,
    "Canada": 9984670,
    "Russia": 17098246,
}

assert dict(sorted(countries.items(), key=lambda kv: kv[1], reverse=True)) == {
    "Russia": 17098246,
    "Canada": 9984670,
    "China": 9596961,
    "United States": 9525067,
    "Argentina": 2780400,
    "Zambia": 752612,
    "Taiwan": 36193,
}

assert dict(sorted(countries.items(), key=lambda kv: kv[0], reverse=False)) == {
   "Argentina": 2780400,
   "Canada": 9984670,
   "China": 9596961,
   "Russia": 17098246,
   "Taiwan": 36193,
   "United States": 9525067,
   "Zambia": 752612,
}

assert dict(sorted(countries.items(), key=lambda kv: kv[0], reverse=True)) == {
    "Zambia": 752612,
    "United States": 9525067,
    "Taiwan": 36193,
    "Russia": 17098246,
    "China": 9596961,
    "Canada": 9984670,
    "Argentina": 2780400,
}

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

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

def merge_dictionaries(left: dict, right: dict) -> dict:
    """Merge two dictionaries using a shallow copy."""
    temp: dict = left.copy()
    temp.update(right)
    return temp


countries: dict = {
    "Taiwan": 36193,
    "Canada": 9984670,
    "United States": 9525067,
    "Russia": 17098246,
    "Argentina": 2780400,
    "Zambia": 752612,
    "China": 9596961,
}

cities: dict = {
    "Toronto": ["Canada", 6082000],
    "New York City": ["United States", 18819000],
    "Moscow": ["Russia", 12410000],
    "Buenos Aires": ["Argentina", 14967000],
    "Shanghai": ["China", 25582000],
    "Lusaka": ["Zambia", 1747152],
    "Taipei": ["Taiwan", 2646204],
}

assert merge_dictionaries(countries, cities) == {
    "Taiwan": 36193,
    "Canada": 9984670,
    "United States": 9525067,
    "Russia": 17098246,
    "Argentina": 2780400,
    "Zambia": 752612,
    "China": 9596961,
    "Toronto": ["Canada", 6082000],
    "New York City": ["United States", 18819000],
    "Moscow": ["Russia", 12410000],
    "Buenos Aires": ["Argentina", 14967000],
    "Shanghai": ["China", 25582000],
    "Lusaka": ["Zambia", 1747152],
    "Taipei": ["Taiwan", 2646204],
}

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

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

def dict_from_two_lists(keys: list, values: list) -> dict:
    """
    Args:
        keys: The list of keys for the dictionary
        values: The list of values for the dictionary
    Returns: A dictionary of key:value pairs
    """
    if len(keys) != len(values):
        raise ValueError("Lists must be of same length")

    return dict(zip(keys, values))


assert dict_from_two_lists(["first", "second", "third"],
                           ["primary", "secondary", "tertiary"]) == {
                               "first": "primary",
                               "second": "secondary",
                               "third": "tertiary",
                           }

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

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

import json
import requests


def get_values_by_key(dictionary: dict, key: str) -> dict:
    """get_values_by_key.
    Args:
        dictionary (dict): dictionary
        key (str): key
    Returns:
        dict:
    """
    if isinstance(dictionary, dict):
        for k, v in dictionary.items():
            if k == key:
                yield v
            elif isinstance(v, dict):
                for result in get_values_by_key(v, key):
                    yield result
            elif type(v) in (list, tuple):
                for d in v:
                    for seq in get_values_by_key(d, key):
                        if type(seq) in (list, tuple):
                            for inner_item in seq:
                                yield inner_item
                        else:
                            yield seq


dict1 = dict(
    json.loads(
        requests.get("http://ergast.com/api/f1/2004/1/results.json").text))

assert "http://en.wikipedia.org/wiki/McLaren" in list(
    get_values_by_key(dict1, "url"))

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

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

import json
import requests


def get_key_by_value(dictionary: dict, val: str) -> object:
    """get_values_by_key.
    Args:
        dictionary (dict): dictionary
        val (str): value
    Returns:
        dict:
    """
    if isinstance(dictionary, dict):
        for k, v in dictionary.items():
            if val == v:
                yield k
            elif isinstance(v, dict):
                for result in get_key_by_value(v, val):
                    yield result
            elif isinstance(v, list):
                for list_item in v:
                    for result in get_key_by_value(list_item, val):
                        yield result


dict1 = dict(
    json.loads(
        requests.get("http://ergast.com/api/f1/2004/1/results.json").text))

assert "raceName" in list(get_key_by_value(dict1, "Australian Grand Prix"))

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

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

asci_uppercase: dict = {i: chr(+i) for i in range(65, 91, 1)}
digits: dict = {i: chr(+i) for i in range(48, 58, 1)}
asci_lowercase: dict = {i: chr(+i) for i in range(97, 123, 1)}
asci_punctuation: dict = {i: chr(+i) for i in range(32, 48, 1)}
asci_punctuation.update({i: chr(+i) for i in range(123, 127, 1)})
asci_punctuation.update({i: chr(+i) for i in range(91, 97, 1)})
asci_punctuation.update({i: chr(+i) for i in range(58, 65, 1)})
asci_extended: dict = {i: chr(+i) for i in range(128, 255, 1)}
asci_system: dict = {i: hex(i) for i in range(0, 32, 1)}

ascii_chars: dict = {}
ascii_chars.update({"asci_punctuation": asci_punctuation})
ascii_chars.update({"asci_lowercase": asci_lowercase})
ascii_chars.update({"asci_uppercase": asci_uppercase})
ascii_chars.update({"digits": digits})
ascii_chars.update({"asci_extended": asci_extended})
ascii_chars.update({"asci_system": asci_system})

print(ascii_chars)

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

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

from collections import defaultdict as dd

s = [("John", "Male"), ("John", "48"), ("John", "Married"), ("Jane", "Female"),
     ("Jane", "25")]

dict1: dict = dd(list)

for k, v in s:
    dict1[k].append(v)

assert dict1["John"] == ["Male", "48", "Married"]
assert dict1["Jane"] == ["Female", "25"]

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

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

from collections import defaultdict as dd

s = [("John", "Male", 25), ("Fred", "Female", 48), ("Sam", "Female", 41),
     ("Jane", "Female", 25)]

dict1 = dd(dict)

for name, gender, age in s:
    dict1[name]["age"] = age
    dict1[name]["gender"] = gender

assert dict1["John"] == {"age": 25, "gender": "Male"}
assert dict1["Fred"] == {"age": 48, "gender": "Female"}
assert dict1["Sam"] == {"age": 41, "gender": "Female"}
assert dict1["Jane"] == {"age": 25, "gender": "Female"

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

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

from collections import defaultdict as dd
import csv
import requests

url: str = "https://data.london.gov.uk/download/london-borough-profiles/c1693b82-68b1-44ee-beb2-3decf17dc1f8/london-borough-profiles.csv "

boroughs = (requests.get(url).text).split("\n")
reader = csv.DictReader(boroughs, dialect="excel")
dict1 = dd(dict)

for row in reader:
    dict1[row["Code"]] = row

assert dict1["E09000001"]["Area_name"] == "City of London"
assert dict1["E09000032"]["Inner/_Outer_London"] == "Inner London"

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

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

digits: dict = {i: chr(+i) for i in range(48, 58, 1)}
key = 50

try:
    val = digits.pop(key)
except KeyError:
    print (f"The item with key {key} did not exist.")
else:
    print(f"Deleted item with key {key} with value {val}.")

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

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

import copy

original = {}
original["object1"] = dict({"a": {"b": [1, 2, 3]}})

dict_shallow = original.copy()
dict_deep = copy.deepcopy(original)

# change the mutable object in original and dict_shallow
original["object1"]["a"]["b"] = [3, 4, 5]

assert id(original["object1"]) == id(dict_shallow["object1"])
assert id(original["object1"]) != id(dict_deep["object1"])

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

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

def invert_dictionary(input:dict)->dict:
    return {v: k for k, v in dict1.items()}

dict1 = {"a": 1, "b": 2, "c": 3}

assert invert_dictionary(dict1) == {1: "a", 2: "b", 3: "c"}

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

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

import shelve

dict1 = {"a": 1, "b": 2, "c": 3}
dict2 = {1: "a", 2: "b", 3: "c"}
dict3 = {}
dict4 = {}

with shelve.open("dictionary-shelf", "c") as shelf:
    shelf["first"] = dict1
    shelf["second"] = dict2

with shelve.open("dictionary-shelf", "r") as shelf:
    dict3 = shelf["first"]
    dict4 = shelf["second"]

assert dict1 == dict3
assert dict2 == dict4
assert dict1 != dict2
assert dict3 != dict4

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

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

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

import json
from datetime import datetime


class PythonObjectEncoder(json.JSONEncoder):

    def default(self, an_object_value):

        if isinstance(an_object_value, str):
            return an_object_value

        try:
            iterable = iter(an_object_value)
            return list(iterable)
        except TypeError:
            pass  # this object is not iterable

        if isinstance(an_object_value, datetime):
            return an_object_value.isoformat()

        elif hasattr(an_object_value, "__class__"):
            return an_object_value.__dict__

        return super().default(an_object_value)


def dict_to_json(the_dict: dict) -> str:

    return PythonObjectEncoder().encode(the_dict)


class SimpleClass:
    def __init__(self):
        self.instance_value = 10


b = SimpleClass()

dict1: dict = {}
dict1["string"] = "Hello, World!"
dict1["datetime"] = datetime(2030, 12, 31)
dict1["nothing"] = None
dict1["set"]: set = {0, 1, 2, 3}
dict1["tuple"] = ("apples", "fresh", "green")
dict1["list"] = ["a", "b", "c"]
dict1["simple_class"] = SimpleClass()

assert (
        dict_to_json(dict1) ==
            (
                '{"string": "Hello, World!",' 
                ' "datetime": "2030-12-31T00:00:00",'
                ' "nothing": null,'
                ' "set": [0, 1, 2, 3],'
                ' "tuple": ["apples", "fresh", "green"],'
                ' "list": ["a", "b", "c"],'
                ' "simple_class": {"instance_value": 10}}'
            )
    )

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

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

def remove_even_items(the_dict: dict):

    # collect the keys of all the items to remove
    delete_these = set(k for k, v in the_dict.items() if v % 2 == 0)

    for delete_this in delete_these:
        del the_dict[delete_this]

    return the_dict


dict1: dict = dict(enumerate(range(13)))
assert remove_even_items(dict1) == {1: 1, 3: 3, 5: 5, 7: 7, 9: 9, 11: 11}

سخن پایانی

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

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

نظر شما چیست؟

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