Список как член класса python, почему его содержимое является общим для всех экземпляров класса?

Я определил класс Listener и создал словарь объектов Listener. У каждого слушателя есть id, чтобы идентифицировать их, и список artists, который они прослушивают, artists = []. Добавление чего-либо в список artists добавляет его для всех экземпляров класса Listener, а не для указанного экземпляра. Это моя проблема.

Класс Listener определяется следующим образом:

class Listener:
    id = ""
    artists = []

    def __init__(self, id):
        self.id = id

    def addArtist(self, artist, plays):
        print self.id # debugging...
        print "pre: ", self.artists
        self.artists.append(artist)
        print "post: ", self.artists

Вот мой тестовый код для отладки:

def debug():
    listeners = {}
    listeners["0"] = Listener("0")
    listeners["1"] = Listener("1")

    listeners["0"].addArtist("The Beatles", 10)
    listeners["0"].addArtist("Lady Gaga", 4)
    listeners["1"].addArtist("Ace of Base", 5)

И вывод:

0
pre:  []
post:  ['The Beatles']
0
pre:  ['The Beatles']
post:  ['The Beatles', 'Lady Gaga']
1
pre:  ['The Beatles', 'Lady Gaga']
post:  ['The Beatles', 'Lady Gaga', 'Ace of Base']

Мой ожидаемый результат состоит в том, что окончательный вызов addArtist("Ace of Base", 5) приведет к выводу

1
pre:  []
post:  ['Ace of Base']

Является ли это тонкостью Python, я не понимаю? Почему это результат и как я могу получить желаемый результат? Спасибо!

Ответ 1

Вы не хотите, чтобы члены объявлялись внутри класса, но просто устанавливались в методе __init__:

class Listener:
    def __init__(self, id):
        self.id = id
        self.artists = []

    def addArtist(self, artist, plays):
        print self.id # debugging...
        print "pre: ", self.artists
        self.artists.append(artist)
        print "post: ", self.artists

Если у вас есть класс вроде

class A:
  x=5

Тогда x является членом класса, а не членом экземпляров этого класса. Это может сбивать с толку, поскольку python позволяет вам обращаться к членам класса через экземпляр:

>>> a=A()
>>> print a.x
5

Но вы также можете получить доступ к нему через сам класс:

>>> print A.x
5

Казалось бы, что это работает правильно:

>>> a1=A()
>>> a2=A()
>>> a1.x=6
>>> print a1.x
6
>>> print a2.x
5

но на самом деле произошло то, что вы поместили новый x в экземпляр a1, который будет напечатан вместо члена класса, который все еще имеет свое исходное значение:

>>> print A.x
5

Вы только начинаете видеть разницу, когда у вас есть что-то, что можно изменить, например список:

class A:
  l=[]

>>> a1=A()
>>> print a1.l
[]
>>> a2=A()
>>> print a2.l
[]
>>> a1.l.append(5)
>>> print a1.l
[5]
>>> print a2.l
[5]
>>> print A.l
[5]

Ответ 2

Является ли это тонкостью Python, я не понимаю?

Это не тонко, это довольно просто; в отличие от других языков, которые путают проблему, в Python все, что вы объявляете внутри класса, принадлежит классу. Это естественно, так как классы - это объекты (как и все остальное) и, таким образом, совершенно правильное место для присоединения вещей. Таким образом, все эти методы принадлежат классу (вместо того, чтобы каким-то образом волшебным образом копироваться в каждый экземпляр), а также атрибуты данных.

У каждого слушателя есть id, чтобы идентифицировать их

Да, потому что вы присоединяете один к каждому экземпляру в __init__. Это не имеет отношения к id, принадлежащему классу, за исключением того, что при поиске id через экземпляр будет найден собственный экземпляр id, скрывающий принадлежащий ему к классу.

и список исполнителей, которых они слушают, artist = []

При поиске artists через класс, однако, будет найден класс 'artists, потому что экземпляр не имеет его.

Добавление чего-либо в список артистов добавляет его для всех экземпляров класса Listener

Нет; он добавляется к самому классу, в котором искажаются вещи, когда они не найдены в экземпляре.

Имейте в виду, что если вы сделали прямое присваивание, например self.artists = [] в экземпляре позже, этот экземпляр получит свой собственный список, скрывающий список классов. Другие экземпляры не будут, потому что этот код не запускался в других экземплярах.