Câu trả lời:
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
x
là một thể hiện của lớp kiểu cũ, sau đóx.__class__
chỉ định lớp củax
, nhưngtype(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ừ
object
hay không, các lớp là kiểu mới trong Python 3.
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ó.
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.
object
.
class AnotherOldStyleClass: pass
class A: pass
và class A(): pass
hoà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 is
vàis not
Thay đổi hành vi quan trọng giữa các lớp phong cách cũ và mới
Exception
(ví dụ bên dưới)__slots__
thêmNó đã đượ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)
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
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
%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
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.
Đâ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:
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>
>>>
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>
>>>
_names_cache
là 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ó.
__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 if
là thích hợp hơn .setdefault()
.
__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ó.
Các lớp kiểu mới kế thừa từ object
và 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.
Các lớp phong cách mới có thể sử dụng super(Foo, self)
nơi Foo
là một lớp và self
là 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.
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)
.