HomeLập trìnhPythonCách làm phẳng...

Cách làm phẳng từ điển bằng Python theo 4 cách khác nhau


Trong bài đăng này, chúng ta sẽ xem xét 4 cách khác nhau để làm phẳng một lệnh trong Python. Đối với mỗi phương pháp tôi sẽ chỉ ra ưu và nhược điểmvà tôi sẽ đưa ra một phân tích nhanh về hiệu suất. Đối với hướng dẫn này, tôi đã chạy tất cả các ví dụ trên Python 3.7.

Tại sao bạn nên biết cách làm phẳng một Dict trong Python?

Có nhiều lý do bạn cần một cuốn từ điển phẳng. Một là nó làm cho việc so sánh hai ký hiệu trở nên đơn giản hơn. Điều khác là việc điều hướng và thao tác với nó dễ dàng hơn, vì cấu trúc phẳng có độ sâu một tầng.

Python là một ngôn ngữ linh hoạt, nghĩa là bạn có thể đạt được các mục tiêu giống nhau theo nhiều cách. Việc chọn giải pháp tốt nhất cho một vấn đề đòi hỏi phải cân nhắc lợi ích của giải pháp này so với giải pháp khác.

Mục tiêu của bài đăng này là cung cấp cho bạn nhiều tùy chọn cho vấn đề này và cung cấp cho bạn càng nhiều dữ liệu càng tốt để bạn có thể đưa ra quyết định sáng suốt. Vì vậy, chúng ta hãy đi.

Tái bút: Nếu bạn không có Python 3.7, bạn có thể cài đặt nó bằng pyenv và thậm chí có nhiều phiên bản cùng lúc mà không xung đột.

Mục lục

  1. Sử dụng chức năng đệ quy của riêng bạn
  2. Sử dụng hàm đệ quy riêng của bạn + Trình tạo
  3. Sử dụng gấu trúc json_normalize
  4. Sử dụng flatdict Thư viện
  5. Phần kết luận

Cách làm phẳng một Dict trong Python bằng hàm đệ quy của riêng bạn

Nhìn nhanh vào Google dẫn chúng ta đến stackoverflow. Câu trả lời đầu tiên cho thấy một hàm đệ quy duyệt qua từ điển và trả về một thể hiện phẳng. Tôi sẽ lấy cảm hứng từ chức năng đó và hiển thị một phiên bản cải tiến hơn một chút.

Chúng ta có thể bắt đầu bằng gợi ý kiểu để cải thiện khả năng đọc và làm cho kiểu gõ an toàn.

from collections.abc import MutableMapping

def flatten_dict(d: MutableMapping, parent_key: str="", sep: str=".") -> MutableMapping:
    items = []
    for k, v in d.items():
        new_key = parent_key + sep + k if parent_key else k
        if isinstance(v, MutableMapping):
            items.extend(flatten_dict(v, new_key, sep=sep).items())
        else:
            items.append((new_key, v))
    return dict(items)


>>> flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
{'a': 1, 'c.a': 2, 'c.b.x': 3, 'c.b.y': 4, 'c.b.z': 5, 'd': [6, 7, 8]}

Điểm chuẩn hiệu suất

Chúng tôi đã nhanh chóng xác minh rằng hàm trả về một lệnh phẳng, nhưng hiệu suất của nó thì sao? Nó có tốt cho sử dụng sản xuất không? Hãy chạy một điểm chuẩn nhanh để xem nó nhanh như thế nào.

Đối với điều này và tất cả các điểm chuẩn trong bài viết này, tôi sẽ sử dụng IPython‘S timeit chức năng ma thuật và memit từ memory_profiler thư viện.

Tái bút: Dành cho %memit để làm việc, bạn cần phải chạy %load_ext memory_profiler đầu tiên.

In [4]: %timeit flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
7.28 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [5]: %load_ext memory_profiler

In [6]: %memit flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
peak memory: 84.94 MiB, increment: 0.29 MiB

Ưu điểm: Dễ hiểu, và nó chỉ hoạt động.

Nhược điểm: Nó lưu trữ các mục trong một danh sách trong bộ nhớ mà sau đó được chuyển đến dict người xây dựng. Điều này gây lãng phí không chỉ về bộ nhớ mà còn về tốc độ.

Mặc dù việc thêm các phần tử vào danh sách trong Python rất nhanh, nhưng thực sự không cần thiết phải thực hiện lặp đi lặp lại. Trong phần tiếp theo, chúng ta sẽ xem cách cải thiện điều này bằng cách sử dụng máy phát điện.

Cách làm phẳng một Dict trong Python bằng Hàm đệ quy của riêng bạn + Trình tạo

Phiên bản đầu tiên hoạt động và hơi nhanh. Tuy nhiên, nó có một vấn đề.

Để tạo một từ điển mới với các phím phẳng, nó duy trì trong bộ nhớ Python list. Điều này là không hiệu quả, vì chúng ta phải giữ toàn bộ cấu trúc dữ liệu trong bộ nhớ chỉ để phục vụ như một bộ lưu trữ tạm thời.

Một giải pháp tốt hơn nhiều là sử dụng trình tạo của Python, một đối tượng có thể tạm dừng thực thi và ghi nhớ trạng thái có thể tiếp tục lại sau này. Bằng cách sử dụng trình tạo, chúng tôi có thể loại bỏ danh sách tạm thời mà không thay đổi hành vi.

from collections.abc import MutableMapping

def _flatten_dict_gen(d, parent_key, sep):
    for k, v in d.items():
        new_key = parent_key + sep + k if parent_key else k
        if isinstance(v, MutableMapping):
            yield from flatten_dict(v, new_key, sep=sep).items()
        else:
            yield new_key, v


def flatten_dict(d: MutableMapping, parent_key: str="", sep: str="."):
    return dict(_flatten_dict_gen(d, parent_key, sep))

>>> flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
{'a': 1, 'c.a': 2, 'c.b.x': 3, 'c.b.y': 4, 'c.b.z': 5, 'd': [6, 7, 8]}

Điểm chuẩn hiệu suất

In [9]: %timeit flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
7.39 µs ± 78.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %memit flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
peak memory: 45.27 MiB, increment: 0.25 MiB

Ưu điểm: Dễ hiểu, nó hoạt động giống như phiên bản trước và bộ nhớ hiệu quả. Phiên bản này tiêu thụ bộ nhớ ít hơn khoảng 50% so với phiên bản sử dụng danh sách.

Nhược điểm: Nó có thể không xử lý các trường hợp cạnh rất tốt. Ví dụ: nếu chúng ta chuyển một đối tượng giống như từ điển không phải là một thể hiện của MutableMapping thì ví dụ này sẽ thất bại. Nhưng đây cũng là một nhược điểm của phiên bản trước.

Cách làm phẳng một Dict trong Python bằng gấu trúc json_normalize

Như chúng ta có thể thấy, các giải pháp trước hoạt động tốt, nhưng việc viết ra giải pháp của riêng mình cho một vấn đề phổ biến như thế này là phát minh lại bánh xe. Thay vào đó, chúng ta có thể sử dụng các thư viện thao tác dữ liệu phổ biến như pandas.

pandas đi kèm với một hàm chung để chuẩn hóa các đối tượng JSON được biểu diễn bằng Python dưới dạng từ điển. Đây là một cơ hội tuyệt vời để chúng tôi không tạo lại các giải pháp hiện có và sử dụng một giải pháp mạnh mẽ hơn.

Hơn nữa, kết quả cuối cùng trông tuyệt vời chỉ trong một dòng và thậm chí chúng ta có thể ẩn nó sau một giao diện mỏng.

from collections.abc import MutableMapping
import pandas as pd

def flatten_dict(d: MutableMapping, sep: str=".") -> MutableMapping:
    [flat_dict] = pd.json_normalize(d, sep=sep).to_dict(orient="records")
    return flat_dict


>>> flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
{'a': 1, 'd': [6, 7, 8], 'c.a': 2, 'c.b.x': 3, 'c.b.y': 4, 'c.b.z': 5}

Điểm chuẩn hiệu suất

In [5]: %timeit flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
779 µs ± 10.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [6]: %memit flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]})
peak memory: 86.30 MiB, increment: 0.90 MiB

Ưu điểm: Dễ hiểu và chúng tôi sử dụng lại một thư viện đã được thiết lập tốt.

Nhược điểm: sử dụng pandas chỉ để làm phẳng một lệnh có vẻ như quá mức cần thiết. Nếu dự án của bạn không cần nó, thì chúng ta có thể sử dụng thư viện nhẹ hơn, chẳng hạn như FlatDict. Bên cạnh đó, theo timeit phiên bản này là chậm hơn 100 lần hơn là sử dụng giải pháp của riêng chúng tôi, điều này không tuyệt vời.

Cách làm phẳng một Dict trong Python bằng cách sử dụng flatdict Thư viện

flatdict là một thư viện Python tạo một lệnh chính cấp đơn từ một lệnh lồng nhau và có sẵn từ Python 3.5 trở đi.

Cho đến nay, chúng tôi đã thấy rằng việc viết giải pháp tùy chỉnh của chúng tôi có thể không lý tưởng và sử dụng thư viện toàn diện như pandas chỉ cho mục đích này cũng không tuyệt vời.

Như một sự thay thế chúng ta có thể sử dụng flatdictnhẹ hơn nhiều và đã được thử nghiệm trong trận chiến.

Thư viện rất linh hoạt và cũng cho phép chúng tôi sử dụng các dấu phân cách tùy chỉnh. Tuy nhiên, một trong những tính năng tốt nhất mà nó cung cấp là khả năng truy cập từ điển mới được tạo như trước đây – nghĩa là bạn có thể truy cập các giá trị bằng cách sử dụng khóa mới hoặc khóa cũ.

Hãy xem một ví dụ.

>>> import flatdict
>>> d =  flatdict.FlatDict(data, delimiter=".")

# d is a FlatDict instance
>>> d
<FlatDict id=140665244199904 {'a': 1, 'c.a': 2, 'c.b.x': 3, 'c.b.y': 4, 'c.b.z': 5, 'd': [6, 7, 8]}>"

# and it allows accessing flat keys
>>> d['c.b.y']
4

# but also nested ones
>>> d['c']['b']['y']
4

# and can be converted to a flatten dict
>>> dict(d)
{'a': 1, 'c.a': 2, 'c.b.x': 3, 'c.b.y': 4, 'c.b.z': 5, 'd': [6, 7, 8]}

Bạn có thể thấy, flatdict cho phép sự linh hoạt và thuận tiện tuyệt vời.

Điểm chuẩn hiệu suất

In [3]: %timeit flatdict.FlatDict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]}, delimiter=".")
8.97 µs ± 21.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: %memit flatdict.FlatDict({'a': 1, 'c': {'a': 2, 'b': {'x': 3, 'y': 4, 'z': 5}}, 'd': [6, 7, 8]}, delimiter=".")
peak memory: 45.21 MiB, increment: 0.14 MiB

Ưu điểm: Dễ hiểu và nó chỉ hoạt động và đó là một thư viện nhẹ. Cho phép truy cập các phần tử lồng nhau theo hai cách khác nhau. Nhanh và bộ nhớ hiệu quả như giải pháp sử dụng trình tạo.

Nhược điểm: Nó vẫn là một thư viện bên ngoài và giống như nhiều công cụ nguồn mở, nếu có lỗi, bạn cần đợi tác giả sửa nó. Và đôi khi các tác giả từ bỏ dự án của họ, điều này dẫn đến rủi ro cho dự án của bạn. Bất chấp điều đó, tôi vẫn nghĩ rằng ưu điểm vượt trội hơn nhược điểm trong trường hợp này.

Phần kết luận

Trong bài đăng này, chúng ta đã thấy 4 cách khác nhau để làm phẳng một từ điển trong Python. Mỗi giải pháp đều có ưu và nhược điểm, và việc chọn giải pháp tốt nhất là vấn đề về sở thích cá nhân và những ràng buộc của dự án.

Tôi hy vọng bạn thích nó và hẹn gặp lại bạn lần sau!



Zik.vn – Biên dịch & Biên soạn Lại

Đọc thêm  Hiểu từ điển trong Python – Giải thích về cách hiểu từ điển
spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status