سؤال

هل يمكن لأحد أن يشرح لماذا يفعل بيثون ما يلي؟

>>> 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 []
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top