В Python всё является ссылками, потому что так проще. При вызове функции, например __init__ не создаётся новый объект, как копия bill, а передаётся ссылка на него.
Потому что хочется связать именно объекты, а не сохранить ссылку на один аттрибут. У Bill могут быть другие методы (для примера пусть paste), которые Duck может вызывать так duck.bill.paste().
Перескажу своими словами, как я понимаю. Композиция - когда вы связываете один объект с другим, путём сохранения ссылки на него внутри этого объекта. Потом объект работает именно с этой ссылкой, а не с объектом снаружи. Связывание происходит при инициализации
def __init__(self, bill, tail):
self.bill = bill
self.tail = tail
сохраняются ссылки на переданные объекты в аттрибутах bill и tail самого объекта (self). Позже при работе с этими объектами к ним обращаются через self.bill и self.tail.
В этом коде явно опечатка, потому что в функции about() происходит обращение к внешним объектам с именами bill и tail, которых, например, может не быть в этой области видимости переменных. Но по удачному стечению обстоятельств они есть, и код выполняется успешно.
Я, к сожалению, никак не могу протестировать. А в документации API слишком много неточностей и нет примеров. TOKEN_ACCESS не надо прикреплять к photo_id, почитайте внимательно документацию к параметру photos в photos.getById. Покажите вывод print(event.attachments) без вызова всего остального кода.
Потому что хочется связать именно объекты, а не сохранить ссылку на один аттрибут. У Bill могут быть другие методы (для примера пусть paste), которые Duck может вызывать так duck.bill.paste().