Sự khác biệt giữa phong cách cũ và các lớp phong cách mới trong Python là gì?


Câu trả lời:


560

Từ các lớp học mới và phong cách cổ điển :

Cho đến Python 2.1, các lớp kiểu cũ là hương vị duy nhất có sẵn cho người dùng.

Khái niệm lớp (kiểu cũ) không liên quan đến khái niệm kiểu: if xlà một thể hiện của lớp kiểu cũ, sau đó x.__class__ chỉ định lớp của x, nhưng type(x)luôn luôn <type 'instance'>.

Điều này phản ánh thực tế là tất cả các thể hiện kiểu cũ, độc lập với lớp của chúng, được triển khai với một kiểu dựng sẵn duy nhất, được gọi là thể hiện.

Các lớp kiểu mới đã được giới thiệu trong Python 2.2 để thống nhất các khái niệm về lớp và kiểu . Một lớp kiểu mới chỉ đơn giản là một kiểu do người dùng định nghĩa, không hơn, không kém.

Nếu x là một thể hiện của một lớp kiểu mới, thì type(x)thường giống như x.__class__(mặc dù điều này không được đảm bảo - một thể hiện của lớp kiểu mới được phép ghi đè giá trị được trả về x.__class__).

Động lực chính để giới thiệu các lớp kiểu mới là cung cấp một mô hình đối tượng thống nhất với một mô hình meta đầy đủ .

Nó cũng có một số lợi ích ngay lập tức, như khả năng phân lớp hầu hết các loại tích hợp sẵn hoặc giới thiệu "mô tả", cho phép các thuộc tính được tính toán.

Vì lý do tương thích, các lớp vẫn theo kiểu cũ theo mặc định .

Các lớp kiểu mới được tạo bằng cách chỉ định một lớp kiểu mới khác (tức là một kiểu) là một lớp cha hoặc đối tượng "kiểu cấp cao nhất" nếu không cần cha mẹ khác.

Hành vi của các lớp kiểu mới khác với các lớp kiểu cũ ở một số chi tiết quan trọng bên cạnh kiểu trả về.

Một số thay đổi này là nền tảng cho mô hình đối tượng mới, giống như cách các phương thức đặc biệt được gọi. Những cái khác là "các bản sửa lỗi" không thể được thực hiện trước đây vì những lo ngại về tính tương thích, như thứ tự giải quyết phương pháp trong trường hợp thừa kế nhiều lần.

Python 3 chỉ có các lớp kiểu mới .

Bất kể bạn có phân lớp từ objecthay không, các lớp là kiểu mới trong Python 3.


41
Không có sự khác biệt nào trong số này có vẻ giống như lý do thuyết phục để sử dụng các lớp kiểu mới, tuy nhiên mọi người đều nói rằng bạn nên luôn luôn sử dụng kiểu mới. Nếu tôi đang sử dụng kiểu gõ vịt như tôi nên, tôi không bao giờ cần sử dụng type(x). Nếu tôi không phân lớp một kiểu dựng sẵn, thì dường như không có bất kỳ lợi thế nào mà tôi có thể thấy về các lớp kiểu mới. Có một bất lợi, đó là gõ thêm (object).
đệ quy

78
Một số tính năng như super()không hoạt động trên các lớp kiểu cũ. Chưa kể, như bài báo đó nói, có những sửa chữa cơ bản, như MRO, và các phương pháp đặc biệt, đó là nhiều lý do tốt để sử dụng nó.
John Doe

21
@ Người dùng: Các lớp kiểu cũ hoạt động giống như trong 2.7 như họ đã làm trong 2.1, và bởi vì ít người còn nhớ những điều kỳ quặc và tài liệu không còn thảo luận về hầu hết trong số họ, thậm chí còn tệ hơn. Trích dẫn tài liệu ở trên nói trực tiếp điều này: có những "sửa lỗi" không thể thực hiện trên các lớp kiểu cũ. Trừ khi bạn muốn chạy theo những điều kỳ quặc mà không ai khác đã xử lý kể từ Python 2.1 và tài liệu không còn giải thích được nữa, đừng sử dụng các lớp kiểu cũ.
abarnert

10
Đây là một ví dụ về một trò chơi mà bạn có thể vấp ngã nếu bạn sử dụng các lớp kiểu cũ trong 2.7: bug.python.org/su21785
KT.

5
Đối với bất kỳ ai thắc mắc, một lý do chính đáng để kế thừa rõ ràng từ đối tượng trong Python 3 là nó giúp hỗ trợ nhiều phiên bản Python dễ dàng hơn.
jpmc26

308

Tuyên bố-khôn ngoan:

Các lớp kiểu mới kế thừa từ đối tượng hoặc từ một lớp kiểu mới khác.

class NewStyleClass(object):
    pass

class AnotherNewStyleClass(NewStyleClass):
    pass

Các lớp học kiểu cũ không.

class OldStyleClass():
    pass

Lưu ý 3 Python:

Python 3 không hỗ trợ các lớp kiểu cũ, do đó, bất kỳ hình thức nào được ghi chú ở trên đều dẫn đến một lớp kiểu mới.


24
nếu một lớp kiểu mới kế thừa từ một lớp kiểu mới khác, thì bằng cách mở rộng, nó sẽ kế thừa từ đó object.
aaronasterling

2
Đây có phải là một ví dụ không chính xác của lớp python kiểu cũ? class AnotherOldStyleClass: pass
Ankur Agarwal

11
@abc Tôi tin điều đó class A: passclass A(): passhoàn toàn tương đương. Cái đầu tiên có nghĩa là "A không thừa kế của bất kỳ lớp cha mẹ nào" và cái thứ hai có nghĩa là "Một thừa kế không có lớp cha mẹ" . Điều đó khá giống với not isis not
Eyquem 20/12/13

5
Cũng như một ghi chú bên lề, đối với 3.X, việc thừa kế "đối tượng" sẽ tự động được thừa nhận (có nghĩa là chúng tôi không có cách nào để không kế thừa "đối tượng" trong 3.X). Đối với lý do tương thích ngược, mặc dù vậy vẫn không tệ khi giữ "(đối tượng)" ở đó.
Yo Hsiao

1
Nếu chúng ta sẽ có được kỹ thuật về các lớp kế thừa, câu trả lời này sẽ lưu ý rằng bạn tạo một lớp kiểu cũ khác bằng cách kế thừa từ một lớp kiểu cũ. (Như đã viết, câu trả lời này khiến người dùng đặt câu hỏi liệu bạn có thể thừa kế từ một lớp kiểu cũ hay không. Bạn có thể.)
jpmc26

224

Thay đổi hành vi quan trọng giữa các lớp phong cách cũ và mới

  • siêu thêm
  • MRO đã thay đổi (giải thích bên dưới)
  • mô tả thêm
  • các đối tượng lớp kiểu mới không thể được nâng lên trừ khi xuất phát từ Exception(ví dụ bên dưới)
  • __slots__ thêm

MRO (Lệnh giải quyết phương pháp) đã thay đổi

Nó đã được đề cập trong các câu trả lời khác, nhưng đây là một ví dụ cụ thể về sự khác biệt giữa MRO cổ điển và MRO C3 (được sử dụng trong các lớp phong cách mới).

Câu hỏi là thứ tự mà các thuộc tính (bao gồm các phương thức và biến thành viên) được tìm kiếm trong nhiều kế thừa.

Các lớp cổ điển thực hiện tìm kiếm theo chiều sâu từ trái sang phải. Dừng lại ở trận đấu đầu tiên. Họ không có __mro__thuộc tính.

class C: i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 0
assert C21().i == 2

try:
    C12.__mro__
except AttributeError:
    pass
else:
    assert False

Các lớp kiểu mới MRO phức tạp hơn để tổng hợp trong một câu tiếng Anh. Nó được giải thích chi tiết ở đây . Một trong những thuộc tính của nó là một lớp cơ sở chỉ được tìm kiếm một khi tất cả các lớp dẫn xuất của nó đã được. Họ có __mro__thuộc tính hiển thị thứ tự tìm kiếm.

class C(object): i = 0
class C1(C): pass
class C2(C): i = 2
class C12(C1, C2): pass
class C21(C2, C1): pass

assert C12().i == 2
assert C21().i == 2

assert C12.__mro__ == (C12, C1, C2, C, object)
assert C21.__mro__ == (C21, C2, C1, C, object)

Các đối tượng lớp phong cách mới không thể được nâng lên trừ khi có nguồn gốc từ Exception

Xung quanh Python 2.5, nhiều lớp có thể được nâng lên và xung quanh Python 2.6, lớp này đã bị xóa. Trên Python 2.7.3:

# OK, old:
class Old: pass
try:
    raise Old()
except Old:
    pass
else:
    assert False

# TypeError, new not derived from `Exception`.
class New(object): pass
try:
    raise New()
except TypeError:
    pass
else:
    assert False

# OK, derived from `Exception`.
class New(Exception): pass
try:
    raise New()
except New:
    pass
else:
    assert False

# `'str'` is a new style object, so you can't raise it:
try:
    raise 'str'
except TypeError:
    pass
else:
    assert False

8
Tóm tắt rõ ràng, cảm ơn. Khi bạn nói "khó giải thích bằng tiếng Anh", tôi nghĩ rằng bạn đang mô tả một tìm kiếm theo chiều sâu của bưu điện trái ngược với lớp kiểu cũ sử dụng tìm kiếm theo chiều sâu đặt hàng trước. (preorder có nghĩa là chúng ta tìm kiếm chính mình trước đứa con đầu tiên và postorder có nghĩa là chúng ta tìm kiếm chính mình sau đứa con cuối cùng của chúng ta).
Steve Carter

40

Các lớp kiểu cũ vẫn nhanh hơn một chút để tra cứu thuộc tính. Điều này thường không quan trọng, nhưng nó có thể hữu ích trong mã Python 2.x nhạy cảm với hiệu năng:

Trong [3]: lớp A:
   ...: def __init __ (tự):
   ...: tự.a = 'xin chào'
   ...

Trong [4]: ​​lớp B (đối tượng):
   ...: def __init __ (tự):
   ...: tự.a = 'xin chào'
   ...

Trong [6]: aobj = A ()
Trong [7]: bobj = B ()

Trong [8]:% timeit aobj.a
10000000 vòng, tốt nhất là 3: 78,7 ns mỗi vòng

Trong [10]:% thời gian bobj.a
10000000 vòng, tốt nhất là 3: 86,9 ns mỗi vòng

5
Thật thú vị khi bạn nhận thấy trong thực tế, tôi chỉ đọc rằng điều này là do các lớp kiểu mới, một khi chúng đã tìm thấy thuộc tính trong dict dụ, phải thực hiện một tra cứu bổ sung để tìm hiểu xem nó có phải là một mô tả hay không, ví dụ, nó có lấy phương thức cần được gọi để lấy giá trị được trả về. Các lớp kiểu cũ đơn giản trả về đối tượng tìm thấy không có tính toán bổ sung (nhưng sau đó không hỗ trợ mô tả). Bạn có thể đọc thêm trong bài viết tuyệt vời này của Guido python-history.blogspot.co.uk/2010/06/ , cụ thể là phần trên các vị trí
xuloChavez

1
điều đó dường như không đúng với CPython 2.7.2:%timeit aobj.a 10000000 loops, best of 3: 66.1 ns per loop %timeit bobj.a 10000000 loops, best of 3: 53.9 ns per loop
Benedikt Waldvogel

1
Vẫn nhanh hơn cho aobj trong CPython 2.7.2 trên x86-64 Linux đối với tôi.
xioxox

41
Có lẽ là một ý tưởng tồi khi dựa vào mã Python thuần cho các ứng dụng nhạy cảm hiệu năng. Không ai nói: "Tôi cần mã nhanh nên tôi sẽ sử dụng các lớp Python kiểu cũ." Numpy không được tính là Python thuần túy.
Đám mây Phillip

còn trong IPython 2.7.6, điều này không đúng. '' '' 477 ns so với 456 ns mỗi vòng '' ''
kmonsoor

37

Guido đã viết Câu chuyện bên trong về các lớp học theo phong cách mới , một bài viết thực sự tuyệt vời về lớp học kiểu mới và kiểu cũ trong Python.

Python 3 chỉ có lớp kiểu mới. Ngay cả khi bạn viết một 'lớp kiểu cũ', nó hoàn toàn có nguồn gốc từ object.

Các lớp kiểu mới có một số tính năng nâng cao thiếu trong các lớp kiểu cũ, chẳng hạn như mro C3super mới , một số phương thức ma thuật, v.v.


24

Đây là một sự khác biệt rất thực tế, đúng / sai. Sự khác biệt duy nhất giữa hai phiên bản của đoạn mã sau là ở phiên bản thứ hai Person kế thừa từ đối tượng . Ngoài ra, hai phiên bản giống hệt nhau, nhưng có kết quả khác nhau:

  1. Các lớp học kiểu cũ

    class Person():
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed1 is ahmed2
    print ahmed1
    print ahmed2
    
    
    >>> False
    <__main__.Person instance at 0xb74acf8c>
    <__main__.Person instance at 0xb74ac6cc>
    >>>
    
  2. Lớp học kiểu mới

    class Person(object):
        _names_cache = {}
        def __init__(self,name):
            self.name = name
        def __new__(cls,name):
            return cls._names_cache.setdefault(name,object.__new__(cls,name))
    
    ahmed1 = Person("Ahmed")
    ahmed2 = Person("Ahmed")
    print ahmed2 is ahmed1
    print ahmed1
    print ahmed2
    
    >>> True
    <__main__.Person object at 0xb74ac66c>
    <__main__.Person object at 0xb74ac66c>
    >>>

2
'_names_cache' làm gì? Bạn có thể chia sẻ một tài liệu tham khảo?
Muatik

4
_names_cachelà một từ điển lưu trữ (lưu trữ để phục hồi trong tương lai) mỗi tên bạn chuyển đến Person.__new__. Phương thức setdefault (được định nghĩa trong bất kỳ từ điển nào) có hai đối số: khóa và giá trị. Nếu khóa nằm trong dict, nó sẽ trả về giá trị của nó. Nếu nó không nằm trong lệnh, nó sẽ đặt giá trị đầu tiên thành giá trị được truyền dưới dạng đối số thứ hai và sau đó trả về nó.
ychaouche

4
Việc sử dụng là sai. Ý tưởng là không xây dựng một đối tượng mới nếu nó đã tồn tại, nhưng trong trường hợp của bạn __new__()luôn được gọi và nó luôn xây dựng một đối tượng mới, và sau đó ném nó. Trong trường hợp này, a iflà thích hợp hơn .setdefault().
Amit Upadhyay

Nhưng, tôi không hiểu tại sao sự khác biệt về đầu ra tức là trong lớp kiểu cũ, hai trường hợp khác nhau do đó trả về Sai, nhưng trong lớp kiểu mới, cả hai trường hợp đều giống nhau. Làm sao ? Sự thay đổi trong lớp kiểu mới là gì, làm cho hai trường hợp giống nhau, không phải là lớp kiểu cũ?
Pabitra Pati

1
@PabitraPati: Đây là một cuộc biểu tình rẻ tiền ở đây. __new__thực sự không phải là một thứ cho các lớp kiểu cũ, nó không được sử dụng trong xây dựng thể hiện (nó chỉ là một tên ngẫu nhiên trông đặc biệt, giống như định nghĩa __spam__). Vì vậy, việc xây dựng lớp kiểu cũ chỉ gọi __init__, trong khi cấu trúc kiểu mới gọi __new__(kết hợp với thể hiện đơn lẻ theo tên) để xây dựng và __init__khởi tạo nó.
ShadowRanger

10

Các lớp kiểu mới kế thừa từ objectvà phải được viết như vậy trong Python 2.2 trở đi (tức là class Classname(object):thay vì class Classname:). Thay đổi cốt lõi là để thống nhất các loại và các lớp, và tác dụng phụ tuyệt vời của việc này là nó cho phép bạn kế thừa từ các loại tích hợp.

Đọc descrintro để biết thêm chi tiết.


8

Các lớp phong cách mới có thể sử dụng super(Foo, self)nơi Foolà một lớp và selflà ví dụ.

super(type[, object-or-type])

Trả về một đối tượng proxy ủy nhiệm các cuộc gọi phương thức cho một kiểu cha mẹ hoặc anh chị em của kiểu. Điều này rất hữu ích để truy cập các phương thức được kế thừa đã bị ghi đè trong một lớp. Thứ tự tìm kiếm giống như thứ tự được sử dụng bởi getattr () ngoại trừ loại chính nó bị bỏ qua.

Và trong Python 3.x bạn chỉ có thể sử dụng super()bên trong một lớp mà không có bất kỳ tham số nào.

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.