Reflection đề cập đến việc code có khả năng kiểm tra các thuộc tính, của những đối tượng mà có thể được truyền vào làm tham số cho một hàm. Ví dụ, nếu chúng ta viết type(obj) thì Python sẽ trả về một đối tượng mà đại diện cho kiểu dữ liệu của obj.

Bằng cách sử dụng reflection, chúng ta có thể viết được một hàm đệ quy dùng để đảo ngược các strings, lists, hay bất kỳ kiểu dữ liệu dạng danh sách có hỗ trợ cắt và nối phần tử nào khác. Nếu một obj là một tham chiếu đến một string thì Python sẽ trả về đối tượng thuộc kiểu str. Hơn nữa, nếu chúng ta viết hàm str(), chúng ta sẽ nhận được một string là chuỗi rỗng (empty string). Nói cách khác, việc viết hàm str() sẽ giống với việc viết “”. Tương tự, việc viết hàm list() là giống với việc viết [].

Dưới đây là đoạn chương trình Python mô tả cách sử dụng reflection:

# -----------------------------------------------------------
#Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
#@author cafedevn
#Contact: cafedevn@gmail.com
#Fanpage: https://www.facebook.com/cafedevn
#Group: https://www.facebook.com/groups/cafedev.vn/
#Instagram: https://instagram.com/cafedevn
#Twitter: https://twitter.com/CafedeVn
#Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
#Pinterest: https://www.pinterest.com/cafedevvn/
#YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
# -----------------------------------------------------------

# Python program to illustrate reflection 
def reverse(seq): 
    SeqType = type(seq) 
    emptySeq = SeqType() 
      
    if seq == emptySeq: 
        return emptySeq 
      
    restrev = reverse(seq[1:]) 
    first = seq[0:1] 
      
    # Combine the result 
    result = restrev + first 
      
    return result 
  
# Driver code 
print(reverse([1, 2, 3, 4])) 
print(reverse("HELLO")) 

Kết quả in ra là:

[4,3,2,1]
OLLEH

1. Reflection-enabling functions 

Trong Python, các hàm Reflection-enabling functions bao gồm: type(), isinstance(), callable(), dir(), và getattr().

1.1. Hàm type() và hàm isinstance() trong Python

Ngôn ngữ lập trình Python có tích hợp sẵn một phương thức tên là type(), thường hữu dụng khi muốn tìm ra kiểu dữ liệu của một biết được sử dụng trong chương trình, tại runtime – thời điểm chương trình đang được thực thi.

Nếu một đối số đơn lẻ là đối tượng được truyền vào bên trong hàm type() của Python, thì nó sẽ trả về dữ liệu có kiểu chính là kiểu của đối tượng vừa được cung cấp làm đối số. Nếu có ba đối số được truyền vào hàm type() là name, bases, và dict, thì hàm type() sẽ trả về một kiểu dữ liệu đối tượng mới.

Cú pháp:

type(object)
type(name, bases, dict)

a) Hàm type() với một tham số duy nhất là đối tượng

– Ví dụ:


# Python code type() with a single object parameter 
x = 5
s = "geeksforgeeks"
y = [1,2,3] 
print(type(x)) 
print(type(s)) 
print(type(y)) 

– Kết quả in ra là:

class 'int'
class 'str'
class 'list'

b) Hàm type() với nhiều tham số

– Dưới đây là đoạn chương trình Python trong đó có sử dụng hàm type(), lần này hàm type() sẽ nhận vào 3 tham số là name, bases và dict:

# -----------------------------------------------------------
#Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
#@author cafedevn
#Contact: cafedevn@gmail.com
#Fanpage: https://www.facebook.com/cafedevn
#Group: https://www.facebook.com/groups/cafedev.vn/
#Instagram: https://instagram.com/cafedevn
#Twitter: https://twitter.com/CafedeVn
#Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
#Pinterest: https://www.pinterest.com/cafedevvn/
#YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
# -----------------------------------------------------------

# Python code for type() with a name,  
# bases and dict parameter 
  
o1 = type('X', (object,), dict(a='Foo', b=12)) 
  
print(type(o1)) 
print(vars(o1)) 
  
class test: 
      a = 'Foo'
  b = 12
    
o2 = type('Y', (test,), dict(a='Foo', b=12)) 
print(type(o2)) 
print(vars(o2)) 

– Kết quả in ra là:

{'b': 12, 'a': 'Foo', '__dict__': , '__doc__': None, '__weakref__': }
{'b': 12, 'a': 'Foo', '__doc__': None}

Nếu bạn muốn kiểm tra kiểu dữ liệu của một đối tượng, thay vì sử dụng hàm type(), có lẽ bạn nên sử dụng hàm isinstance() thì tốt hơn. Điều là là do, bên cạnh tính năng tương tự như hàm type(), thì hàm isinstance() còn kiểm tra xem liệu rằng đối tượng được cung cấp có phải là một thể hiện của lớp con (subclass) hay không.

c) Hàm isinstance()

Hàm isinstance() sẽ kiểm tra xem đối tượng object (là đối số đầu tiên) có phải là một thể hiện hoặc là lớp con (subclass) của lớp classinfor (đối số thứ hai) hay không?

Cú pháp:

isinstance(object, classinfo) 

The isinstance() takes two parameters:
object : object to be checked
classinfo : class, type, or tuple of classes and types

Hàm isinstance() sẽ nhận vào hai tham số:

object: đối tượng được kiểm tra

classinfor: Thông tin về class, kiểu dữ liệu, hoặc về tuple của các classes và kiểu dữ liệu.

Giá trị trả về:

Hàm isinstance() sẽ trả về true nếu object là một thể hiện hoặc là lớp con (subclass) của một lớp nào đó, hoặc trả về false nếu object là một phần tử của tuple. Nếu classinfor không phải là một kiểu dữ liệu class nào đó, hoặc không phải là một kiểu tuple, thì ngoại lệ TypeError sẽ xuất hiện.

Ví dụ về việc sử dụng hàm isinstance() trong Python:


# Python code for  isinstance() 
class Test: 
    a = 5
    
TestInstance = Test() 
  
print(isinstance(TestInstance, Test)) 
print(isinstance(TestInstance, (list, tuple))) 
print(isinstance(TestInstance, (list, tuple, Test))) 

Kết quả in ra là:

True
False
True

d) Hàm type() và hàm isinstance()

Có một lỗi cơ bản mà mọi người thường mắc phải, đó là sử dụng hàm type(), trong khi hàm isinstance() sẽ phù hợp hơn khi đó.

– Nếu bạn đang muốn kiểm tra liệu rằng một đối tượng cụ thể nào đó có phải là thuộc kiểu dữ liệu này hay không, tốt hơn là bạn nên sử dụng hàm isinstance() bởi vì nó sẽ kiểm tra liệu rằng đối tượng được truyền vào trong đối số đầu tiên có phải là thuộc kiểu dữ liệu giống với kiểu dữ liệu của đối tượng được truyền vào cho đối số thứ hai hay không. Do đó, nó rất hữu dụng trong trường hợp làm việc với các lớp có quan hệ kế thừa với nhau, và các old-style classes (các lớp theo kiểu cũ, tuy nhiên Python 3 đã không hỗ trợ old-style class nữa), bởi vì tất cả chúng đều có thể hiện đối tượng kiểu kế thừa (legacy type object instance).

– Mặt khác, đối với hàm type(), nó chỉ đơn giản là trả về kiểu dữ liệu của một đối tượng và so sánh kết quả đó với một kiểu đối tượng khác, nếu hai đối tượng được so sánh có cùng kiểu dữ liệu với nhau thì kết quả của phép so sánh sẽ là True. Trong Python, thay vì kiểm tra kiểu dữ liệu của đối tượng, tốt hơn hết bạn nên sử dụng Duck Typing (việc kiểm tra kiểu dữ liệu được hoãn lại tới run-time, và được cài đặt bởi dynamic typing hoặc reflection)

Dưới đây là ví dụ sử dụng duck typing:


#Python code to illustrate duck typing 
  
class User(object): 
    def __init__(self, firstname): 
        self.firstname = firstname 
  
    @property
    def name(self): 
        return self.firstname 
  
class Animal(object): 
    pass
  
class Fox(Animal): 
    name = "Fox"
  
class Bear(Animal): 
    name = "Bear"
  
# Use the .name attribute (or property) regardless of the type 
for a in [User("Cafedev"), Fox(), Bear()]: 
    print(a.name) 

Kết quả in ra là:

Cafedev
Fox
Bear

Lý do tiếp theo để không sử dụng hàm type() chính là sự thiếu sót của nó trong việc không hỗ trợ cho những trường hợp sử dụng có liên quan đến quan hệ kế thừa.

Đoạn chương trình Python dưới đây sẽ mô tả sự thiếu sót trong việc không hỗ trợ những usecase có liên quan đến quan hệ kế thừa của hàm type():


#python code to illustrate the lack of 
#support for inheritance in type() 
  
class MyDict(dict): 
    """A normal dict, that is always created with an "initial" key"""
    def __init__(self): 
        self["initial"] = "some data"
  
d = MyDict() 
print(type(d) == dict) 
print(type(d) == MyDict) 
  
d = dict() 
print(type(d) == dict) 
print(type(d) == MyDict) 

Kết quả in ra là:

False
True
True
False

Lớp MyDict sở hữu tất cả các thuộc tính của một đối tượng dict, và không có bất kỳ phương thức mới nào. Nó sẽ hoạt động giống hệt như kiểu dictionary. Nhưng hàm type() sẽ không trả về kết quả như mong đợi.

Sử dụng hàm isinstance() là thích hợp hơn trong trường hợp này, vì nó sẽ cho ta kết quả như mong đợi:

Đoạn code dưới đây sẽ cho thấy khả năng hỗ trợ quan hệ kế thừa của hàm isinstance():

# -----------------------------------------------------------
#Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
#@author cafedevn
#Contact: cafedevn@gmail.com
#Fanpage: https://www.facebook.com/cafedevn
#Group: https://www.facebook.com/groups/cafedev.vn/
#Instagram: https://instagram.com/cafedevn
#Twitter: https://twitter.com/CafedeVn
#Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
#Pinterest: https://www.pinterest.com/cafedevvn/
#YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
# -----------------------------------------------------------

#python code to show isintance() support 
#inheritance 
class MyDict(dict): 
    """A normal dict, that is always created with an "initial" key"""
    def __init__(self): 
        self["initial"] = "some data"
  
d = MyDict() 
print(isinstance(d, MyDict)) 
print(isinstance(d, dict)) 
  
d = dict() 
print(isinstance(d, MyDict)) 
print(isinstance(d, dict)) 

Kết quả in ra là:

True
True
False
True

1.2. Hàm Callable()

Bất cứ cái gì mà có thể được gọi đến, thì đều được coi là một cái callable. Khi sử dụng cho một đối tượng, hàm callable() sẽ xác định liệu rằng đối tượng đó thể được gọi đến hay không. Có thể làm cho một class trở nên callable (có thể gọi đến) bằng cách cung cấp cho nó một phương thức __call__(). Phương thức callable() sẽ trả về True nếu đối tượng được truyền vào là callable (có thể gọi đến), nếu không nó sẽ trả về False.

Ví dụ:

x = 5

def testFunction():
  print("Test")
   
y = testFunction

if (callable(x)):
    print("x is callable")
else:
    print("x is not callable")

if (callable(y)):
    print("y is callable")
else:
    print("y is not callable")

Kết quả in ra là:

x is not callable
y is callable

Ví dụ về callable khi được sử dụng trong OOP:

class Foo1:
  def __call__(self):
    print('Print Something')

print(callable(Foo1))

Kết quả in ra là:

True

1.3. Dir:

Phương thức dir() sẽ cố gắng trả về một danh sách các thuộc tính (attributes) hợp lệ của đối tượng. Nếu một đối tượng có phương thức __dir__(), thì phương thức này sẽ được gọi, và phải trả về một danh sách các thuộc tính. Nếu đối tượng không có phương thức __dir()__, thì phương thức này sẽ cố gắng tìm kiếm các thông tin từ thuộc tính __dict__ (nếu được khai báo), và đối tượng type (type object). Trong trường hợp này, danh sách được trả về từ phương thức dir() có thể sẽ không đầy đủ.

Ví dụ:

number = [1,2,3]
print(dir(number))

characters = ["a", "b"]
print(dir(number))

Kết quả in ra là:

1.4. Getattr

Phương thức getattr() sẽ trả về giá trị của thuộc tính được đặt tên của một đối tượng. Nếu không tìm thấy, nó sẽ trả về giá trị mặc định đã được cung cấp cho phương thức này. Phương thức getattr sẽ nhận vào ba tham số là object (đối tượng), name (tên của thuộc tính muốn truy xuất giá trị), và default (giá trị mặc định được trả về nếu không tìm thấy giá trị của thuộc tính có tên là name, tham số này là tùy chọn, không bắt buộc phải có).

Ví dụ:

class Employee:
    salary = 25000
    company_name= "geeksforgeeks"

employee = Employee()
print('The salary is:', getattr(employee, "salary"))
print('The salary is:', employee.salary)

Kết quả in ra là:

The salary is: 25000
The salary is: 25000

Tham khảo:
1. docs_python
2. wikibooks

Nguồn và Tài liệu tiếng anh tham khảo:

Tài liệu từ cafedev:

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!