Hướng dẫn python print object properties - thuộc tính đối tượng in python

Có chức năng tích hợp để in tất cả các thuộc tính và giá trị hiện tại của một đối tượng không?

Không. Câu trả lời được nâng cấp nhiều nhất không bao gồm một số loại thuộc tính và câu trả lời được chấp nhận cho thấy cách lấy tất cả các thuộc tính, bao gồm các phương thức và các phần của API không công khai. Nhưng không có chức năng xây dựng hoàn chỉnh tốt cho việc này.

Nội phân Chính showShow

  • Có chức năng tích hợp để in tất cả các thuộc tính và giá trị hiện tại của một đối tượng không?
  • Vấn đề với các câu trả lời khác
  • Mô hÌnh getter/setter trong python Class
  • Tài sản trong python, ha không () tài sản ()
  • TạO tài sản với trang trí
  • Kết luận

Vì vậy, hệ quả ngắn là bạn có thể tự viết, nhưng nó sẽ tính toán các thuộc tính và các mô tả dữ liệu được tính toán khác là một phần của API công khai và bạn có thể không muốn điều đó:

from pprint import pprint
from inspect import getmembers
from types import FunctionType

def attributes(obj):
    disallowed_names = {
      name for name, value in getmembers(type(obj)) 
        if isinstance(value, FunctionType)}
    return {
      name: getattr(obj, name) for name in dir(obj) 
        if name[0] != '_' and name not in disallowed_names and hasattr(obj, name)}

def print_attributes(obj):
    pprint(attributes(obj))

Vấn đề với các câu trả lời khác

Mô hÌnh getter/setter trong python Class

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))

Tài sản trong python, ha không () tài sản ()

{'baz': 'baz'}

TạO tài sản với trang trí

vars(obj)['quux'] = 'WHAT?!'
vars(obj)

Kết luận

{'baz': 'baz', 'quux': 'WHAT?!'}

Vì vậy, hệ quả ngắn là bạn có thể tự viết, nhưng nó sẽ tính toán các thuộc tính và các mô tả dữ liệu được tính toán khác là một phần của API công khai và bạn có thể không muốn điều đó:

Quan sát ứng dụng của câu trả lời hiện tại được bỏ phiếu hàng đầu trên một lớp với rất nhiều loại thành viên dữ liệu khác nhau:

Chỉ in:

Bởi vì

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
6 chỉ trả về
from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
7 của một đối tượng và nó không phải là một bản sao, vì vậy nếu bạn sửa đổi dict được trả về bởi VAR, bạn cũng sẽ sửa đổi
from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
7 của chính đối tượng.

Trả lại:

- Điều này xấu vì Quux là một tài sản mà chúng ta không nên đặt và không nên ở trong không gian tên ...

Áp dụng lời khuyên trong câu trả lời hiện đang được chấp nhận (và những người khác) không tốt hơn nhiều:

def api(obj):
    return [name for name in dir(obj) if name[0] != '_']

>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']

from types import FunctionType
from inspect import getmembers

def attrs(obj):
    disallowed_properties = {
        name for name, value in getmembers(type(obj)) 
        if isinstance(value, (property, FunctionType))
    }
    return {
        name: getattr(obj, name) for name in api(obj) 
        if name not in disallowed_properties and hasattr(obj, name)
    }

Như chúng ta có thể thấy,

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
9 chỉ trả về tất cả (thực sự chỉ là hầu hết) các tên liên quan đến một đối tượng.

>>> attrs(obj)
{'bar': 0, 'baz': 'baz', 'foo': ''}

{'baz': 'baz'} 0, được đề cập trong các bình luận, tương tự như thiếu sót - nó trả về tất cả các tên và giá trị.

Từ lớp họccustom data descriptors instead.

Khi dạy tôi, học sinh của mình tạo ra một chức năng cung cấp API công khai về ngữ nghĩa của một đối tượng:

Chúng tôi có thể mở rộng điều này để cung cấp một bản sao của không gian tên ngữ nghĩa của một đối tượng, nhưng chúng tôi cần loại trừ {'baz': 'baz'} 1 không được gán và nếu chúng tôi thực hiện yêu cầu "thuộc tính hiện tại" một cách nghiêm túc, chúng tôi cần loại trừ các thuộc tính được tính toán ( vì chúng có thể trở nên đắt đỏ, và có thể được hiểu là không phải là "hiện tại"):

Và bây giờ chúng tôi không tính toán hoặc hiển thị tài sản, Quux:

Hãy cẩn thận

Mô hÌnh getter/setter trong python Class

Tài sản trong python, ha không () tài sản ()

TạO tài sản với trang trí

Kết luận

class Person:
    def __init__(self, fname: str = '', lname: str = '', age: int = 18):
        self.__fname = fname
        self.__lname = lname
        self.__age = age
    def set_age(self, age: int):
        if age > 0:
            self.__age = age
    def get_age(self):
        return self.__age
    def get_lname(self):
        return self.__lname
    def set_lname(self, lname: str):
        if lname.isalpha():
            self.__lname = lname    
    def get_fname(self):
        return self.__fname
    def set_fname(self, fname: str):
        if fname.isalpha():
            self.__fname = fname
    def get_name(self):
        return f'{self.__fname} {self.__lname}'

putin = Person()
putin.set_fname('Putin')
putin.set_lname('Vladimir')
putin.set_age(66)
print(putin.get_name())

Vì vậy, hệ quả ngắn là bạn có thể tự viết, nhưng nó sẽ tính toán các thuộc tính và các mô tả dữ liệu được tính toán khác là một phần của API công khai và bạn có thể không muốn điều đó:

Quan sát ứng dụng của câu trả lời hiện tại được bỏ phiếu hàng đầu trên một lớp với rất nhiều loại thành viên dữ liệu khác nhau:

Chỉ in:

Tài sản trong python, ha không () tài sản ()

Mô hình getter/setter như trên hoạt động tốt. Nhiều ngôn ngữ khuyến khích vận dụng và phát triển mô hình đó. Ví dụ, trong C# cung cấp một số cú pháp tắt để nhanh chóng tạo ra các cặp getter/setter và gọi loại cú pháp đó là C# property.

Trong Python bạn có thể tiếp tục sử dụng mô hình getter/setter như trên. Tuy nhiên, khi sử dụng mô hình này, trong code của bạn chứa quá nhiều lời gọi hàm khiến code nhìn thiếu tự nhiên và khó đọc.

Python đưa ra giải pháp riêng. Hãy điều chỉnh code như sau:

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
0

Hãy để ý nhóm lệnh đặc biệt trong class Person:

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
1

Khi tồn tại nhóm lệnh này, bạn có thể viết code sử dụng class như sau:

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
2

Khi này,

vars(obj)['quux'] = 'WHAT?!'
vars(obj)
9,
{'baz': 'baz', 'quux': 'WHAT?!'}
0 và
{'baz': 'baz', 'quux': 'WHAT?!'}
1 trở thành các property trong class Python.property trong class Python.

Trong class Python, property là một dạng giao diện tới các instance attribute để thực hiện xuất/nhập dữ liệu qua bộ getter/setter. Mỗi property cung cấp một cách thức tự nhiên để nhập xuất dữ liệu cho một instance attribute qua phép gán và phép toán truy xuất phần tử thông thường. Property hoàn toàn che đi lời gọi hàm getter/setter thực tế.

Như vậy, khi sử dụng property

vars(obj)['quux'] = 'WHAT?!'
vars(obj)
9, bạn có thể dùng cách viết tự nhiên
{'baz': 'baz', 'quux': 'WHAT?!'}
3 giống như một biến thành viên thông thường để truy xuất dữ liệu từ biến private
{'baz': 'baz'}
3. Phép gán giá trị cho
vars(obj)['quux'] = 'WHAT?!'
vars(obj)
9 sẽ chuyển thành lời gọi hàm
vars(obj)['quux'] = 'WHAT?!'
vars(obj)
2, lệnh đọc giá trị
vars(obj)['quux'] = 'WHAT?!'
vars(obj)
9 sẽ chuyển thành lời gọi hàm
vars(obj)['quux'] = 'WHAT?!'
vars(obj)
1.

Python cung cấp hai cách để tạo property: sử dụng hàm

{'baz': 'baz', 'quux': 'WHAT?!'}
9 và sử dụng
>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']
0 decorator.

Ở trên chúng ta vừa sử dụng hàm

{'baz': 'baz', 'quux': 'WHAT?!'}
9.

Hàm property() nhận 3 tham số tương ứng với tên hàm getter, setter và deleter. Kết quả của lời gọi hàm property chính là một biến mà bạn có thể sử dụng làm property tương ứng của attribute.getter, setter deleter. Kết quả của lời gọi hàm property chính là một biến mà bạn có thể sử dụng làm property tương ứng của attribute.

Getter và setter thì bạn đã biết. Còn deleter là hàm được gọi tương ứng với lệnh

>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']
2 của Python. Ví dụ, khi gọi lệnh
>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']
3 thì sẽ gọi tới deleter tương ứng. Trên thực tế deleter ít được sử dụng hơn.

Nếu thiếu setter, property trở thành chỉ đọc (read-only).

Tạo property với decorator

Một phương pháp đơn giản hơn để tạo property là sử dụng @property decorator.

Hãy thay đổi code của Person như sau:

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
3

Bạn có thể thấy code của Person giờ tương đối khác biệt và ngắn gọn hơn. Hãy để ý các dòng được đánh dấu. Đây là khối code dùng để tạo ra property sử dung @property decorator.

Lấy ví dụ:

from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
4

Bạn để ý mấy vấn đề sau:

  • Cả getter và setter (và cả deleter) giờ sử dụng chung một tên, và đó cũng là tên của property. Ví dụ nếu bạn cần tạo
    {'baz': 'baz', 'quux': 'WHAT?!'}
    
    1 property để truy xuất giá trị cho attribute
    {'baz': 'baz'}
    
    2 thì cần tạo getter, setter và deleter với cùng một tên
    {'baz': 'baz', 'quux': 'WHAT?!'}
    
    1.
  • Phương thức getter cần đánh dấu với
    >>> dir(obj)
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']
    
    0 decorator.
  • Phương thức setter cần đánh dấu với cấu trúc
    >>> dir(obj)
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']
    
    8, phương thức deleter cần đánh dấu với cấu trúc
    >>> dir(obj)
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']
    
    9. Ví dụ, với
    {'baz': 'baz', 'quux': 'WHAT?!'}
    
    1 property thì setter phải dánh dấu
    def api(obj):
        return [name for name in dir(obj) if name[0] != '_']
    
    1, deleter phải đánh dấu
    def api(obj):
        return [name for name in dir(obj) if name[0] != '_']
    
    2.
  • Nếu chỉ có getter, property trở thành read-only. Ví dụ,
    def api(obj):
        return [name for name in dir(obj) if name[0] != '_']
    
    3 là một property chỉ đọc.
from pprint import pprint

class Obj:
    __slots__ = 'foo', 'bar', '__dict__'
    def __init__(self, baz):
        self.foo = ''
        self.bar = 0
        self.baz = baz
    @property
    def quux(self):
        return self.foo * self.bar

obj = Obj('baz')
pprint(vars(obj))
5

Rõ ràng, cấu trúc khai báo property này đơn giản ngắn gọn và dễ đọc hơn.

Kết luận

Trong bài học này chúng ta đã làm quen với property trong Python với các ý chính sau:

  • Property là loại thành phần tương đối đặc biệt cho phép kết hợp giữa attribute và method để kiểm soát truy xuất dữ liệu cho attribute.
  • Python cung cấp hai cú pháp để xây dựng property: sử dụng hàm
    {'baz': 'baz', 'quux': 'WHAT?!'}
    
    9 và sử dụng
    >>> dir(obj)
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'bar', 'baz', 'foo', 'quux']
    
    0 decorator. Trong đó cú pháp decorator đơn giản gọn nhẹ và dễ đọc hơn.
  • Dù sử dụng phương pháp nào, cần lưu ý rằng property thực chất chỉ là giao diện để gọi tới các phương thức getter, setter và deleter cho attribute theo cách thức tiện lợi hơn.

+ Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.+ Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.+ Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.Cảm ơn bạn!giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
+ Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
+ Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
Cảm ơn bạn!