ทำซ้ำมากกว่าคุณลักษณะของวัตถุในหลาม


162

ฉันมีวัตถุหลามที่มีคุณลักษณะและวิธีการหลายอย่าง ฉันต้องการวนซ้ำคุณลักษณะของวัตถุ

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

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

ฉันต้องการสร้างพจนานุกรมที่มีคุณลักษณะของวัตถุทั้งหมดและค่าปัจจุบันของพวกเขา แต่ฉันต้องการที่จะทำมันในแบบไดนามิก (ดังนั้นถ้าภายหลังฉันเพิ่มคุณสมบัติอื่นที่ฉันไม่ต้องจำเพื่อปรับปรุงฟังก์ชั่นของฉันด้วย)

ในตัวแปร php สามารถใช้เป็นกุญแจได้ แต่วัตถุในไพ ธ อนนั้นไม่สามารถอธิบายได้และถ้าฉันใช้เครื่องหมายจุดสำหรับสิ่งนี้มันจะสร้างแอททริบิวใหม่ที่มีชื่อ var ของฉันซึ่งไม่ใช่เจตนาของฉัน

เพียงเพื่อทำให้สิ่งต่าง ๆ ชัดเจนขึ้น:

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

อัปเดต: ด้วยคุณลักษณะฉันหมายถึงเฉพาะตัวแปรของวัตถุนี้ไม่ใช่วิธีการ


3
stackoverflow.com/questions/1251692/…อาจมีความช่วยเหลือบ้าง
ฌอน

@sean โดยเฉพาะstackoverflow.com/a/1251789/4203เป็นวิธีที่จะไป; คุณจะใช้predicateอาร์กิวเมนต์ที่เป็นตัวเลือกเพื่อส่งผ่าน callable ที่จะกรองฟังก์ชัน (bound?) (ที่กำจัดเมธอด)
แฮงค์เกย์

ต่อ sean คำถามนี้ตอบคำถามของคุณหรือไม่? จะระบุคุณสมบัติของวัตถุใน Python ได้อย่างไร
Davis Herring

คำตอบ:


227

สมมติว่าคุณมีชั้นเรียนเช่น

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

การเรียกdirใช้อ็อบเจกต์จะให้ข้อมูลแอททริบิวต์ทั้งหมดของวัตถุนั้นรวมถึงแอททริบิวต์พิเศษของไพ ธ อน แม้ว่าบางคุณลักษณะของวัตถุจะเรียกได้เช่นวิธีการ

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

คุณสามารถกรองวิธีการพิเศษโดยใช้รายการความเข้าใจ

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

หรือถ้าคุณชอบแผนที่ / ตัวกรอง

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

หากคุณต้องการกรองวิธีการต่างๆคุณสามารถใช้ builtin callableเป็นตัวตรวจสอบ

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

คุณสามารถตรวจสอบความแตกต่างระหว่างคลาสของคุณกับวัตถุตัวอย่างของมันโดยใช้

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

1
นี่เป็นความคิดที่ไม่ดีฉันจะไม่แม้แต่จะแนะนำ แต่ถ้าคุณจะไปอย่างน้อยก็ให้ข้อแม้
Julian

2
@Meitham @Pablo มีอะไรผิดปกติเป็นส่วนใหญ่ที่คุณไม่จำเป็นต้องทำ ซึ่งคุณลักษณะที่คุณสนใจในกำลังควรจะรวมได้แต่เพียงผู้เดียว ในขณะที่คุณได้เห็นแล้วคุณรวมทั้งวิธีการและพยายามที่จะแยกพวกเขาจะมีข้อบกพร่องเพราะพวกเขากำลังจะไปเกี่ยวข้องกับสิ่งที่น่ารังเกียจเช่นหรือcallable types.MethodTypeจะเกิดอะไรขึ้นถ้าฉันเพิ่มแอตทริบิวต์ชื่อ "_foo" ที่เก็บผลลัพธ์ระหว่างกลางหรือโครงสร้างข้อมูลภายใน จะเกิดอะไรขึ้นถ้าฉันไม่เพิ่มคุณสมบัติลงในคลาส a name, พูดและตอนนี้ก็รวมอยู่ในทันที
Julian

3
@ จูเลียนโค้ดด้านบนจะเลือกทั้งคู่_fooและnameเพราะตอนนี้พวกเขาเป็นคุณลักษณะของวัตถุ หากคุณไม่ต้องการที่จะรวมพวกเขาคุณสามารถยกเว้นพวกเขาในเงื่อนไขความเข้าใจรายการ รหัสข้างต้นเป็นสำนวนภาษาไพ ธ อนทั่วไปและวิธีการที่นิยมของวัตถุหลาม
Meitham

30
ที่จริงแล้ว obj .__ dict__ นั้นดีกว่า (อาจ) สำหรับจุดประสงค์นี้
เดฟ

5
@ จูเลียนdir()เท่านั้น "โกหก" เมื่อผ่าน metaclasses (กรณีขอบสุดขีด) หรือคลาสที่มีคุณลักษณะที่ตกแต่งโดย@DynamicClassAttribute(ตัวพิมพ์ขอบอีกตัวหนึ่ง) ในทั้งสองกรณีการเรียกdirs()wrapper inspect.getmembers()แก้ปัญหานี้ อย่างไรก็ตามสำหรับวัตถุมาตรฐานวิธีการทำความเข้าใจรายการของโซลูชันนี้จะกรองความไม่เพียงพอของcallables อย่างสมบูรณ์ Pythonistas บางตัวจะระบุว่าเป็น "ความคิดที่ไม่ดี" ทำให้ฉันงุนงง แต่ ... สำหรับพวกเขาแต่ละคนฉันคิดว่า?
เซซิลแกงกะหรี่

57

โดยทั่วไปให้ใส่__iter__วิธีการในชั้นเรียนของคุณและทำซ้ำผ่านคุณสมบัติของวัตถุหรือใส่คลาสมิกซ์อินนี้ในชั้นเรียนของคุณ

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

ชั้นเรียนของคุณ:

>>> 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'}

นี้ทำงานเฉพาะเมื่อoneและถูกกำหนดให้หลังtwo yc = YourClass()คำถามที่ถามเกี่ยวกับการวนลูปผ่าน attris YourClass()ที่มีอยู่ใน นอกจากนี้การสืบทอดIterMixinคลาสอาจไม่สามารถใช้เป็นโซลูชันได้เสมอไป
เซียว

4
vars(yc)ให้ผลลัพธ์เดียวกันโดยไม่ต้องสืบทอดจากคลาสอื่น
นาธาน

1
ความคิดเห็นด้านบนของฉันไม่ถูกต้องนัก vars(yc)เกือบจะเหมือนกัน แต่พจนานุกรมที่ให้คุณกลับมาเป็นของอินสแตนซ์__dict__ดังนั้นการแก้ไขจะถูกแสดงในอินสแตนซ์ วิธีนี้อาจนำไปสู่ปัญหาหากคุณไม่ระวังดังนั้นบางครั้งวิธีการข้างต้นอาจดีกว่า (แม้ว่าการคัดลอกพจนานุกรมอาจจะง่ายกว่า) นอกจากนี้โปรดทราบว่าในขณะที่พจนานุกรมที่ส่งคืนด้านบนไม่ใช่ของอินสแตนซ์__dict__ค่าจะเหมือนกันดังนั้นการแก้ไขค่าที่ไม่แน่นอนใด ๆ จะยังคงปรากฏในแอตทริบิวต์ของอินสแตนซ์
นาธาน

25

ตามที่กล่าวไว้ในคำตอบ / ความคิดเห็นบางส่วนแล้ววัตถุ Python จะจัดเก็บพจนานุกรมของคุณสมบัติของพวกเขาไว้แล้ว (ไม่รวมวิธีการ) สิ่งนี้สามารถเข้าถึงได้เป็น__dict__แต่วิธีที่ดีกว่าคือการใช้vars(เอาท์พุทเหมือนกันแม้ว่า) โปรดทราบว่าการแก้ไขพจนานุกรมนี้จะแก้ไขแอตทริบิวต์ในอินสแตนซ์! สิ่งนี้มีประโยชน์ แต่ก็หมายความว่าคุณควรระมัดระวังในการใช้พจนานุกรมนี้ด้วย นี่คือตัวอย่างรวดเร็ว:

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}

การใช้dir(a)เป็นเรื่องแปลกหากไม่ได้เลวร้ายอย่างสิ้นเชิงเข้าใกล้ปัญหานี้ เป็นเรื่องที่ดีถ้าคุณต้องการย้ำคุณสมบัติและวิธีการเรียนทั้งหมด (รวมถึงวิธีพิเศษเช่น__init__) อย่างไรก็ตามนี่ไม่ใช่สิ่งที่คุณต้องการและแม้กระทั่งคำตอบที่ได้รับการยอมรับก็ไม่ดีพอโดยการใช้การกรองแบบเปราะบางเพื่อพยายามลบวิธีการและทิ้งคุณลักษณะไว้ คุณสามารถดูว่าสิ่งนี้จะล้มเหลวสำหรับคลาสที่Aกำหนดไว้ข้างต้น

(การใช้งาน__dict__ได้ทำในสองคำตอบ แต่พวกเขาทั้งหมดกำหนดวิธีการที่ไม่จำเป็นแทนการใช้โดยตรงเพียงความคิดเห็นแนะนำให้ใช้vars)


1
สิ่งที่ฉันไม่ได้คิดด้วยวิธี vars () คือวิธีจัดการกับสถานการณ์ที่คลาส A มีสมาชิกที่เป็นวัตถุอื่นซึ่งเป็นประเภทที่ผู้ใช้กำหนดคลาส vars vars (a) ดูเหมือนจะเรียก __repr __ () ในสมาชิกประเภท B. __repr __ () ตามที่ฉันเข้าใจควรจะส่งคืนสตริง แต่เมื่อฉันเรียก vars (a) ดูเหมือนว่ามันจะสมเหตุสมผลสำหรับการเรียกนี้เพื่อคืนค่า dict ที่ซ้อนกันแทนที่จะเป็น dict ที่มีการแสดงสตริงของ B.
plafratt

1
ถ้าคุณมีa.bเป็นชั้นเองบางส่วนBแล้วvars(a)["b"] is a.bเป็นหนึ่งคาดว่าจะได้; ไม่มีอะไรจะทำให้รู้สึก (คิดว่าa.bเป็นน้ำตาล syntactic สำหรับa.__dict__["b"]) หากคุณมีd = vars(a)และโทรrepr(d)แล้วมันจะเรียกrepr(d["b"])ว่าเป็นส่วนหนึ่งของการกลับมาเป็นสายอักขระที่ซ้ำกันของตัวเองเป็นคลาสเท่านั้นที่Bรู้ว่ามันควรจะแสดงเป็นสตริงอย่างไร
นาธาน

2

วัตถุในหลามเก็บ atributes ของพวกเขา (รวมถึงฟังก์ชั่น) ใน Dict __dict__ที่เรียกว่า คุณสามารถ (แต่โดยทั่วไปไม่ควร) ใช้สิ่งนี้เพื่อเข้าถึงแอตทริบิวต์โดยตรง หากคุณต้องการรายการคุณยังสามารถโทรdir(obj)ซึ่งจะส่งคืนชื่อ iterable ที่มีชื่อแอตทริบิวต์ทั้งหมดซึ่งคุณสามารถส่งผ่านgetattrได้

อย่างไรก็ตามการทำอะไรที่มีชื่อของตัวแปรนั้นมักจะเป็นการออกแบบที่ไม่ดี ทำไมไม่เก็บไว้ในของสะสม?

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

จากนั้นคุณสามารถทำซ้ำปุ่มมากกว่าด้วย for key in obj.special_values:


คุณช่วยอธิบายเพิ่มเติมอีกเล็กน้อยได้ว่าฉันจะใช้คอลเลกชันเพื่อให้บรรลุสิ่งที่ฉันต้องการได้อย่างไร
Pablo Mescher

1
ฉันไม่ได้เพิ่มคุณสมบัติให้กับวัตถุแบบไดนามิก ตัวแปรที่ฉันจะยังคงเหมือนเดิมเป็นเวลานาน แต่ฉันคิดว่ามันดีที่จะทำสิ่งที่ฉันตั้งใจจะทำ
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

จากนั้นเพียงแค่เรียกมันว่าเป็น iterable

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

ดูเหมือนซับซ้อนเกินไป ถ้าฉันไม่มีทางเลือกฉันก็อยากจะยึดติดกับสิ่งที่ฉันทำอยู่ตอนนี้
Pablo Mescher

0

คำตอบที่ถูกต้องคือคุณไม่ควรทำ หากคุณต้องการสิ่งประเภทนี้เพียงใช้ dict หรือคุณจะต้องเพิ่มแอตทริบิวต์ลงในคอนเทนเนอร์บางตัว คุณสามารถทำให้เป็นแบบอัตโนมัติได้โดยเรียนรู้เกี่ยวกับการตกแต่ง

โดยเฉพาะอย่างยิ่งวิธีการที่ 1 ในตัวอย่างของคุณนั้นดีพอ ๆ กับแอตทริบิวต์


2
ใช่ฉันรู้ว่าฉันไม่ควร .. แต่มันช่วยให้ฉันเข้าใจว่าสิ่งต่าง ๆ ทำงานอย่างไร ฉันมาจากพื้นหลัง php และเคยทำสิ่งนี้ทุกวัน
Pablo Mescher

คุณเชื่อฉัน การรักษาวิธีการ to_dict () ได้รับการปรับปรุงนั้นตรงไปตรงมามากกว่าคำตอบใด ๆ
Pablo Mescher

1
มีคนเหลือที่จะละเว้นจากคำแนะนำดันทุรัง การเขียนโปรแกรมแบบไดนามิกจะเผาคุณถ้าคุณไม่ระวัง แต่คุณสมบัติเป็นภาษาที่จะใช้
Henrik Vendelbo

0

สำหรับ python 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
คุณสามารถเพิ่มคำอธิบายบางอย่างให้กับโพสต์ของคุณเพื่อที่ว่าไพ ธ อนที่ไม่ใช่มืออาชีพจะได้ประโยชน์จากคำตอบของคุณ?
not2qubit

-3

สำหรับ zealots pythonian ทั้งหมดที่นั่นฉันแน่ใจว่า Johan Cleeze จะเห็นด้วยกับความหยิ่งยโสของคุณ;) ฉันทิ้งคำตอบนี้เอาไว้ทำให้มันแย่ลงจริง ๆ แล้วมันทำให้ฉันไว้ใจมากขึ้น แสดงความคิดเห็นคุณไก่!

สำหรับ python 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

มีเหตุผลที่คุณมีสองคำตอบสำหรับคำถามนี้หรือไม่?
นาธาน

@ นาธานนั่นเป็นเพราะ verbose มากกว่าและอีก pythonian ฉันก็ไม่รู้เหมือนกันว่าจะทำงานเร็วกว่าอะไรฉันจึงตัดสินใจให้ผู้อ่านเลือก
ยางเป็ด

@ นาธานคุณเป็นตำรวจไพ ธ อนหรือตำรวจล้น มีรูปแบบการเข้ารหัสที่แตกต่างกัน ฉันชอบไพทอนนิกเพราะฉันทำงานในหลายภาษาและเทคโนโลยีและฉันคิดว่ามันมีแนวโน้มที่จะเป็นไปได้ อย่างไรก็ตาม Iist comprehension นั้นเจ๋งและรัดกุม ดังนั้นทำไมไม่จ่ายผู้อ่านที่รู้แจ้งทั้งสอง?
ยางเป็ด

สำหรับ zealots pythonian ทั้งหมดที่นั่นฉันแน่ใจว่า Johan Cleeze จะเห็นด้วยกับความหยิ่งยโสของคุณ;) ฉันทิ้งคำตอบนี้เอาไว้ทำให้มันแย่ลงจริง ๆ แล้วมันทำให้ฉันไว้ใจมากขึ้น แสดงความคิดเห็นคุณไก่!
เป็ดยาง
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.