Дзен Python заявляє, що робити лише один спосіб - але часто я стикаюся з проблемою вирішення питання про те, коли використовувати функцію, а не коли використовувати метод.
Візьмемо тривіальний приклад - об’єкт ChessBoard. Скажімо, нам потрібен певний спосіб отримати всі законні кроки короля, доступні на дошці. Чи пишемо ми ChessBoard.get_king_moves () чи get_king_moves (chess_board)?
Ось кілька пов’язаних питань, які я подивився:
- Чому пітон використовує "магічні методи"?
- Чи є причина, що для рядків Python немає методу довжини рядка?
Відповіді, які я отримав, були значною мірою непереконливими:
Чому Python використовує методи для деяких функцій (наприклад, list.index ()), але функціонує для інших (наприклад, len (список))?
Основна причина - історія. Функції використовувались для тих операцій, які були загальними для групи типів і призначені для роботи навіть для об'єктів, у яких взагалі не було методів (наприклад, кортежі). Також зручно мати функцію, яку можна легко застосувати до аморфної колекції об’єктів, коли ви використовуєте функціональні функції Python (map (), apply () та ін.).
Насправді реалізація len (), max (), min () як вбудованої функції насправді менше коду, ніж реалізація їх як методів для кожного типу. Можна посперечатися щодо окремих випадків, але це частина Python, і зараз зробити такі фундаментальні зміни вже пізно. Функції повинні залишатися, щоб уникнути масового зламу коду.
Хоча цікаво, вище сказане насправді не говорить про те, яку стратегію слід прийняти.
Це одна з причин - за допомогою власних методів розробники можуть вільно обирати іншу назву методу, наприклад, getLength (), length (), getlength () чи що завгодно. Python застосовує суворе іменування, щоб можна було використовувати загальну функцію len ().
Трохи цікавіше. Я вважаю, що функції в певному розумінні є пітонічною версією інтерфейсів.
Нарешті, від самого Гвідо :
Якщо говорити про здібності / інтерфейси, то змусило мене задуматися про деякі наші "негідні" назви спеціальних методів. У мовній довідці йдеться: "Клас може реалізувати певні операції, які викликаються спеціальним синтаксисом (наприклад, арифметичні операції або підписка та розрізання) шляхом визначення методів зі спеціальними іменами". Але є всі ці методи зі спеціальними іменами на кшталт
__len__
або__unicode__
які, здається, передбачені для вигоди вбудованих функцій, а не для підтримки синтаксису. Імовірно, в Python на основі інтерфейсу ці методи перетвориться на регулярно названі методи на ABC, так що__len__
це станеclass container: ... def len(self): raise NotImplemented
Хоча, обмірковуючи це ще раз, я не бачу, чому всі синтаксичні операції не просто викликатимуть відповідний нормально названий метод на певному ABC.
<
Наприклад, " ", імовірно, може викликати "object.lessthan
" (або, можливо, "comparable.lessthan
"). Таким чином, ще однією перевагою була б здатність відхилити Python від цієї дивної дивацтва, що мені здається поліпшенням HCI .Гм. Я не впевнений, що згоден (цифра, що :-).
Є два біти "обгрунтування Python", які я хотів би пояснити спочатку.
Перш за все, я вибрав len (x) над x.len () з причин HCI (
def __len__()
прийшов набагато пізніше). Насправді є дві взаємопов'язані причини, обидві ІСІ:(a) Для деяких операцій позначення префікса просто читається краще, ніж постфікс - операції з префіксом (і інфіксом!) мають давню традицію в математиці, яка подобається нотаціям, коли візуальні матеріали допомагають математику думати про проблему. Порівняйте легкий , з яким ми перепишемо формулу , як
x*(a+b)
вx*a + x*b
до незграбності робити те ж саме з використанням вихідного OO позначення.(b) Коли я читаю код, який говорить,
len(x)
я знаю, що він запитує про довжину чогось. Це говорить мені про дві речі: результат - ціле число, а аргумент - якийсь контейнер. Навпаки, коли я читаюx.len()
, я вже повинен знати, щоx
це якийсь контейнер, що реалізує інтерфейс або успадковує клас, у якого є стандартlen()
. Зауважте, що ми час від часу виникаємо плутанини, коли у класу, який не реалізує відображення, є методget()
чиkeys()
метод, або щось, що не є файлом, маєwrite()
метод.Говорячи те ж саме по-іншому, я бачу 'len' як вбудовану операцію . Мені б не хотілося цього втрачати. Я не можу точно сказати, чи ти це мав на увазі чи ні, але "def len (self): ...", безумовно, звучить так, що ти хочеш знищити його звичайним методом. Я на цьому рішуче -1.
Другий біт обґрунтування Python, який я пообіцяв пояснити, є причиною, чому я обрав спеціальні методи, щоб виглядати,
__special__
а не простоspecial
. Я передбачив безліч операцій, які класи можуть захотіти переосмислити, деякі стандартні (наприклад,__add__
або__getitem__
), деякі не такі стандартні (наприклад, підберезники__reduce__
довгий час взагалі не підтримували код C). Я не хотів, щоб ці спеціальні операції використовували звичайні назви методів, тому що тоді вже існуючі класи або класи, написані користувачами без енциклопедичної пам'яті для всіх спеціальних методів, могли б випадково визначити операції, які вони не мали на увазі здійснити , з можливими катастрофічними наслідками. Іван Крстіч пояснив це більш лаконічно у своєму повідомленні, яке надійшло після того, як я все це написав.- - Гуїдо ван Россум (домашня сторінка: http://www.python.org/~guido/ )
Я розумію це в тому, що в деяких випадках позначення префікса просто має більше сенсу (тобто, Duck.quack має більше сенсу, ніж квак (Duck) з мовної точки зору.), І знову ж таки, функції дозволяють "інтерфейси".
У такому випадку, я думаю, було б реалізувати get_king_moves виходячи виключно з першої точки Гуйдо. Але це все ще залишає багато відкритих питань щодо скажімо, реалізація класу стеків та черг з подібними методами push та pop - чи це вони повинні бути функціями чи методами? (тут я б здогадався про функції, тому що мені дуже хочеться сигналізувати про push-pop інтерфейс)
TLDR: Чи може хтось пояснити, якою має бути стратегія вирішення питання щодо використання функцій проти методів?
X.frob
чиX.__frob__
вільно стоятьfrob
.