Я не понимаю какая переменная/атрибут меняется, ведь я хочу получить ошибку AttributeError

from dotenv import load_dotenv
import os
from googleapiclient.discovery import build
import json
load_dotenv()


class Channel:
    """Класс для ютуб-канала"""

    youtube = build('youtube', 'v3', developerKey=os.getenv("API_YOU_TUBE"))

    def __init__(self, channel_id: str) -> None:
        """Экземпляр инициализируется id канала. Дальше все данные будут подтягиваться по API."""
        self.__channel_id = channel_id
        self.title = self.get_data_init()["items"][0]["snippet"]["title"]
        self.description = self.get_data_init()["items"][0]["snippet"]["description"]
        self.url = "https://www.youtube.com/channel/" + self.get_data_init()["items"][0]["id"]
        self.subscriber_count = self.get_data_init()["items"][0]["statistics"]["subscriberCount"]
        self.video_count = self.get_data_init()["items"][0]["statistics"]["videoCount"]
        self.view_count = self.get_data_init()["items"][0]["statistics"]["viewCount"]


    def print_info(self) -> None:
        """Выводит в консоль информацию о канале."""
        channel = self.youtube.channels().list(id=self.__channel_id, part='snippet,statistics').execute()
        print(channel)

    def get_data_init(self):
        channel = self.youtube.channels().list(id=self.__channel_id, part='snippet,statistics').execute()
        return channel

    @staticmethod
    def get_service():
        return Channel.youtube

    def to_json(self, data):
        with open(data, "w") as f:
            f.write(str(self.get_data_init()))

    def __repr__(self):
        return f"{self.__channel_id}, {self.subscriber_count}"

moscowpython = Channel('UC-OVMPlMA3-YCIeg4z5z23A')
moscowpython.__channel_id = 'Новое название'
print(moscowpython.__channel_id)
print(moscowpython)

# не выдает никаких ошибок, хотя должна AttributeError```

Изменено:
 вот так выдает ошибку:
moscowpython = Channel('UC-OVMPlMA3-YCIeg4z5z23A')
print(moscowpython.__channel_id)
moscowpython.__channel_id = 'Новое название'
print(moscowpython.__channel_id)
print(moscowpython)

а вот так нет:

moscowpython = Channel('UC-OVMPlMA3-YCIeg4z5z23A')
# print(moscowpython.__channel_id)
moscowpython.__channel_id = 'Новое название'
print(moscowpython.__channel_id)
print(moscowpython)

Ответы (2 шт):

Автор решения: Artem

Ошибка AttributeError связана с вызовом поля __channel_id объекта у класса Channel

Неправильно:

moscowpython = Channel('UC-OVMPlMA3-YCIeg4z5z23A') # Создание объекта класса
print(moscowpython.__channel_id) # Вызов несуществующего поля

Правильно:

moscowpython = Channel('UC-OVMPlMA3-YCIeg4z5z23A') # Создание объекта класса
moscowpython.__channel_id = 'Новое название' # Присваивание значения полю
print(moscowpython.__channel_id) # Вызов существующего поля
→ Ссылка
Автор решения: Amgarak

Давайте сперва упростим пример и уберём всё лишнее:

class Channel:

    def __init__(self, channel_id: str) -> None:
        self.__channel_id = channel_id
        
    def test(self):
        print(self.__channel_id )
        

moscowpython = Channel('UC-OVMPlMA3-YCIeg4z5z23A')
print(dir(moscowpython))
moscowpython.__channel_id = 'Новое название'
print(moscowpython.__channel_id)
moscowpython.test()
print(dir(moscowpython))

Вывод:

['_Channel__channel_id', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'test']
Новое название
UC-OVMPlMA3-YCIeg4z5z23A
['_Channel__channel_id', '__channel_id', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'test']

[Program finished]

Функция dir() в Python является встроенной функцией, которая возвращает список всех атрибутов и методов объекта.

Теперь обратите внимание на первый список распечатанных объектов, а конкретно на _Channel__channel_id это и есть ваш приватный атрибут.

Во втором списке распечатанных объектов он так же есть, а следовательно мы его не переопределили, что легко проверяется через метод test.


В нашем примере атрибут __channel_id был определен как private в классе Channel. Приватные атрибуты в Python обрабатываются с использованием механизма, называемого name mangling, который переименовывает атрибуты, чтобы предотвратить их случайное переопределение или доступ к ним за пределами класса.

Когда вы объявляете атрибут с двойным подчеркиванием, Python автоматически переименовывает его в форме _ИмяКласса__ИмяАтрибута. В нашем случае, атрибут __channel_id будет переименован в _Channel__channel_id.

Таким образом, если вы попробуете изменить значение moscowpython.__channel_id, как в нашем примере, Python не выбрасывает ошибку, потому что он не видит __channel_id как приватный атрибут. Вместо этого, он просто создает новый атрибут __channel_id в объекте moscowpython, который будет доступен как обычный атрибут, но это уже не тот атрибут, который был определен внутри класса, потому что наш атрибут уже выглядит так - _Channel__channel_id.


Можно немного докрутить идею, что бы нельзя было создавать атрибуты с __ переопределив один из магических методов класса:

class Channel:

    def __init__(self, channel_id: str) -> None:
        self.__channel_id = channel_id
        
    def __setattr__(self, name, value):
        # Если имя атрибута начинается с двойного подчеркивания, выбрасываем исключение
        if name.startswith('__'):
            raise AttributeError(f"Создание атрибута '{name}' запрещено")
        super().__setattr__(name, value)

moscowpython = Channel('UC-OVMPlMA3-YCIeg4z5z23A')

moscowpython.__channel_id = 'Новое название'

Вывод:

Traceback (most recent call last):
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 31, in <module>
    start(fakepyfile,mainpyfile)
  File "/data/user/0/ru.iiec.pydroid3/files/accomp_files/iiec_run/iiec_run.py", line 30, in start
    exec(open(mainpyfile).read(),  __main__.__dict__)
  File "<string>", line 18, in <module>
  File "<string>", line 9, in __setattr__
AttributeError: Создание атрибута '__channel_id' запрещено

[Program finished]

И соответственно, уже так ошибку можно отловить:

try:
    moscowpython.__channel_id = 'Новое название'
except AttributeError as e:
    print(e) 
→ Ссылка