Làm thế nào để triển khai các phương thức ảo trong Python?


88

Tôi biết các phương pháp ảo từ PHP hoặc Java.

Làm thế nào chúng có thể được triển khai bằng Python?

Hay tôi phải xác định một phương thức trống trong một lớp trừu tượng và ghi đè nó?

Câu trả lời:


104

Chắc chắn, và bạn thậm chí không phải xác định một phương thức trong lớp cơ sở. Trong Python, các phương thức tốt hơn ảo - chúng hoàn toàn động, vì cách gõ trong Python là kiểu gõ vịt .

class Dog:
  def say(self):
    print "hau"

class Cat:
  def say(self):
    print "meow"

pet = Dog()
pet.say() # prints "hau"
another_pet = Cat()
another_pet.say() # prints "meow"

my_pets = [pet, another_pet]
for a_pet in my_pets:
  a_pet.say()

CatDogtrong Python thậm chí không phải bắt nguồn từ một lớp cơ sở chung để cho phép hành vi này - bạn có được nó miễn phí. Điều đó nói rằng, một số lập trình viên thích xác định cấu trúc phân cấp lớp của họ theo cách cứng nhắc hơn để ghi lại nó tốt hơn và áp đặt một số nghiêm ngặt khi nhập. Điều này cũng có thể - xem ví dụ như abcmô-đun chuẩn .


32
+1 để làm ví dụ. Con chó nói "hau" bằng ngôn ngữ nào?
JeremyP

4
@JeremyP: hmm, điểm tốt :-) Tôi đoán trong các ngôn ngữ mà "h" được hiểu là tạo ra âm thanh giống như chữ cái đầu tiên của "hippy" hoặc của "Javier" trong tiếng Tây Ban Nha.
Eli Bendersky

4
@Eli: Xin lỗi, nhưng tôi thực sự quan tâm đến câu trả lời cho câu hỏi. Trong tiếng Anh, họ nói "gâu gâu", họ không nói nhưng đó là từ mà chúng tôi sử dụng tương tự như "meo" cho mèo và "moo" cho bò. Vậy "hau" có phải là tiếng Tây Ban Nha không?
JeremyP

9
@JeremyP Tôi biết chắc chắn rằng những gì của con chó Ba Lan nói;)
j_kubik

2
@JeremyP vâng Tôi xác nhận chó nói "Jau" bằng tiếng Tây Ban Nha và khi viết bằng tiếng Anh sẽ là "Hau" :) hth
SkyWalker

68

raise NotImplementedError()

Đây là trường hợp ngoại lệ được khuyến nghị nêu ra trên "các phương thức ảo thuần túy" của các lớp cơ sở "trừu tượng" không triển khai một phương thức.

https://docs.python.org/3.5/library/exceptions.html#NotImplementedError nói:

Ngoại lệ này có nguồn gốc từ RuntimeError. Trong các lớp cơ sở do người dùng định nghĩa, các phương thức trừu tượng sẽ nêu ra ngoại lệ này khi chúng yêu cầu các lớp dẫn xuất ghi đè phương thức.

Như những người khác đã nói, đây chủ yếu là một quy ước tài liệu và không bắt buộc, nhưng bằng cách này, bạn sẽ nhận được một ngoại lệ có ý nghĩa hơn là một lỗi thuộc tính bị thiếu.

Ví dụ:

class Base(object):
    def virtualMethod(self):
        raise NotImplementedError()
    def usesVirtualMethod(self):
        return self.virtualMethod() + 1

class Derived(Base):
    def virtualMethod(self):
        return 1

print Derived().usesVirtualMethod()
Base().usesVirtualMethod()

cho:

2
Traceback (most recent call last):
  File "./a.py", line 13, in <module>
    Base().usesVirtualMethod()
  File "./a.py", line 6, in usesVirtualMethod
    return self.virtualMethod() + 1
  File "./a.py", line 4, in virtualMethod
    raise NotImplementedError()
NotImplementedError

Liên quan: Có thể tạo các lớp trừu tượng trong Python không?


53

Các phương thức Python luôn ảo.


Ngoại trừ các phương pháp dunder.
Konstantin

Câu trả lời này không thực sự giúp ích cho mục tiêu triển khai các lớp giao diện, đây là một trong những lý do chính cho việc sử dụng các phương thức ảo.
Jean-Marc Volle

21

Trên thực tế, trong phiên bản 2.6, python cung cấp một thứ gọi là lớp cơ sở trừu tượng và bạn có thể đặt các phương thức ảo một cách rõ ràng như sau:

from abc import ABCMeta
from abc import abstractmethod
...
class C:
    __metaclass__ = ABCMeta
    @abstractmethod
    def my_abstract_method(self, ...):

Nó hoạt động rất tốt, miễn là lớp đó không kế thừa từ các lớp đã sử dụng kính đo.

nguồn: http://docs.python.org/2/library/abc.html


Có tương đương với python 3 cho chỉ thị này không?
locke

9

Các phương thức Python luôn ảo

như Ignacio đã nói Bằng cách nào đó, kế thừa lớp có thể là một cách tiếp cận tốt hơn để thực hiện những gì bạn muốn.

class Animal:
    def __init__(self,name,legs):
        self.name = name
        self.legs = legs

    def getLegs(self):
        return "{0} has {1} legs".format(self.name, self.legs)

    def says(self):
        return "I am an unknown animal"

class Dog(Animal): # <Dog inherits from Animal here (all methods as well)

    def says(self): # <Called instead of Animal says method
        return "I am a dog named {0}".format(self.name)

    def somethingOnlyADogCanDo(self):
        return "be loyal"

formless = Animal("Animal", 0)
rover = Dog("Rover", 4) #<calls initialization method from animal

print(formless.says()) # <calls animal say method

print(rover.says()) #<calls Dog says method
print(rover.getLegs()) #<calls getLegs method from animal class

Kết quả phải là:

I am an unknown animal
I am a dog named Rover
Rover has 4 legs

4

Một cái gì đó giống như một phương thức ảo trong C ++ (gọi thực thi phương thức của một lớp dẫn xuất thông qua một tham chiếu hoặc con trỏ đến lớp cơ sở) không có ý nghĩa trong Python, vì Python không có tính năng nhập. (Tôi không biết các phương thức ảo hoạt động như thế nào trong Java và PHP.)

Nhưng nếu bằng "ảo", bạn có nghĩa là gọi triển khai dưới cùng trong hệ thống phân cấp kế thừa, thì đó là những gì bạn luôn nhận được trong Python, như một số câu trả lời đã chỉ ra.

Chà, hầu như luôn luôn ...

Như dplamp đã chỉ ra, không phải tất cả các phương thức trong Python đều hoạt động như vậy. Phương pháp Dunder không. Và tôi nghĩ đó là một tính năng không quá nổi tiếng.

Hãy xem xét ví dụ nhân tạo này

class A:
    def prop_a(self):
        return 1
    def prop_b(self):
        return 10 * self.prop_a()

class B(A):
    def prop_a(self):
        return 2

Hiện nay

>>> B().prop_b()
20
>>> A().prob_b()
10

Tuy nhiên, hãy xem xét điều này

class A:
    def __prop_a(self):
        return 1
    def prop_b(self):
        return 10 * self.__prop_a()

class B(A):
    def __prop_a(self):
        return 2

Hiện nay

>>> B().prop_b()
10
>>> A().prob_b()
10

Điều duy nhất chúng tôi đã thay đổi là tạo ra prop_a()một phương pháp tốt hơn.

Một vấn đề với hành vi đầu tiên có thể là bạn không thể thay đổi hành vi của prop_a()trong lớp dẫn xuất mà không ảnh hưởng đến hành vi của prop_b(). Bài nói rất hay này của Raymond Hettinger đưa ra một ví dụ cho một trường hợp sử dụng mà điều này là bất tiện.

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.