Hướng dẫn dùng radd python

Các khóa học miễn phí qua video:
Lập trình C Java C# SQL Server PHP HTML5-CSS3-JavaScript

Mục lục bài viết:

  • Mô hình dữ liệu Python
  • Nội bộ của hoạt động như len [] và []
  • Quá tải các chức năng tích hợp
    • Cung cấp chiều dài cho các đối tượng của bạn bằng len []
    • Làm cho các đối tượng của bạn hoạt động với abs []
    • In nhanh các đối tượng của bạn bằng cách sử dụng str []
    • Đại diện cho các đối tượng của bạn bằng cách sử dụng repr []
    • Làm cho các đối tượng của bạn là sự thật hoặc sai bằng cách sử dụng bool []
  • Quá tải các toán tử tích hợp
    • Làm cho các đối tượng của bạn có khả năng được thêm vào bằng cách sử dụng +
    • Phím tắt: Toán tử + =
    • Lập chỉ mục và cắt các đối tượng của bạn bằng []
    • Toán tử đảo ngược: Làm cho lớp học của bạn chính xác về mặt toán học
  • Một ví dụ hoàn chỉnh
  • Tóm tắt và tài nguyên

Nếu bạn đã sử dụng toán tử +or *trên một strđối tượng trong Python, bạn phải nhận thấy hành vi khác biệt của nó khi so sánh với inthoặc floatcác đối tượng:

>>>

>>> # Adds the two numbers
>>> 1 + 2
3

>>> # Concatenates the two strings
>>> 'Real' + 'Python'
'RealPython'


>>> # Gives the product
>>> 3 * 2
6

>>> # Repeats the string
>>> 'Python' * 3
'PythonPythonPython'

Bạn có thể đã tự hỏi làm thế nào mà cùng một toán tử hoặc hàm tích hợp sẵn lại hiển thị các hành vi khác nhau cho các đối tượng của các lớp khác nhau. Điều này được gọi là quá tải toán tử hoặc quá tải hàm tương ứng. Bài viết này sẽ giúp bạn hiểu cơ chế này, để bạn có thể làm điều tương tự trong các lớp Python của riêng mình và làm cho các đối tượng của bạn trở nên giống Pythonic hơn.

Bạn sẽ học những điều sau:

  • API xử lý toán tử và tích hợp sẵn trong Python
  • "Bí mật" đằng sau len[]và các tích hợp khác
  • Làm thế nào để làm cho các lớp của bạn có thể sử dụng các toán tử
  • Cách làm cho các lớp của bạn tương thích với các hàm tích hợp sẵn của Python

Như một phần thưởng, bạn cũng sẽ thấy một lớp mẫu, các đối tượng trong đó sẽ tương thích với nhiều toán tử và hàm này. Bắt đầu nào!

Mô hình dữ liệu Python

Giả sử bạn có một lớp đại diện cho đơn đặt hàng trực tuyến có giỏ hàng [a list] và khách hàng [một strhoặc trường hợp của lớp khác đại diện cho khách hàng].

Trong trường hợp như vậy, việc muốn có được độ dài của danh sách giỏ hàng là điều hoàn toàn tự nhiên. Ai đó mới sử dụng Python có thể quyết định triển khai một phương thức được gọi get_cart_len[]trong lớp của họ để thực hiện điều này. Nhưng bạn có thể định cấu hình tích hợp sẵn len[]theo cách mà nó trả về độ dài của danh sách giỏ hàng khi cho đối tượng của chúng ta.

Trong trường hợp khác, chúng tôi có thể muốn thêm một thứ gì đó vào giỏ hàng. Một lần nữa, ai đó mới sử dụng Python sẽ nghĩ đến việc triển khai một phương thức có tên là append_to_cart[]lấy một mặt hàng và gắn nó vào danh sách giỏ hàng. Nhưng bạn có thể định cấu hình +nhà điều hành theo cách nó gắn một mặt hàng mới vào giỏ hàng.

Python thực hiện tất cả điều này bằng cách sử dụng các phương pháp đặc biệt. Các phương thức đặc biệt này có quy ước đặt tên, trong đó tên bắt đầu bằng hai dấu gạch dưới, tiếp theo là một số nhận dạng và kết thúc bằng một cặp dấu gạch dưới khác.

Về cơ bản, mỗi hàm hoặc toán tử cài sẵn có một phương thức đặc biệt tương ứng với nó. Ví dụ, có __len__[],tương ứng với len[]và __add__[], tương ứng với +toán tử.

Theo mặc định, hầu hết các toán tử và trình cài đặt sẵn sẽ không hoạt động với các đối tượng thuộc các lớp của bạn. Bạn phải thêm các phương thức đặc biệt tương ứng trong định nghĩa lớp của mình để làm cho đối tượng của bạn tương thích với các toán tử và toán tử cài sẵn.

Khi bạn làm điều này, hành vi của hàm hoặc toán tử được liên kết với nó sẽ thay đổi theo điều đó được xác định trong phương thức.

Đây chính xác là những gì Mô hình Dữ liệu [Phần 3 của tài liệu Python] giúp bạn thực hiện. Nó liệt kê tất cả các phương thức đặc biệt có sẵn và cung cấp cho bạn phương tiện nạp chồng các hàm và toán tử cài sẵn để bạn có thể sử dụng chúng trên các đối tượng của riêng mình.

Hãy xem điều này có nghĩa là gì.

Thực tế thú vị: Do quy ước đặt tên sử dụng cho các phương pháp này, họ cũng gọi dunder phương pháp mà là một viết tắt cho d Gấp đôi dưới phương pháp điểm . Đôi khi chúng còn được gọi là phương pháp đặc biệt hoặc phương pháp ma thuật . Chúng tôi thích phương pháp dunder mặc dù!

Nội bộ của Hoạt động Giống như len[][]

Mỗi lớp trong Python đều xác định hành vi của riêng nó cho các hàm và phương thức tích hợp sẵn. Khi bạn chuyển một thể hiện của lớp nào đó vào một hàm dựng sẵn hoặc sử dụng một toán tử trên cá thể đó, nó thực sự tương đương với việc gọi một phương thức đặc biệt với các đối số có liên quan.

Nếu có một built-in chức năng, func[]và các phương pháp đặc biệt tương ứng với chức năng là __func__[], Python giải thích một cuộc gọi đến các chức năng như obj.__func__[], nơi objlà đối tượng. Trong trường hợp toán tử, nếu bạn có một toán tử oprvà phương thức đặc biệt tương ứng cho nó __opr__[], Python sẽ diễn giải một cái gì đó giống obj1 obj2như obj1.__opr__[obj2].

Vì vậy, khi bạn đang gọi len[]một đối tượng, Python xử lý cuộc gọi như obj.__len__[]. Khi bạn sử dụng []toán tử trên một có thể lặp để nhận giá trị tại một chỉ mục, Python sẽ xử lý nó như là itr.__getitem__[index], đâu itrlà đối tượng có thể lặp và indexlà chỉ mục bạn muốn lấy.

Do đó, khi bạn xác định các phương thức đặc biệt này trong lớp của riêng mình, bạn ghi đè hành vi của hàm hoặc toán tử được liên kết với chúng bởi vì, đằng sau, Python đang gọi phương thức của bạn. Chúng ta hãy hiểu rõ hơn về điều này:

>>>

>>> a = 'Real Python'
>>> b = ['Real', 'Python']
>>> len[a]
11
>>> a.__len__[]
11
>>> b[0]
'Real'
>>> b.__getitem__[0]
'Real'

Như bạn có thể thấy, khi bạn sử dụng hàm hoặc phương thức đặc biệt tương ứng của nó, bạn sẽ nhận được kết quả tương tự. Trên thực tế, khi bạn có được danh sách các thuộc tính và phương thức của một strđối tượng bằng cách sử dụng dir[], bạn sẽ thấy các phương thức đặc biệt này trong danh sách ngoài các phương thức thông thường có sẵn trên strcác đối tượng:

>>>

>>> dir[a]
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 ...,
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 ...,
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']

Nếu hành vi của một hàm hoặc toán tử dựng sẵn không được định nghĩa trong lớp bằng phương thức đặc biệt, thì bạn sẽ nhận được một TypeError.

Vì vậy, làm thế nào bạn có thể sử dụng các phương thức đặc biệt trong các lớp học của bạn ?

Quá tải các chức năng tích hợp

Nhiều người trong số những phương pháp đặc biệt được định nghĩa trong mô hình dữ liệu có thể được sử dụng để thay đổi hành vi của các chức năng như lenabshashdivmod, và vân vân. Để làm điều này, bạn chỉ cần xác định phương thức đặc biệt tương ứng trong lớp của bạn. Hãy xem một vài ví dụ:

Tạo chiều dài cho các đối tượng của bạn bằng cách sử dụng len[]

Để thay đổi hành vi của len[], bạn cần xác định __len__[]phương thức đặc biệt trong lớp của mình. Bất cứ khi nào bạn chuyển một đối tượng của lớp mình tới len[], định nghĩa tùy chỉnh của bạn về __len__[]sẽ được sử dụng để lấy kết quả. Hãy triển khai len[]cho lớp thứ tự mà chúng ta đã nói ở phần đầu:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __len__[self]:
...         return len[self.cart]
...
>>> order = Order[['banana', 'apple', 'mango'], 'Real Python']
>>> len[order]
3

Như bạn thấy, bây giờ bạn có thể sử dụng len[]để lấy trực tiếp chiều dài của giỏ hàng. Hơn nữa, nó có ý nghĩa trực quan hơn khi nói "độ dài của đơn hàng" hơn là gọi một cái gì đó như thế order.get_cart_len[]. Cuộc gọi của bạn vừa mang tính chất Pythonic vừa trực quan hơn. Khi bạn không __len__[]xác định phương thức nhưng vẫn gọi len[]đối tượng của mình, bạn sẽ nhận được TypeError:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
>>> order = Order[['banana', 'apple', 'mango'], 'Real Python']
>>> len[order]  # Calling len when no __len__
Traceback [most recent call last]:
  File "", line 1, in 
TypeError: object of type 'Order' has no len[]

Tuy nhiên, khi nạp chồng len[], bạn nên nhớ rằng Python yêu cầu hàm trả về một số nguyên. Nếu phương thức của bạn trả về bất kỳ thứ gì khác ngoài một số nguyên, bạn sẽ nhận được một TypeError. Điều này, có lẽ nhất, là để giữ cho nó phù hợp với thực tế len[]thường được sử dụng để lấy độ dài của một chuỗi, chỉ có thể là một số nguyên:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __len__[self]:
...         return float[len[self.cart]]  # Return type changed to float
...
>>> order = Order[['banana', 'apple', 'mango'], 'Real Python']
>>> len[order]
Traceback [most recent call last]:
  File "", line 1, in 
TypeError: 'float' object cannot be interpreted as an integer

Làm cho các đối tượng của bạn hoạt động với abs[]

Bạn có thể ra lệnh cho hành vi của abs[]cài đặt sẵn cho các trường hợp của lớp của bạn bằng cách xác định __abs__[]phương thức đặc biệt trong lớp. Không có giới hạn nào đối với giá trị trả về của abs[]và bạn nhận được một TypeErrorkhi phương thức đặc biệt không có trong định nghĩa lớp của bạn.

Trong một lớp biểu diễn một vectơ trong không gian hai chiều, abs[]có thể được sử dụng để lấy độ dài của vectơ. Hãy xem nó trong hành động:

>>>

>>> class Vector:
...     def __init__[self, x_comp, y_comp]:
...         self.x_comp = x_comp
...         self.y_comp = y_comp
...
...     def __abs__[self]:
...         return [self.x_comp ** 2 + self.y_comp ** 2] ** 0.5
...
>>> vector = Vector[3, 4]
>>> abs[vector]
5.0

Sẽ có ý nghĩa trực quan hơn khi nói "giá trị tuyệt đối của vectơ" hơn là gọi một cái gì đó như thế vector.get_mag[].

In các đối tượng của bạn trước khi sử dụng str[]

Các str[]built-in được sử dụng để đúc một thể hiện của một lớp học để một strđối tượng, hoặc một cách thích hợp hơn, để có được một người dùng thân thiện chuỗi đại diện của đối tượng có thể được đọc bởi một người dùng bình thường chứ không phải là lập trình viên. Bạn có thể xác định định dạng chuỗi mà đối tượng của bạn sẽ được hiển thị khi được chuyển đến str[]bằng cách xác định __str__[]phương thức trong lớp của bạn. Hơn nữa, __str__[]là phương thức được Python sử dụng khi bạn gọi print[]đối tượng của mình.

Hãy triển khai điều này trong Vectorlớp để định dạng Vectorcác đối tượng như xi+yj. Thành phần y phủ định sẽ được xử lý bằng cách sử dụng ngôn ngữ nhỏ có định dạng :

>>>

>>> class Vector:
...     def __init__[self, x_comp, y_comp]:
...         self.x_comp = x_comp
...         self.y_comp = y_comp
...
...     def __str__[self]:
...         # By default, sign of +ve number is not displayed
...         # Using `+`, sign is always displayed
...         return f'{self.x_comp}i{self.y_comp:+}j'
...
>>> vector = Vector[3, 4]
>>> str[vector]
'3i+4j'
>>> print[vector]
3i+4j

Nó là cần thiết để __str__[]trả về một strđối tượng, và chúng tôi nhận được một TypeErrornếu kiểu trả về không phải là chuỗi.

Đại diện cho các đối tượng của bạn bằng cách sử dụng repr[]

Các repr[]built-in được sử dụng để có được những đại diện chuỗi được phân tách của một đối tượng. Nếu một đối tượng có thể phân tích cú pháp, điều đó có nghĩa là Python sẽ có thể tạo lại đối tượng từ biểu diễn khi reprđược sử dụng kết hợp với các hàm như eval[]. Để xác định hành vi của repr[], bạn có thể sử dụng __repr__[]phương pháp đặc biệt.

Đây cũng là phương thức Python sử dụng để hiển thị đối tượng trong phiên REPL. Nếu __repr__[]phương thức không được xác định, bạn sẽ nhận được một cái gì đó giống như cố gắng xem xét đối tượng trong phiên REPL. Hãy xem nó hoạt động trong Vectorlớp học:

>>>

>>> class Vector:
...     def __init__[self, x_comp, y_comp]:
...         self.x_comp = x_comp
...         self.y_comp = y_comp
...
...     def __repr__[self]:
...         return f'Vector[{self.x_comp}, {self.y_comp}]'
...

>>> vector = Vector[3, 4]
>>> repr[vector]
'Vector[3, 4]'

>>> b = eval[repr[vector]]
>>> type[b], b.x_comp, b.y_comp
[__main__.Vector, 3, 4]

>>> vector  # Looking at object; __repr__ used
'Vector[3, 4]'

Lưu ý: Trong trường hợp __str__[]phương thức không được xác định, Python sử dụng __repr__[]phương thức để in đối tượng, cũng như để biểu diễn đối tượng khi str[]được gọi trên nó. Nếu thiếu cả hai phương thức, nó sẽ mặc định là . Nhưng __repr__[]là phương pháp duy nhất được sử dụng để hiển thị đối tượng trong một phiên tương tác. Sự vắng mặt của nó trong lớp dẫn đến kết quả .

Ngoài ra, trong khi sự phân biệt giữa __str__[]và __repr__[]là hành vi được khuyến nghị, nhiều thư viện phổ biến bỏ qua sự phân biệt này và sử dụng hai phương pháp thay thế cho nhau.

Đây là một bài viết được đề xuất về __repr__[]và __str__[]bởi Dan Bader rất riêng của chúng tôi: Chuyển đổi chuỗi Python 101: Tại sao mọi lớp cần một “repr” .

Làm cho các đối tượng của bạn là trung thực hoặc sai bằng cách sử dụng bool[]

Các bool[]built-in có thể được sử dụng để có được những giá trị thật của một đối tượng. Để xác định hành vi của nó, bạn có thể sử dụng phương thức đặc biệt __bool__[]__nonzero__[]trong Python 2.x].

Hành vi được định nghĩa ở đây sẽ xác định giá trị chân lý của một cá thể trong tất cả các ngữ cảnh yêu cầu thu được giá trị chân lý như trong các ifcâu lệnh.

Ví dụ, đối với Orderlớp đã được định nghĩa ở trên, một thể hiện có thể được coi là trung thực nếu độ dài của danh sách giỏ hàng khác 0. Điều này có thể được sử dụng để kiểm tra xem một đơn đặt hàng có nên được xử lý hay không:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __bool__[self]:
...         return len[self.cart] > 0
...
>>> order1 = Order[['banana', 'apple', 'mango'], 'Real Python']
>>> order2 = Order[[], 'Python']

>>> bool[order1]
True
>>> bool[order2]
False

>>> for order in [order1, order2]:
...     if order:
...         print[f"{order.customer}'s order is processing..."]
...     else:
...         print[f"Empty order for customer {order.customer}"]
Real Python's order is processing...
Empty order for customer Python

Lưu ý: Khi __bool__[]phương thức đặc biệt không được triển khai trong một lớp, giá trị được trả về __len__[]được sử dụng làm giá trị chân lý, trong đó giá trị khác 0 cho biết Truevà giá trị 0 cho biết False. Trong trường hợp cả hai phương thức không được thực hiện, tất cả các phiên bản của lớp được coi là True.

Có nhiều phương thức đặc biệt hơn làm quá tải các hàm tích hợp sẵn. Bạn có thể tìm thấy chúng trong tài liệu . Sau khi thảo luận về một số trong số chúng, chúng ta hãy chuyển sang các toán tử.

Quá tải các toán tử tích hợp

Thay đổi hành vi của các toán tử cũng đơn giản như thay đổi hành vi của các hàm. Bạn xác định các phương thức đặc biệt tương ứng của chúng trong lớp của bạn và các toán tử hoạt động theo hành vi được xác định trong các phương thức này.

Các phương pháp này khác với các phương pháp đặc biệt ở trên theo nghĩa là chúng cần phải chấp nhận một đối số khác trong định nghĩa khác với tên selfthường được gọi chung other. Hãy xem một vài ví dụ.

Làm cho các đối tượng của bạn có khả năng được thêm vào bằng cách sử dụng +

Phương thức đặc biệt tương ứng với +toán tử là __add__[]phương thức. Thêm một định nghĩa tùy chỉnh về các __add__[]thay đổi hành vi của toán tử. Bạn nên __add__[]trả về một thể hiện mới của lớp thay vì sửa đổi chính thể hiện đang gọi. Bạn sẽ thấy hành vi này khá phổ biến trong Python:

>>>

>>> a = 'Real'
>>> a + 'Python'  # Gives new str instance
'RealPython'
>>> a  # Values unchanged
'Real'
>>> a = a + 'Python'  # Creates new instance and assigns a to it
>>> a
'RealPython'

Bạn có thể thấy ở trên rằng việc sử dụng +toán tử trên một strđối tượng thực sự trả về một thể hiện mới str, giữ cho giá trị của thể hiện đang gọi [ a] không bị sửa đổi. Để thay đổi nó, chúng ta cần chỉ định rõ ràng phiên bản mới cho a.

Hãy triển khai khả năng thêm các mặt hàng mới vào giỏ hàng của chúng ta trong Orderlớp bằng toán tử. Chúng tôi sẽ thực hiện theo phương pháp được khuyến nghị và làm cho toán tử trả về một phiên bản mới Ordercó các thay đổi theo yêu cầu của chúng tôi thay vì thực hiện các thay đổi trực tiếp cho phiên bản của chúng tôi:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __add__[self, other]:
...         new_cart = self.cart.copy[]
...         new_cart.append[other]
...         return Order[new_cart, self.customer]
...
>>> order = Order[['banana', 'apple'], 'Real Python']

>>> [order + 'orange'].cart  # New Order instance
['banana', 'apple', 'orange']
>>> order.cart  # Original instance unchanged
['banana', 'apple']

>>> order = order + 'mango'  # Changing the original instance
>>> order.cart
['banana', 'apple', 'mango']

Tương tự, bạn có __sub__[]__mul__[]và các phương pháp đặc biệt khác mà xác định hành vi của -*và vân vân. Các phương thức này cũng sẽ trả về một thể hiện mới của lớp.

Phím tắt: +=Nhà điều hành

Các +=nhà điều hành đứng như một phím tắt để biểu thức obj1 = obj1 + obj2. Phương pháp đặc biệt tương ứng với nó là __iadd__[]. Các __iadd__[]phương pháp nên thực hiện thay đổi trực tiếp với các selfđối số và trả về kết quả, mà có thể hoặc không thể được self. Hành vi này hoàn toàn khác với hành vi __add__[]sau này tạo một đối tượng mới và trả về đối tượng đó, như bạn đã thấy ở trên.

Đại khái, bất kỳ +=việc sử dụng nào trên hai đối tượng đều tương đương với điều này:

>>> result = obj1 + obj2
>>> obj1 = result

Đây, resultlà giá trị được trả về bởi __iadd__[]. Phép gán thứ hai được Python thực hiện tự động, có nghĩa là bạn không cần phải gán một cách rõ ràng cho obj1kết quả như trong trường hợp của obj1 = obj1 + obj2.

Hãy làm cho điều này khả thi cho Orderlớp học để các mặt hàng mới có thể được thêm vào giỏ hàng bằng cách sử dụng +=:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __iadd__[self, other]:
...         self.cart.append[other]
...         return self
...
>>> order = Order[['banana', 'apple'], 'Real Python']
>>> order += 'mango'
>>> order.cart
['banana', 'apple', 'mango']

Có thể thấy, bất kỳ thay đổi nào được thực hiện trực tiếp selfvà sau đó nó sẽ được trả lại. Điều gì xảy ra khi bạn trả về một số giá trị ngẫu nhiên, như một chuỗi hoặc một số nguyên?

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __iadd__[self, other]:
...         self.cart.append[other]
...         return 'Hey, I am string!'
...
>>> order = Order[['banana', 'apple'], 'Real Python']
>>> order += 'mango'
>>> order
'Hey, I am string!'

Mặc dù mặt hàng có liên quan đã được thêm vào giỏ hàng, giá trị của mặt hàng orderđã thay đổi thành giá trị được trả lại __iadd__[]. Python đã hoàn toàn xử lý bài tập cho bạn. Điều này có thể dẫn đến hành vi đáng ngạc nhiên nếu bạn quên trả lại một cái gì đó trong quá trình triển khai của mình:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __iadd__[self, other]:
...         self.cart.append[other]
...
>>> order = Order[['banana', 'apple'], 'Real Python']
>>> order += 'mango'
>>> order  # No output
>>> type[order]
NoneType

Vì tất cả các hàm [hoặc phương thức] Python trả về Nonemột cách ngầm định, orderđược gán lại cho Nonevà phiên REPL không hiển thị bất kỳ đầu ra nào khi orderđược kiểm tra. Nhìn vào loại order, bạn thấy rằng nó là bây giờ NoneType. Do đó, hãy luôn đảm bảo rằng bạn đang trả lại thứ gì đó trong quá trình triển khai của mình __iadd__[]và đó là kết quả của hoạt động chứ không phải bất kỳ thứ gì khác.

Tương tự như __iadd__[], bạn có __isub__[]__imul__[]__idiv__[]và các phương pháp đặc biệt khác mà xác định hành vi của -=*=/=, và những người khác như nhau.

Lưu ý: Khi __iadd__[]hoặc bạn bè của nó bị thiếu trong định nghĩa lớp của bạn nhưng bạn vẫn sử dụng toán tử của họ trên các đối tượng của mình, Python sẽ sử dụng __add__[]và bạn bè của nó để lấy kết quả của hoạt động và gán nó cho phiên bản gọi. Nói chung, sẽ an toàn khi không thực hiện __iadd__[]và bạn bè của nó trong lớp của bạn miễn là __add__[]và những người bạn của nó hoạt động bình thường [trả lại thứ gì đó là kết quả của hoạt động].

Tài liệu Python có giải thích tốt về các phương pháp này. Ngoài ra, hãy xem này ví dụ đó cho thấy những hãy cẩn thận liên quan với +=và những người khác khi làm việc với các loại không thay đổi.

Lập chỉ mục và cắt các đối tượng của bạn bằng cách sử dụng []

Các []nhà điều hành được gọi là toán tử chỉ mục và được sử dụng trong bối cảnh khác nhau trong Python chẳng hạn như nhận giá trị tại một chỉ số trong chuỗi, nhận được giá trị gắn liền với một chìa khóa trong từ điển, hoặc lấy một phần của một chuỗi thông qua cắt. Bạn có thể thay đổi hành vi của nó bằng __getitem__[]phương pháp đặc biệt.

Hãy cấu hình Orderlớp của chúng ta để chúng ta có thể sử dụng trực tiếp đối tượng và lấy một mặt hàng từ giỏ hàng:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __getitem__[self, key]:
...         return self.cart[key]
...
>>> order = Order[['banana', 'apple'], 'Real Python']
>>> order[0]
'banana'
>>> order[-1]
'apple'

Bạn sẽ nhận thấy rằng ở trên, tên của đối số __getitem__[]không phải là indexbut key. Điều này là do đối số có thể có ba dạng chủ yếu: giá trị số nguyên , trong trường hợp đó là chỉ mục hoặc khóa từ điển, giá trị chuỗi , trong trường hợp đó là khóa từ điển và đối tượng lát cắt , trong trường hợp đó nó sẽ cắt chuỗi được sử dụng bởi lớp. Trong khi có những khả năng khác, đây là những khả năng thường gặp nhất.

Vì cấu trúc dữ liệu bên trong của chúng ta là một danh sách, chúng ta có thể sử dụng []toán tử để cắt danh sách, như trong trường hợp này, keyđối số sẽ là một đối tượng lát cắt. Đây là một trong những lợi thế lớn nhất của việc có một __getitem__[]định nghĩa trong lớp của bạn. Miễn là bạn đang sử dụng cấu trúc dữ liệu hỗ trợ cắt [danh sách, bộ giá trị, chuỗi, v.v.], bạn có thể định cấu hình các đối tượng của mình để cắt trực tiếp cấu trúc:

>>>

>>> order[1:]
['apple']
>>> order[::-1]
['apple', 'banana']

Lưu ý: Có một __setitem__[]phương pháp đặc biệt tương tự được sử dụng để xác định hành vi của obj[x] = y. Phương thức này có hai đối số ngoài self, thường được gọi keyvà value, và có thể được sử dụng để thay đổi giá trị tại keythành value.

Toán tử đảo ngược: Làm cho lớp học của bạn chính xác về mặt toán học

Trong khi định __add__[]__sub__[]__mul__[], và tương tự như phương pháp đặc biệt cho phép bạn sử dụng các nhà khai thác khi dụ lớp học của bạn là bên trái toán hạng, các nhà điều hành sẽ không làm việc nếu cá thể lớp là phía bên phải toán hạng:

>>>

>>> class Mock:
...     def __init__[self, num]:
...         self.num = num
...     def __add__[self, other]:
...         return Mock[self.num + other]
...
>>> mock = Mock[5]
>>> mock = mock + 6
>>> mock.num
11

>>> mock = 6 + Mock[5]
Traceback [most recent call last]:
  File "", line 1, in 
TypeError: unsupported operand type[s] for +: 'int' and 'Mock'

Nếu lớp của bạn đại diện cho một thực thể toán học như vectơ, tọa độ hoặc số phức , thì việc áp dụng các toán tử sẽ hoạt động trong cả hai trường hợp vì nó là một phép toán hợp lệ.

Hơn nữa, nếu các toán tử chỉ hoạt động khi thể hiện là toán hạng bên trái, chúng ta đang vi phạm nguyên tắc cơ bản về tính giao hoán trong nhiều trường hợp. Vì vậy, để giúp bạn thực hiện các lớp học của bạn về mặt toán học chính xác, Python cung cấp cho bạn với các phương pháp đặc biệt ngược như __radd__[]__rsub__[]__rmul__[], và vân vân.

Các lệnh gọi này xử lý như x + objx - objvà x * obj, đâu xkhông phải là một thể hiện của lớp liên quan. Cũng giống như __add__[]và các phương thức khác, các phương thức đặc biệt đảo ngược này sẽ trả về một thể hiện mới của lớp với các thay đổi của hoạt động thay vì sửa đổi chính thể hiện đang gọi.

Hãy cấu hình __radd__[]trong Orderlớp theo cách mà nó sẽ thêm một thứ gì đó ở phía trước giỏ hàng. Điều này có thể được sử dụng trong trường hợp giỏ hàng được sắp xếp theo mức độ ưu tiên của các đơn đặt hàng:

>>>

>>> class Order:
...     def __init__[self, cart, customer]:
...         self.cart = list[cart]
...         self.customer = customer
...
...     def __add__[self, other]:
...         new_cart = self.cart.copy[]
...         new_cart.append[other]
...         return Order[new_cart, self.customer]
...
...     def __radd__[self, other]:
...         new_cart = self.cart.copy[]
...         new_cart.insert[0, other]
...         return Order[new_cart, self.customer]
...
>>> order = Order[['banana', 'apple'], 'Real Python']

>>> order = order + 'orange'
>>> order.cart
['banana', 'apple', 'orange']

>>> order = 'mango' + order
>>> order.cart
['mango', 'banana', 'apple', 'orange']

Một ví dụ hoàn chỉnh

Để chuyển tất cả những điểm này về nhà, tốt hơn là bạn nên xem một lớp ví dụ thực thi các toán tử này cùng nhau.

Hãy phát minh lại bánh xe và triển khai lớp của riêng chúng ta để biểu diễn các số phức , CustomComplex. Các đối tượng trong lớp của chúng ta sẽ hỗ trợ nhiều hàm và toán tử tích hợp sẵn, làm cho chúng hoạt động rất giống với lớp số phức được tích hợp sẵn:

from math import hypot, atan, sin, cos

class CustomComplex:
    def __init__[self, real, imag]:
        self.real = real
        self.imag = imag

Hàm tạo chỉ xử lý một loại lệnh gọi CustomComplex[a, b],. Nó có các đối số vị trí, đại diện cho phần thực và phần ảo của số phức.

Hãy xác định hai phương thức bên trong lớp, conjugate[]và phương thức argz[]này sẽ cung cấp cho chúng ta liên hợp phức và đối số của một số phức tương ứng:

def conjugate[self]:
    return self.__class__[self.real, -self.imag]

def argz[self]:
    return atan[self.imag / self.real]

Lưu ý: __class__ không phải là một phương thức đặc biệt mà là một thuộc tính lớp được hiển thị theo mặc định. Nó có một tham chiếu đến lớp. Bằng cách sử dụng nó ở đây, chúng ta đang có được điều đó và sau đó gọi hàm tạo theo cách thông thường. Nói cách khác, điều này tương đương với CustomComplex[real, imag]. Điều này được thực hiện ở đây để tránh cấu trúc lại mã nếu tên của lớp thay đổi vào một ngày nào đó.

Tiếp theo, chúng tôi cấu hình abs[]để trả về mô-đun của một số phức:

def __abs__[self]:
    return hypot[self.real, self.imag]

Chúng tôi sẽ tuân theo sự phân biệt được khuyến nghị giữa __repr__[]và __str__[]và sử dụng thứ nhất cho biểu diễn chuỗi có thể phân tích cú pháp và thứ hai cho biểu diễn “đẹp”.

Các __repr__[]phương pháp đơn giản sẽ trở lại CustomComplex[a, b]trong một chuỗi để chúng ta có thể gọi eval[]để tái tạo các đối tượng, trong khi __str__[]phương pháp này sẽ trả về số phức tạp trong ngoặc đơn, như [a+bj]:

def __repr__[self]:
    return f"{self.__class__.__name__}[{self.real}, {self.imag}]"

def __str__[self]:
    return f"[{self.real}{self.imag:+}j]"

Về mặt toán học, có thể cộng hai số phức bất kỳ hoặc cộng một số thực vào một số phức. Hãy cấu hình +toán tử theo cách mà nó hoạt động cho cả hai trường hợp.

Phương thức này sẽ kiểm tra loại toán tử bên tay phải. Trong trường hợp nó là một inthoặc một float, nó sẽ chỉ tăng phần thực [vì bất kỳ số thực nào a, tương đương với a+0j], trong khi trong trường hợp là một số phức khác, nó sẽ thay đổi cả hai phần:

def __add__[self, other]:
    if isinstance[other, float] or isinstance[other, int]:
        real_part = self.real + other
        imag_part = self.imag

    if isinstance[other, CustomComplex]:
        real_part = self.real + other.real
        imag_part = self.imag + other.imag

    return self.__class__[real_part, imag_part]

Tương tự, chúng tôi xác định hành vi cho -và *:

def __sub__[self, other]:
    if isinstance[other, float] or isinstance[other, int]:
        real_part = self.real - other
        imag_part = self.imag

    if isinstance[other, CustomComplex]:
        real_part = self.real - other.real
        imag_part = self.imag - other.imag

    return self.__class__[real_part, imag_part]

def __mul__[self, other]:
    if isinstance[other, int] or isinstance[other, float]:
        real_part = self.real * other
        imag_part = self.imag * other

    if isinstance[other, CustomComplex]:
        real_part = [self.real * other.real] - [self.imag * other.imag]
        imag_part = [self.real * other.imag] + [self.imag * other.real]

    return self.__class__[real_part, imag_part]

Vì cả phép cộng và phép nhân đều có tính chất giao hoán, chúng ta có thể xác định các toán tử đảo ngược của chúng bằng cách gọi __add__[]và __mul__[]trong __radd__[]và __rmul__[]tương ứng. Mặt khác, hành vi của __rsub__[]cần được xác định vì phép trừ không có tính chất giao hoán:

def __radd__[self, other]:
    return self.__add__[other]

def __rmul__[self, other]:
    return self.__mul__[other]

def __rsub__[self, other]:
    # x - y != y - x
    if isinstance[other, float] or isinstance[other, int]:
        real_part = other - self.real
        imag_part = -self.imag

    return self.__class__[real_part, imag_part]

Lưu ý: Bạn có thể nhận thấy rằng chúng tôi đã không thêm một cấu trúc để xử lý một CustomComplexphiên bản ở đây. Điều này là do, trong trường hợp như vậy, cả hai toán hạng đều là thể hiện của lớp chúng ta và __rsub__[]sẽ không chịu trách nhiệm xử lý hoạt động. Thay vào đó, __sub__[]sẽ được gọi. Đây là một chi tiết tinh tế nhưng quan trọng.

Bây giờ, chúng tôi chăm sóc hai nhà khai thác, ==và !=. Các phương pháp đặc biệt được sử dụng cho chúng lần lượt là __eq__[]và __ne__[]. Hai số phức được cho là bằng nhau nếu phần thực và phần ảo tương ứng của chúng bằng nhau. Chúng được cho là không bình đẳng khi một trong hai điều này không bình đẳng:

def __eq__[self, other]:
    # Note: generally, floats should not be compared directly
    # due to floating-point precision
    return [self.real == other.real] and [self.imag == other.imag]

def __ne__[self, other]:
    return [self.real != other.real] or [self.imag != other.imag]

Lưu ý: Hướng dẫn Dấu phẩy động là một bài viết nói về việc so sánh các dấu phẩy động và độ chính xác của dấu phẩy động. Nó làm nổi bật những điều cần lưu ý liên quan đến việc so sánh các phao một cách trực tiếp, đó là điều mà chúng tôi đang làm ở đây.

Cũng có thể nâng một số phức lên bất kỳ lũy thừa nào bằng công thức đơn giản . Chúng tôi định cấu hình hành vi cho cả toán tử cài sẵn pow[]và **toán tử bằng __pow__[]phương pháp đặc biệt:

def __pow__[self, other]:
    r_raised = abs[self] ** other
    argz_multiplied = self.argz[] * other

    real_part = round[r_raised * cos[argz_multiplied]]
    imag_part = round[r_raised * sin[argz_multiplied]]

    return self.__class__[real_part, imag_part]

Lưu ý: Hãy xem kỹ định nghĩa của phương thức. Chúng tôi đang gọi abs[]để lấy môđun của số phức. Vì vậy, khi bạn đã xác định phương thức đặc biệt cho một hàm hoặc toán tử cụ thể trong lớp của mình, nó có thể được sử dụng trong các phương thức khác của cùng lớp.

Hãy tạo hai trường hợp của lớp này, một trường hợp có phần ảo dương và một có phần ảo âm:

>>>

>>> a = CustomComplex[1, 2]
>>> b = CustomComplex[3, -4]

Biểu diễn chuỗi:

>>>

>>> a
CustomComplex[1, 2]
>>> b
CustomComplex[3, -4]
>>> print[a]
[1+2j]
>>> print[b]
[3-4j]

Tái tạo các đối tượng sử dụng eval[]với repr[]:

>>>

>>> b_copy = eval[repr[b]]
>>> type[b_copy], b_copy.real, b_copy.imag
[__main__.CustomComplex, 3, -4]

Cộng, trừ và nhân:

>>>

>>> a + b
CustomComplex[4, -2]
>>> a - b
CustomComplex[-2, 6]
>>> a + 5
CustomComplex[6, 2]
>>> 3 - a
CustomComplex[2, -2]
>>> a * 6
CustomComplex[6, 12]
>>> a * [-6]
CustomComplex[-6, -12]

Kiểm tra bình đẳng và bất bình đẳng:

>>>

>>> a == CustomComplex[1, 2]
True
>>> a ==  b
False
>>> a != b
True
>>> a != CustomComplex[1, 2]
False

Cuối cùng, nâng một số phức lên một số lũy thừa:

>>>

>>> a ** 2
CustomComplex[-3, 4]
>>> b ** 5
CustomComplex[-237, 3116]

Như bạn có thể thấy, các đối tượng của lớp tùy chỉnh của chúng tôi hoạt động và trông giống như các đối tượng của một lớp tích hợp và rất Pythonic. Mã ví dụ đầy đủ cho lớp này được nhúng bên dưới.

from math import hypot, atan, sin, cos

class CustomComplex[]:
    """
    A class to represent a complex number, a+bj.
    Attributes:
        real - int, representing the real part
        imag - int, representing the imaginary part

    Implements the following:

    * Addition with a complex number or a real number using `+`
    * Multiplication with a complex number or a real number using `*`
    * Subtraction of a complex number or a real number using `-`
    * Calculation of absolute value using `abs`
    * Raise complex number to a power using `**`
    * Nice string representation using `__repr__`
    * Nice user-end viewing using `__str__`

    Notes:
        * The constructor has been intentionally kept simple
        * It is configured to support one kind of call:
            CustomComplex[a, b]
        * Error handling was avoided to keep things simple
    """

    def __init__[self, real, imag]:
        """
        Initializes a complex number, setting real and imag part
        Arguments:
            real: Number, real part of the complex number
            imag: Number, imaginary part of the complex number
        """
        self.real = real
        self.imag = imag

    def conjugate[self]:
        """
        Returns the complex conjugate of a complex number
        Return:
            CustomComplex instance
        """
        return CustomComplex[self.real, -self.imag]

    def argz[self]:
        """
        Returns the argument of a complex number
        The argument is given by:
            atan[imag_part/real_part]
        Return:
            float
        """
        return atan[self.imag / self.real]

    def __abs__[self]:
        """
        Returns the modulus of a complex number
        Return:
            float
        """
        return hypot[self.real, self.imag]

    def __repr__[self]:
        """
        Returns str representation of an instance of the 
        class. Can be used with eval[] to get another 
        instance of the class
        Return:
            str
        """
        return f"CustomComplex[{self.real}, {self.imag}]"


    def __str__[self]:
        """
        Returns user-friendly str representation of an instance 
        of the class
        Return:
            str
        """
        return f"[{self.real}{self.imag:+}j]"

    def __add__[self, other]:
        """
        Returns the addition of a complex number with
        int, float or another complex number
        Return:
            CustomComplex instance
        """
        if isinstance[other, float] or isinstance[other, int]:
            real_part = self.real + other
            imag_part = self.imag

        if isinstance[other, CustomComplex]:
            real_part = self.real + other.real
            imag_part = self.imag + other.imag

        return CustomComplex[real_part, imag_part]

    def __sub__[self, other]:
        """
        Returns the subtration from a complex number of
        int, float or another complex number
        Return:
            CustomComplex instance
        """
        if isinstance[other, float] or isinstance[other, int]:
            real_part = self.real - other
            imag_part = self.imag

        if isinstance[other, CustomComplex]:
            real_part = self.real - other.real
            imag_part = self.imag - other.imag

        return CustomComplex[real_part, imag_part]

    def __mul__[self, other]:
        """
        Returns the multiplication of a complex number with
        int, float or another complex number
        Return:
            CustomComplex instance
        """
        if isinstance[other, int] or isinstance[other, float]:
            real_part = self.real * other
            imag_part = self.imag * other

        if isinstance[other, CustomComplex]:
            real_part = [self.real * other.real] - [self.imag * other.imag]
            imag_part = [self.real * other.imag] + [self.imag * other.real]

        return CustomComplex[real_part, imag_part]

    def __radd__[self, other]:
        """
        Same as __add__; allows 1 + CustomComplex['x+yj']
        x + y == y + x
        """
        pass

    def __rmul__[self, other]:
        """
        Same as __mul__; allows 2 * CustomComplex['x+yj']
        x * y == y * x
        """
        pass

    def __rsub__[self, other]:
        """
        Returns the subtraction of a complex number from
        int or float
        x - y != y - x
        Subtration of another complex number is not handled by __rsub__
        Instead, __sub__ handles it since both sides are instances of
        this class
        Return:
            CustomComplex instance
        """
        if isinstance[other, float] or isinstance[other, int]:
            real_part = other - self.real
            imag_part = -self.imag

        return CustomComplex[real_part, imag_part]

    def __eq__[self, other]:
        """
        Checks equality of two complex numbers
        Two complex numbers are equal when:
            * Their real parts are equal AND
            * Their imaginary parts are equal
        Return:
            bool
        """
        # note: comparing floats directly is not a good idea in general
        # due to floating-point precision
        return [self.real == other.real] and [self.imag == other.imag]

    def __ne__[self, other]:
        """
        Checks inequality of two complex numbers
        Two complex numbers are unequal when:
            * Their real parts are unequal OR
            * Their imaginary parts are unequal
        Return:
            bool
        """
        return [self.real != other.real] or [self.imag != other.imag]

    def __pow__[self, other]:
        """
        Raises a complex number to a power
        Formula:
            z**n = [r**n]*[cos[n*agrz] + sin[n*argz]j], where
            z = complex number
            n = power
            r = absolute value of z
            argz = argument of z
        Return:
            CustomComplex instance
        """
        r_raised = abs[self] ** other
        argz_multiplied = self.argz[] * other

        real_part = round[r_raised * cos[argz_multiplied]]
        imag_part = round[r_raised * sin[argz_multiplied]]

        return CustomComplex[real_part, imag_part]

Tóm tắt và tài nguyên

Trong hướng dẫn này, bạn đã tìm hiểu về Mô hình Dữ liệu Python và cách Mô hình Dữ liệu có thể được sử dụng để xây dựng các lớp Pythonic. Bạn đã học về thay đổi hành vi của built-in chức năng như len[]abs[]str[]bool[], và vân vân. Bạn cũng biết về việc thay đổi hành vi được xây dựng trong các nhà khai thác thích +-***, và vân vân.

Sau khi đọc phần này, bạn có thể tự tin tạo các lớp sử dụng các tính năng thành ngữ tốt nhất của Python và làm cho các đối tượng của bạn trở thành Pythonic!

Để biết thêm thông tin về Mô hình Dữ liệu, cũng như nạp chồng hàm và toán tử, hãy xem các tài nguyên sau:

  • Phần 3.3, Tên phương pháp đặc biệt của phần Mô hình dữ liệu trong tài liệu Python
  • Fluent Python của Luciano Ramalho
  • Thủ thuật Python: Cuốn sách
Các khóa học miễn phí qua video:
Lập trình C Java C# SQL Server PHP HTML5-CSS3-JavaScript
« Prev: Python: Tôi có thể làm gì với Python?
» Next: Python: Cách in có định dạng trong Python với bài toán in các thừa số nguyên tố

Copied !!!

Chủ Đề