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

دیکشنریها قلب و جان پایتون محسوب میشوند. در این مقاله در مورد 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}")
خروجی کد فوق چنین است:
مرتبسازی یک دیکشنری
از پایتون نسخه 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}
سخن پایانی
امیدواریم این مطلب مورد توجه شما قرار گرفته باشد. هر گونه دیدگاه یا پیشنهاد خود را در مورد موضوع این مطلب در بخش نظرات این نوشته با ما و دیگر خوانندگان مجله فرادرس در میان بگذارید.