Python و Object/Class attrs - ما الذي يحدث؟
-
27-09-2019 - |
سؤال
هل يمكن لأحد أن يشرح لماذا يفعل بيثون ما يلي؟
>>> class Foo(object):
... bar = []
...
>>> a = Foo()
>>> b = Foo()
>>> a.bar.append(1)
>>> b.bar
[1]
>>> a.bar = 1
>>> a.bar
1
>>> b.bar
[1]
>>> a.bar = []
>>> a.bar
[]
>>> b.bar
[1]
>>> del a.bar
>>> a.bar
[1]
إنه أمر محير إلى حد ما!
المحلول
كما قال آخرون ، فإن الرمز كما هو مكتوب يخلق متغيرًا فئة بدلاً من متغير مثيل. تحتاج إلى تعيين في __init__
لإنشاء متغير مثيل.
نأمل أن تكون هذه النسخة المشروحة من الكود مفيدة في شرح ما يجري في كل مرحلة:
>>> class Foo(object):
... bar = [] # defines a class variable on Foo (shared by all instances)
...
>>> a = Foo()
>>> b = Foo()
>>> a.bar.append(1) # appends the value 1 to the previously empty list Foo.bar
>>> b.bar # returns the value of the class variable Foo.bar
[1]
>>> a.bar = 1 # binds 1 to the instance variable a.bar, masking the access
>>> a.bar # you previously had to the class variable through a.bar
1
>>> b.bar # b doesn't have an instance variable 'bar' so this still
[1] # returns the class variable
>>> a.bar = [] # bind a's instance variable to to an empty list
>>> a.bar
[]
>>> b.bar # b doesn't have an instance variable 'bar' so this still
[1] # returns the class variable
>>> del a.bar # unbinds a's instance variable unmasking the class variable
>>> a.bar # so a.bar now returns the list with 1 in it.
[1]
أيضا ، طباعة قيمة Foo.bar
(قد يساعد متغير الفصل الذي يتم الوصول إليه عبر الفصل بدلاً من مثيله) بعد كل من العبارات الخاصة بك في توضيح ما يجري.
نصائح أخرى
هذا لأن الطريقة التي كتبتها ، bar
هو متغير فئة وليس متغير مثيل.
لتحديد متغير مثيل ، قم بربطه في المُنشئ:
class Foo(object):
def __init__(self):
self.bar = []
لاحظ أنه ينتمي الآن إلى مثيل واحد Foo
(self
) بدلا من Foo
الفصل ، وسوف ترى النتائج التي تتوقعها عند تعيينها.
عندما تعلن عن عنصر في الفصل مثل ذلك ، يتم مشاركته من قبل جميع حالات الفصل. لإنشاء عضو فئة مناسب ينتمي إلى كل مثيل ، بشكل منفصل ، قم بإنشائه في __init__ مثل ما يلي:
class Foo(object):
def __init__(self):
self.bar = []
في البداية، bar
هو متغير الفصل ويتم مشاركته بين a
و b
, ، على حد سواء a.bar
و b.bar
الرجوع إلى نفس الكائن.
عند تعيين قيمة جديدة إلى a.bar
, ، هذا لا يكتب متغير الفئة ، ويضيف متغير مثيل جديد إلى a
كائن ، إخفاء متغير الفئة عند الوصول إليه a.bar
. إذا قمت بحذف a.bar
(متغير المثيل) ، ثم a.bar
يحل مرة أخرى إلى متغير الفصل.
b.bar
من ناحية أخرى ، يشير دائمًا إلى متغير الفصل ، فهو لا يتأثر بالإضافات bar
على ال a
كائن أو أي قيم مخصصة لذلك.
لتعيين متغير الفصل يمكنك الوصول إليه من خلال الفصل نفسه:
Foo.bar = 1
>>> class Foo(object):
... bar = []
...
bar
هو متغير فئة مشتركة ، وليس متغير مثيل. أعتقد أن ذلك يتعامل مع معظم ارتباكك. لجعله مثيل var ، حدده في الفصل __init__
لكل إجابات أخرى.
>>> a = Foo()
>>> b = Foo()
>>> a.bar.append(1)
>>> b.bar
[1]
هذا هو دليل على ذلك.
>>> a.bar = 1
>>> a.bar
1
>>> b.bar
[1]
الآن قمت بإعادة تعريفها a.bar
كمتغير مثيل. هذا ما يحدث عندما تحدد المتغيرات خارجيًا افتراضيًا.
>>> a.bar = []
>>> a.bar
[]
>>> b.bar
[1]
>>> del a.bar
>>> a.bar
[1]
نفس الشيء مرة أخرى. b.bar
لا يزال متغير الفئة المشتركة.
في ملاحظة ذات صلة ، يجب أن تكون على دراية بهذا المأزق الذي قد تراه في وقت ما قريبًا:
class A:
def __init__(self, mylist = []):
self.mylist = mylist
a = A()
a2 = A()
a.mylist.append(3)
print b.mylist #prints [3] ???
هذا يربك الكثير من الناس ويتعلق بكيفية تفسير الكود. Python يفسر في الواقع عناوين الوظائف أولاً ، لذلك فهي تُقيّم __init__(self, mylist = [])
ويخزن إشارة إلى تلك القائمة كمعلمة افتراضية. هذا يعني أن جميع مثيلات الوصية (ما لم يتم توفير قائمتها الخاصة) تشير إلى القائمة الأصلية. الرمز الصحيح للقيام بهذا الشيء سيكون
class A:
def __init__(self, mylist=None):
if mylist:
self.mylist = mylist
else:
self.mylist = []
أو إذا كنت تريد تعبيرًا أقصر ، فيمكنك استخدام بناء الجملة الثلاثي:
self.mylist = mylist if mylist else []