Lặp lại các thuộc tính đối tượng trong python


162

Tôi có một đối tượng python với một số thuộc tính và phương thức. Tôi muốn lặp lại các thuộc tính đối tượng.

class my_python_obj(object):
    attr1='a'
    attr2='b'
    attr3='c'

    def method1(self, etc, etc):
        #Statements

Tôi muốn tạo một từ điển chứa tất cả các thuộc tính đối tượng và các giá trị hiện tại của chúng, nhưng tôi muốn thực hiện nó theo cách động (vì vậy nếu sau này tôi thêm một thuộc tính khác thì tôi cũng không phải nhớ cập nhật chức năng của mình).

Trong các biến php có thể được sử dụng làm khóa, nhưng các đối tượng trong python không thể mô tả được và nếu tôi sử dụng ký hiệu dấu chấm cho việc này, nó sẽ tạo ra một thuộc tính mới với tên var của tôi, đây không phải là ý định của tôi.

Chỉ để làm cho mọi thứ rõ ràng hơn:

def to_dict(self):
    '''this is what I already have'''
    d={}
    d["attr1"]= self.attr1
    d["attr2"]= self.attr2
    d["attr3"]= self.attr3
    return d

·

def to_dict(self):
    '''this is what I want to do'''
    d={}
    for v in my_python_obj.attributes:
        d[v] = self.v
    return d

Cập nhật: Với các thuộc tính tôi chỉ có nghĩa là các biến của đối tượng này, không phải các phương thức.


3
stackoverflow.com/questions/1251692/ Mạnh Có thể giúp đỡ.
sean

@sean Đặc biệt, stackoverflow.com/a/1251789/4203 là con đường để đi; bạn sẽ sử dụng predicateđối số tùy chọn để truyền một hàm có thể gọi ra các hàm (bị ràng buộc?) (loại bỏ các phương thức).
Hank Gay

Mỗi sean, điều này có trả lời câu hỏi của bạn? Làm thế nào để liệt kê các thuộc tính của đối tượng trong Python?
Davis Herring

Câu trả lời:


227

Giả sử bạn có một lớp như

>>> class Cls(object):
...     foo = 1
...     bar = 'hello'
...     def func(self):
...         return 'call me'
...
>>> obj = Cls()

việc gọi dirđối tượng sẽ trả lại cho bạn tất cả các thuộc tính của đối tượng đó, bao gồm các thuộc tính đặc biệt của python. Mặc dù một số thuộc tính đối tượng có thể gọi được, chẳng hạn như các phương thức.

>>> dir(obj)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo', 'func']

Bạn luôn có thể lọc ra các phương thức đặc biệt bằng cách sử dụng một danh sách hiểu.

>>> [a for a in dir(obj) if not a.startswith('__')]
['bar', 'foo', 'func']

hoặc nếu bạn thích bản đồ / bộ lọc.

>>> filter(lambda a: not a.startswith('__'), dir(obj))
['bar', 'foo', 'func']

Nếu bạn muốn lọc ra các phương thức, bạn có thể sử dụng nội dung callabledưới dạng kiểm tra.

>>> [a for a in dir(obj) if not a.startswith('__') and not callable(getattr(obj, a))]
['bar', 'foo']

Bạn cũng có thể kiểm tra sự khác biệt giữa lớp của bạn và đối tượng thể hiện của nó bằng cách sử dụng.

>>> set(dir(Cls)) - set(dir(object))
set(['__module__', 'bar', 'func', '__dict__', 'foo', '__weakref__'])

1
Đây là một ý tưởng tồi, tôi thậm chí sẽ không đề xuất nó, nhưng nếu bạn sẽ, ít nhất là cung cấp sự cảnh báo.
Julian

2
@Meitham @Pablo Điều gì sai chủ yếu là bạn không cần phải làm điều này. Những thuộc tính bạn quan tâm nên được bao gồm không độc quyền . Như bạn đã thấy, bạn bao gồm các phương thức và mọi nỗ lực để loại trừ chúng sẽ bị sai sót, bởi vì chúng sẽ liên quan đến những điều khó chịu như callablehoặc types.MethodType. Điều gì xảy ra nếu tôi thêm một thuộc tính có tên "_foo" lưu trữ một số kết quả trung gian hoặc cấu trúc dữ liệu nội bộ? Điều gì xảy ra nếu tôi vô tình thêm một thuộc tính vào lớp, a name, giả sử, và bây giờ tất cả đều bất ngờ được đưa vào.
Julian

3
@Julian đoạn mã trên sẽ chọn cả hai _foonamevì bây giờ chúng là thuộc tính của đối tượng. Nếu bạn không muốn bao gồm chúng, bạn có thể loại trừ chúng trong điều kiện hiểu danh sách. Đoạn mã trên là một thành ngữ python phổ biến và cách phổ biến để hướng nội các đối tượng python.
Meitham

30
Trên thực tế obj .__ dict__ là (có lẽ) tốt hơn cho mục đích này.
Dave

5
@Julian dir()chỉ "nói dối" khi thông qua siêu dữ liệu (trường hợp cạnh cực) hoặc các lớp có thuộc tính được trang trí bởi @DynamicClassAttribute(trường hợp cạnh cực khác). Trong cả hai trường hợp, gọi dirs()trình bao bọc sẽ inspect.getmembers()giải quyết điều này. Tuy nhiên, đối với các đối tượng tiêu chuẩn, cách tiếp cận hiểu danh sách của giải pháp này lọc ra những thứ không thể gọi được hoàn toàn đủ. Rằng một số Pythonistas sẽ gán cho nó một "ý tưởng tồi" gây trở ngại cho tôi, nhưng ... với mỗi người, tôi cho rằng?
Cecil Curry

57

nói chung, đặt một __iter__phương thức trong lớp của bạn và lặp qua các thuộc tính đối tượng hoặc đặt lớp mixin này trong lớp của bạn.

class IterMixin(object):
    def __iter__(self):
        for attr, value in self.__dict__.iteritems():
            yield attr, value

Lớp của bạn:

>>> class YourClass(IterMixin): pass
...
>>> yc = YourClass()
>>> yc.one = range(15)
>>> yc.two = 'test'
>>> dict(yc)
{'one': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'two': 'test'}

Điều này chỉ hoạt động khi onetwođược xác định sau yc = YourClass(). Câu hỏi hỏi về việc lặp qua các attris hiện có YourClass(). Ngoài ra, kế thừa IterMixinlớp có thể không phải lúc nào cũng có sẵn như là một giải pháp.
Xiao

4
vars(yc)cho kết quả tương tự mà không phải kế thừa từ lớp khác.
Nathan

1
Nhận xét trên của tôi không hoàn toàn đúng; vars(yc)gần như giống nhau, nhưng từ điển mà nó đưa lại cho bạn là của cá thể __dict__, vì vậy việc sửa đổi nó sẽ được phản ánh trên cá thể. Điều này có thể dẫn đến các vấn đề nếu bạn không cẩn thận, vì vậy phương pháp trên đôi khi có thể tốt hơn (mặc dù sao chép từ điển có thể dễ dàng hơn). Ngoài ra, lưu ý rằng mặc dù từ điển được trả về ở trên không phải là của thể hiện __dict__, các giá trị là như nhau, vì vậy sửa đổi bất kỳ giá trị có thể thay đổi nào vẫn sẽ được phản ánh trong các thuộc tính của thể hiện.
Nathan

25

Như đã đề cập trong một số câu trả lời / nhận xét đã có, các đối tượng Python đã lưu trữ một từ điển các thuộc tính của chúng (không bao gồm các phương thức). Điều này có thể được truy cập như __dict__, nhưng cách tốt hơn là sử dụng vars(mặc dù đầu ra là như nhau). Lưu ý rằng sửa đổi từ điển này sẽ sửa đổi các thuộc tính trên ví dụ! Điều này có thể hữu ích, nhưng cũng có nghĩa là bạn nên cẩn thận với cách bạn sử dụng từ điển này. Đây là một ví dụ nhanh:

class A():
    def __init__(self, x=3, y=2, z=5):
        self.x = x
        self._y = y
        self.__z__ = z

    def f(self):
        pass

a = A()
print(vars(a))
# {'x': 3, '_y': 2, '__z__': 5}
# all of the attributes of `a` but no methods!

# note how the dictionary is always up-to-date
a.x = 10
print(vars(a))
# {'x': 10, '_y': 2, '__z__': 5}

# modifying the dictionary modifies the instance attribute
vars(a)["_y"] = 20
print(vars(a))
# {'x': 10, '_y': 20, '__z__': 5}

Sử dụng dir(a)là một cách kỳ lạ, nếu không hoàn toàn xấu, tiếp cận vấn đề này. Thật tốt nếu bạn thực sự cần lặp lại tất cả các thuộc tính và phương thức của lớp (bao gồm các phương thức đặc biệt như __init__). Tuy nhiên, đây dường như không phải là điều bạn muốn, và thậm chí câu trả lời được chấp nhận cũng không tốt bằng cách áp dụng một số bộ lọc dễ vỡ để cố gắng loại bỏ các phương thức và chỉ để lại các thuộc tính; bạn có thể thấy điều này sẽ thất bại như thế nào đối với lớp Ađược định nghĩa ở trên.

(việc sử dụng __dict__đã được thực hiện trong một vài câu trả lời, nhưng tất cả đều xác định các phương thức không cần thiết thay vì sử dụng trực tiếp. Chỉ một nhận xét gợi ý sử dụng vars).


1
Một cái gì đó tôi chưa tìm ra với cách tiếp cận vars () là cách xử lý tình huống trong đó lớp A có một thành viên là một đối tượng khác có loại là lớp B. vars (a) do người dùng định nghĩa dường như gọi __Vpr __ () theo thành viên của loại B. __Vpr __ (), theo tôi hiểu, có nghĩa vụ phải trả về một chuỗi. Nhưng khi tôi gọi vars (a), có vẻ như sẽ có ý nghĩa khi cuộc gọi này trả lại một câu lệnh lồng nhau, thay vì một câu lệnh có biểu diễn chuỗi của B.
plafratt

1
Nếu bạn có a.bmột số lớp tùy chỉnh Bsau đó vars(a)["b"] is a.b, như người ta mong đợi; không có gì khác thực sự có ý nghĩa (nghĩ a.bnhư là cú pháp cho a.__dict__["b"]). Nếu bạn có d = vars(a)và gọi repr(d)thì nó sẽ gọi repr(d["b"])như là một phần của việc trả về chuỗi repr của chính nó vì chỉ có lớp Bthực sự biết nó nên được biểu diễn như thế nào dưới dạng chuỗi.
Nathan

2

Các đối tượng trong python lưu trữ các thuộc tính của chúng (bao gồm các hàm) trong một lệnh được gọi __dict__. Bạn có thể (nhưng thường không nên) sử dụng điều này để truy cập trực tiếp vào các thuộc tính. Nếu bạn chỉ muốn một danh sách, bạn cũng có thể gọi dir(obj), nó trả về một lần lặp với tất cả các tên thuộc tính, sau đó bạn có thể chuyển đếngetattr .

Tuy nhiên, cần phải làm bất cứ điều gì với tên của các biến thường là thiết kế xấu. Tại sao không giữ chúng trong một bộ sưu tập?

class Foo(object):
    def __init__(self, **values):
        self.special_values = values

Sau đó, bạn có thể lặp lại các phím với for key in obj.special_values:


Bạn có thể giải thích thêm một chút về cách tôi sẽ sử dụng bộ sưu tập để đạt được những gì tôi muốn không?
Pablo Mescher

1
Tôi không thêm các thuộc tính cho đối tượng một cách linh hoạt. Các biến tôi có sẽ giữ nguyên trong một thời gian dài, tuy nhiên tôi nghĩ sẽ thật tuyệt nếu có thể làm điều gì đó giống như những gì tôi dự định.
Pablo Mescher

1
class someclass:
        x=1
        y=2
        z=3
        def __init__(self):
           self.current_idx = 0
           self.items = ["x","y","z"]
        def next(self):
            if self.current_idx < len(self.items):
                self.current_idx += 1
                k = self.items[self.current_idx-1]
                return (k,getattr(self,k))
            else:
                raise StopIteration
        def __iter__(self):
           return self

sau đó chỉ cần gọi nó là một lần lặp

s=someclass()
for k,v in s:
    print k,"=",v

Điều này có vẻ quá phức tạp. Nếu tôi không có sự lựa chọn, tôi thà gắn bó với những gì tôi đang làm ngay bây giờ.
Pablo Mescher

0

Câu trả lời chính xác cho điều này là bạn không nên. Nếu bạn muốn loại điều này hoặc chỉ cần sử dụng một dict, hoặc bạn sẽ cần thêm các thuộc tính vào một số container. Bạn có thể tự động hóa điều đó bằng cách tìm hiểu về trang trí.

Cụ thể, nhân tiện, phương thức 1 trong ví dụ của bạn cũng tốt như một thuộc tính.


2
Vâng, tôi biết tôi không nên .. nhưng nó giúp tôi hiểu mọi thứ hoạt động như thế nào. Tôi đến từ một nền tảng php và thường làm những việc như thế này hàng ngày
Pablo Mescher

Bạn đã thuyết phục tôi. Giữ cho phương thức to_dict () được cập nhật đơn giản hơn nhiều so với bất kỳ câu trả lời nào cho đến nay.
Pablo Mescher

1
Có những người còn lại sẽ kiềm chế lời khuyên giáo điều. Lập trình động sẽ đốt cháy bạn nếu bạn không cẩn thận, nhưng các tính năng là ngôn ngữ sẽ được sử dụng.
Henrik Vendelbo

0

Đối với trăn 3.6

class SomeClass:

    def attr_list(self, should_print=False):

        items = self.__dict__.items()
        if should_print:
            [print(f"attribute: {k}    value: {v}") for k, v in items]

        return items

6
Bạn có thể thêm một số lời giải thích cho bài viết của mình, để những người trăn không chuyên nghiệp cũng có thể hưởng lợi từ câu trả lời của bạn không?
not2qubit

-3

Đối với tất cả những người quá khích ở Pythonia ngoài kia, tôi chắc chắn Johan Cleeze sẽ tán thành chủ nghĩa giáo điều của bạn;). Tôi đang để lại câu trả lời này tiếp tục hủy bỏ nó Nó thực sự khiến tôi thêm tự tin. Để lại một bình luận bạn gà!

Đối với trăn 3.6

class SomeClass:

    def attr_list1(self, should_print=False):

        for k in self.__dict__.keys():
            v = self.__dict__.__getitem__(k)
            if should_print:
                print(f"attr: {k}    value: {v}")

    def attr_list(self, should_print=False):

        b = [(k, v) for k, v in self.__dict__.items()]
        if should_print:
            [print(f"attr: {a[0]}    value: {a[1]}") for a in b]
        return b

Có một lý do bạn có hai câu trả lời cho câu hỏi này?
Nathan

@Nathan có bởi vì một người thì dài dòng hơn và người kia thì hào hoa hơn. Tôi cũng không biết cái nào chạy nhanh hơn nên tôi quyết định để người đọc chọn.
Vịt cao su

@nathan bạn là python hay stack tràn cảnh sát? Có nhiều phong cách mã hóa khác nhau. Tôi thích một người không pythonic bởi vì tôi làm việc trong nhiều ngôn ngữ và công nghệ và tôi nghĩ rằng nó có xu hướng bình dị. Tuy nhiên hiểu Iist là mát mẻ và súc tích. Vậy tại sao không đủ khả năng cho người đọc giác ngộ cả?
Vịt cao su

Đối với tất cả những người quá khích ở Pythonia ngoài kia, tôi chắc chắn Johan Cleeze sẽ tán thành chủ nghĩa giáo điều của bạn;). Tôi đang để lại câu trả lời này tiếp tục hủy bỏ nó Nó thực sự khiến tôi thêm tự tin. Để lại một bình luận bạn gà!
Vịt cao su
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.