Tôi có nên sử dụng một lớp học hoặc từ điển?


99

Tôi có một lớp chỉ chứa các trường và không có phương thức nào, như thế này:

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

Điều này có thể dễ dàng được dịch sang một chính tả. Lớp này linh hoạt hơn để bổ sung trong tương lai và có thể nhanh chóng __slots__. Vì vậy, sẽ có lợi ích của việc sử dụng một dict thay thế? Một câu lệnh sẽ nhanh hơn một lớp học? Và nhanh hơn một lớp học có khe cắm?


2
tôi luôn sử dụng từ điển để lưu giữ dữ liệu và đây là một trường hợp sử dụng đối với tôi. trong một số trường hợp dẫn xuất một lớp từ dictcó thể có ý nghĩa, để. lợi thế gọn gàng: khi bạn gỡ lỗi, chỉ cần nói print(request)và bạn sẽ dễ dàng xem tất cả thông tin trạng thái. với cách tiếp cận cổ điển hơn, bạn sẽ phải viết các __str__phương thức tùy chỉnh của mình , điều này thật tệ nếu bạn phải làm điều đó mọi lúc.
flow

Chúng có thể hoán đổi cho nhau stackoverflow.com/questions/1305532/…
Oleksiy

Nếu lớp học hoàn toàn có ý nghĩa và rõ ràng đối với những người khác, tại sao không? Hơn nữa, nếu bạn định nghĩa nhiều lớp với các giao diện chung chẳng hạn, tại sao không? Nhưng Python không hỗ trợ các khái niệm hướng đối tượng mạnh mẽ như C ++.
MasterControlProgram

3
@Ralf những gì OOP không hỗ trợ python?
qwr

Câu trả lời:


31

Tại sao bạn lại làm cho nó trở thành một cuốn từ điển? Ưu điểm là gì? Điều gì xảy ra nếu sau này bạn muốn thêm một số mã? __init__Mã của bạn sẽ đi đâu?

Các lớp dành cho nhóm dữ liệu liên quan (và thường là mã).

Từ điển dùng để lưu trữ các mối quan hệ khóa-giá trị, trong đó thường các khóa đều thuộc cùng một kiểu và tất cả các giá trị cũng thuộc một kiểu. Đôi khi chúng có thể hữu ích cho việc nhóm dữ liệu khi tên khóa / thuộc tính không phải tất cả đều được biết trước, nhưng đây thường là dấu hiệu cho thấy thiết kế của bạn có vấn đề.

Giữ đây là một lớp học.


Tôi sẽ tạo một loại phương thức nhà máy tạo ra một dict thay vì __init__phương thức của lớp . Nhưng bạn nói đúng: Tôi sẽ tách rời những thứ thuộc về nhau.
phó tế

88
không thể không đồng ý với bạn nhiều hơn: từ điển, bộ, danh sách và bộ giá trị đều ở đó để đóng gói dữ liệu liên quan. không có cách nào có bất kỳ giả định nào rằng các giá trị của từ điển phải hoặc phải có cùng kiểu dữ liệu, hoàn toàn ngược lại. trong nhiều danh sách và tập hợp, các giá trị sẽ có cùng kiểu, nhưng đó chủ yếu là vì đó là những gì chúng ta muốn liệt kê cùng nhau. Tôi thực sự nghĩ rằng việc sử dụng rộng rãi các lớp để lưu giữ dữ liệu là một sự lạm dụng; khi bạn suy nghĩ về các vấn đề tuần tự hóa, bạn có thể dễ dàng hiểu tại sao.
flow

4
Đó là lập trình theo hướng OBJECT chứ không phải theo định hướng lớp vì một lý do: chúng tôi xử lý các đối tượng. Một đối tượng được đặc trưng bởi 2 (3) thuộc tính: 1. trạng thái (thành viên) 2. hành vi (phương thức) và 3. thể hiện có thể được mô tả bằng vài từ. Do đó, các lớp là để gộp trạng thái và hành vi lại với nhau.
friendzis

14
Tôi đã đánh dấu điều này vì bạn nên luôn mặc định cấu trúc dữ liệu đơn giản hơn nếu có thể. Trong trường hợp này, một từ điển là đủ cho mục đích đã định. Câu hỏi đáng where would your __init__ code go?quan tâm. Nó có thể thuyết phục một nhà phát triển ít kinh nghiệm hơn rằng chỉ sử dụng các lớp vì phương thức init không được sử dụng trong từ điển. Phi lý.
Lloyd Moore

1
@Ralf Một lớp Foo chỉ là một kiểu, giống như int và string. Bạn có lưu trữ giá trị ở kiểu số nguyên hay biến foo kiểu số nguyên không? Sự khác biệt tinh tế, nhưng quan trọng về ngữ nghĩa. Trong các ngôn ngữ như C, sự khác biệt này không quá phù hợp bên ngoài giới học thuật. Mặc dù hầu hết các ngôn ngữ OO đều có hỗ trợ biến lớp, điều này làm cho sự phân biệt giữa lớp và đối tượng / thể hiện rất có liên quan - bạn có lưu trữ dữ liệu trong lớp (được chia sẻ trên tất cả các trường hợp) hay trong một đối tượng cụ thể không? Đừng để bị cắn
friendzis

44

Sử dụng từ điển trừ khi bạn cần cơ chế bổ sung của một lớp. Bạn cũng có thể sử dụng một namedtuplephương pháp kết hợp:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

Sự khác biệt về hiệu suất ở đây sẽ là tối thiểu, mặc dù tôi sẽ ngạc nhiên nếu từ điển không nhanh hơn.


15
"Sự khác biệt về hiệu suất ở đây sẽ là tối thiểu , mặc dù tôi sẽ ngạc nhiên nếu từ điển không nhanh hơn đáng kể ." Điều này không tính toán. :)
mipadi

1
@mipadi: điều này đúng. Hiện đã được sửa: p
Katriel. 28/10/10

Mệnh lệnh nhanh hơn 1,5 lần so với lớp có tên và nhanh gấp đôi so với lớp không có khe cắm. Kiểm tra bài viết của tôi về câu trả lời này.
alexpinho98

@ alexpinho98: Tôi đã rất cố gắng tìm 'bài đăng' mà bạn đang đề cập đến, nhưng tôi không thể tìm thấy nó! bạn có thể cung cấp URL. Cảm ơn!
Dan Oblinger

@DanOblinger Tôi cho rằng anh ấy có nghĩa là câu trả lời của mình bên dưới.
Adam Lewis

37

Một lớp trong python một lệnh bên dưới. Bạn nhận được một số chi phí với hành vi lớp, nhưng bạn sẽ không thể nhận thấy nó nếu không có trình biên dịch. Trong trường hợp này, tôi tin rằng bạn được hưởng lợi từ lớp học vì:

  • Tất cả logic của bạn sống trong một chức năng duy nhất
  • Nó dễ dàng cập nhật và luôn được đóng gói
  • Nếu bạn thay đổi bất kỳ điều gì sau đó, bạn có thể dễ dàng giữ nguyên giao diện

Tất cả logic của bạn không tồn tại trong một chức năng duy nhất. Một lớp là một nhóm trạng thái được chia sẻ và thường là một hoặc nhiều phương thức. Nếu bạn thay đổi một lớp học, bạn sẽ không được đảm bảo về giao diện của nó.
Lloyd Moore

25

Tôi nghĩ rằng cách sử dụng của mỗi cái là cách quá chủ quan đối với tôi khi tham gia vào điều đó, vì vậy tôi sẽ chỉ bám vào các con số.

Tôi đã so sánh thời gian cần thiết để tạo và thay đổi một biến trong lớp dict, lớp new_style và lớp new_style với các vị trí.

Đây là mã tôi đã sử dụng để kiểm tra nó (nó hơi lộn xộn nhưng nó hoạt động tốt.)

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

Và đây là đầu ra ...

Đang tạo ...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Thay đổi một biến ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Vì vậy, nếu bạn chỉ lưu trữ các biến, bạn cần tốc độ và nó sẽ không yêu cầu bạn thực hiện nhiều phép tính, tôi khuyên bạn nên sử dụng dict (bạn luôn có thể tạo một hàm trông giống như một phương thức). Tuy nhiên, nếu bạn thực sự cần các lớp học, hãy nhớ - luôn sử dụng __ slot __ .

Ghi chú:

Tôi đã kiểm tra 'Lớp' với cả lớp new_style và old_style. Hóa ra là các lớp old_style được tạo nhanh hơn nhưng sửa đổi chậm hơn (không nhiều nhưng đáng kể nếu bạn đang tạo nhiều lớp trong một vòng lặp chặt chẽ (mẹo: bạn đang làm sai)).

Ngoài ra, thời gian tạo và thay đổi các biến có thể khác nhau trên máy tính của bạn vì máy tính của tôi đã cũ và chậm. Hãy chắc chắn rằng bạn tự kiểm tra nó để xem kết quả 'thực'.

Biên tập:

Sau đó, tôi đã thử nghiệm têntuple: tôi không thể sửa đổi nó nhưng để tạo 10000 mẫu (hoặc cái gì đó tương tự), phải mất 1,4 giây để từ điển thực sự là nhanh nhất.

Nếu tôi thay đổi hàm dict để bao gồm các khóa và giá trị và trả về dict thay vì biến chứa dict khi tôi tạo, nó sẽ cho tôi 0,65 thay vì 0,8 giây.

class Foo(dict):
    pass

Việc tạo giống như một lớp có các khe và việc thay đổi biến là chậm nhất (0,17 giây) nên không sử dụng các lớp này . đi cho một dict (tốc độ) hoặc cho lớp có nguồn gốc từ đối tượng ('cú pháp kẹo')


Tôi muốn xem các số cho một lớp con của dict(không có phương thức bị ghi đè, tôi đoán vậy?). Nó có hoạt động giống như một lớp kiểu mới được viết từ đầu không?
Benjamin Hodgson

12

Tôi đồng ý với @adw. Tôi sẽ không bao giờ trình bày một "đối tượng" (theo nghĩa OO) bằng từ điển. Từ điển tổng hợp các cặp tên / giá trị. Các lớp đại diện cho các đối tượng. Tôi đã thấy mã nơi các đối tượng được biểu diễn bằng từ điển và không rõ hình dạng thực tế của vật đó là gì. Điều gì xảy ra khi không có tên / giá trị nhất định? Điều gì hạn chế khách hàng đưa bất cứ thứ gì vào. Hoặc cố gắng lấy bất cứ thứ gì ra. Hình dạng của vật phải luôn được xác định rõ ràng.

Khi sử dụng Python, điều quan trọng là phải xây dựng kỷ luật vì ngôn ngữ này cho phép nhiều cách để tác giả bắn vào chân anh ta / cô ta.


9
Các câu trả lời trên SO được sắp xếp theo số phiếu, và các câu trả lời có cùng số phiếu được sắp xếp ngẫu nhiên. Vì vậy, hãy làm rõ ý của bạn bằng "người đăng cuối cùng".
Mike DeSimone

4
Và làm thế nào để bạn biết rằng một thứ chỉ có các trường và không có chức năng là một "đối tượng" theo nghĩa OO?
Muhammad Alkarouri

1
Bài kiểm tra quỳ của tôi là "cấu trúc của dữ liệu này có cố định không?". Nếu có, hãy sử dụng một đối tượng, nếu không, một mệnh lệnh. Khởi hành từ điều này sẽ chỉ tạo ra sự nhầm lẫn.
weberc2

Bạn phải phân tích bối cảnh của vấn đề bạn đang giải quyết. Các đối tượng sẽ tương đối rõ ràng khi bạn đã quen thuộc với cảnh quan đó. Cá nhân tôi nghĩ rằng trả lại từ điển nên là phương sách cuối cùng của bạn trừ khi những gì bạn đang trả lại NÊN là một ánh xạ các cặp tên / giá trị. Tôi đã thấy quá nhiều mã cẩu thả trong đó mọi thứ được truyền vào và chuyển ra khỏi các phương thức chỉ là một từ điển. Nó lười biếng. Tôi thích các ngôn ngữ được gõ động, nhưng tôi cũng thích mã của mình phải rõ ràng và logic. Chuyển mọi thứ vào từ điển có thể là một sự tiện lợi mà ẩn chứa ý nghĩa.
jaydel

5

Tôi muốn giới thiệu một lớp học, vì nó là tất cả các loại thông tin liên quan đến một yêu cầu. Nếu một người sử dụng từ điển, tôi hy vọng dữ liệu được lưu trữ sẽ giống hơn nhiều về bản chất. Một nguyên tắc mà tôi có xu hướng làm theo bản thân là nếu tôi có thể muốn lặp lại toàn bộ tập hợp các cặp khóa-> giá trị và làm điều gì đó, tôi sử dụng từ điển. Nếu không, dữ liệu rõ ràng có nhiều cấu trúc hơn so với một ánh xạ giá trị> khóa cơ bản, nghĩa là một lớp có thể sẽ là một lựa chọn thay thế tốt hơn.

Do đó, hãy gắn bó với lớp học.


2
Tôi hoàn toàn không đồng ý. Không có lý do gì để hạn chế việc sử dụng từ điển vào những thứ cần lặp lại. Chúng để duy trì một bản đồ.
Katriel

1
Vui lòng đọc kỹ hơn một chút. Tôi nói có thể muốn lặp lại , không sẽ . Đối với tôi, việc sử dụng từ điển ngụ ý rằng có một sự tương đồng về chức năng lớn giữa các khóa và giá trị. Ví dụ, tên có tuổi. Sử dụng một lớp có nghĩa là các 'khóa' khác nhau có các giá trị với ý nghĩa rất khác nhau. Ví dụ, tham gia một lớp PErson. Ở đây, 'tên' sẽ là một chuỗi, và 'bạn bè' có thể là danh sách, từ điển hoặc một số đối tượng phù hợp khác. Bạn sẽ không lặp lại tất cả các thuộc tính này như một phần của việc sử dụng bình thường của lớp này.
Stigma

1
Tôi nghĩ rằng sự phân biệt giữa các lớp và từ điển không rõ ràng trong Python bởi thực tế là cái trước được thực hiện bằng cách sử dụng cái sau (không kể đến 'slot'). Tôi biết điều này khiến tôi bối rối một chút khi lần đầu tiên học ngôn ngữ (cùng với thực tế là các lớp là các đối tượng và do đó là các trường hợp của một số kính thiên thạch bí ẩn).
martineau 29/10/10

4

Nếu tất cả những gì bạn muốn đạt được là cú pháp candy như obj.bla = 5thay vì obj['bla'] = 5, đặc biệt nếu bạn phải lặp lại điều đó nhiều, bạn có thể muốn sử dụng một số lớp vùng chứa đơn giản như trong đề xuất của martineaus. Tuy nhiên, mã ở đó khá cồng kềnh và chậm không cần thiết. Bạn có thể giữ nó đơn giản như vậy:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

Một lý do khác để chuyển sang namedtuples hoặc một lớp có __slots__thể là sử dụng bộ nhớ. Các lệnh yêu cầu nhiều bộ nhớ hơn đáng kể so với các loại danh sách, vì vậy đây có thể là một điểm cần suy nghĩ.

Dù sao, trong trường hợp cụ thể của bạn, dường như không có bất kỳ động lực nào để chuyển khỏi triển khai hiện tại của bạn. Bạn dường như không duy trì hàng triệu đối tượng này, vì vậy không cần có kiểu dẫn xuất danh sách. Và nó thực sự chứa một số logic chức năng bên trong __init__, vì vậy bạn cũng không nên có AttrDict.


types.SimpleNamespace(có sẵn kể từ Python 3.3) là một thay thế cho AttrDict tùy chỉnh.
Cristian Ciupitu

4

Có thể có bánh của bạn và ăn nó, quá. Nói cách khác, bạn có thể tạo thứ gì đó cung cấp chức năng của cả lớp và thể hiện từ điển. Xem công thức Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss của ActiveState và nhận xét về cách làm điều đó.

Nếu bạn quyết định sử dụng một lớp thông thường thay vì một lớp con, tôi đã nhận thấy công thức Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" (của Alex Martelli) rất linh hoạt và hữu ích cho loại thứ. có vẻ như bạn đang làm (tức là tạo một bộ tổng hợp thông tin tương đối đơn giản). Vì nó là một lớp nên bạn có thể dễ dàng mở rộng chức năng của nó hơn nữa bằng cách thêm các phương thức.

Cuối cùng, cần lưu ý rằng tên của các thành viên lớp phải là định danh Python hợp pháp, nhưng khóa từ điển thì không — vì vậy, từ điển sẽ cung cấp sự tự do hơn về mặt đó vì khóa có thể là bất kỳ thứ gì có thể băm được (ngay cả thứ không phải là chuỗi).

Cập nhật

Một lớp object(không có __dict__) lớp con được đặt tên SimpleNamespace(có một) đã được thêm vào typesmô-đun Python 3.3 và là một thay thế khác.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.