Навіщо зберігати функцію всередині словника python?


68

Я початківець пітон, і я просто навчився техніці, що включає словники та функції. Синтаксис простий і здається, що це банальна річ, але мої відчуття пітону поколюються. Щось підказує мені, що це глибока і дуже пітонічна концепція, і я не зовсім розумію її значення. Чи може хтось поставити ім’я цій техніці і пояснити, як / чому це корисно?


Ця техніка полягає в тому, що у вас є словник python і функція, яку ви збираєтеся використовувати в ньому. Ви вставляєте додатковий елемент у дікт, значенням якого є назва функції. Коли ви готові зателефонувати в функцію, ви посилаєте виклик опосередковано , посилаючись на елемент dict, а не на функцію по імені.

Приклад, з якого я працюю, - з Learn Python The Hard Way, 2nd Ed. (Ця версія доступна під час реєстрації через Udemy.com ; на жаль, в даний час безкоштовна версія HTML в даний час є Ed 3, і більше не включає цей приклад).

Перефразовуючи:

# make a dictionary of US states and major cities
cities = {'San Diego':'CA', 'New York':'NY', 'Detroit':'MI'}

# define a function to use on such a dictionary
def find_city (map, city):
    # does something, returns some value
    if city in map:
        return map[city]
    else:
        return "Not found"

# then add a final dict element that refers to the function
cities['_found'] = find_city

Тоді наступні вирази рівнозначні. Ви можете викликати функцію безпосередньо або посилаючись на елемент dict, значенням якого є функція.

>>> find_city (cities, 'New York')
NY

>>> cities['_found'](cities, 'New York')
NY

Чи може хтось пояснити, що це за мовна особливість, і, можливо, де мова йде про «справжнє» програмування? Цієї іграшкової вправи було достатньо, щоб навчити мене синтаксису, але не вела мене туди.


13
Чому ця публікація буде поза темою? Це чудове питання щодо алгоритму та структури даних!
Martijn Pieters

Я бачив (і робив) деякі подібні речі іншими мовами. Ви могли б виглядати на це як оператор перемикання, але добре загорнувшись у прохідний об'єкт із часом пошуку O (1).
KChaloux

1
У мене була думка, що було щось важливе і самореференційне щодо включення функції у власний диктант ... див. Відповідь @ dietbuddha ... але, можливо, ні?
mdeutschmtl

Відповіді:


83

За допомогою диктату давайте переведемо ключ на дзвінок. Ключ не повинен бути жорстко закодований, як у вашому прикладі.

Зазвичай це форма відправки абонента, де ви використовуєте значення змінної для підключення до функції. Скажімо, мережевий процес надсилає вам коди команд, диспетчеризація дозволяє легко переводити коди команд у виконуваний код:

def do_ping(self, arg):
    return 'Pong, {0}!'.format(arg)

def do_ls(self, arg):
    return '\n'.join(os.listdir(arg))

dispatch = {
    'ping': do_ping,
    'ls': do_ls,
}

def process_network_command(command, arg):
    send(dispatch[command](arg))

Зауважте, що функція, яку ми зараз називаємо, повністю залежить від значення command. Ключ також не повинен відповідати; це навіть не повинно бути рядком, ви можете використовувати все, що можна використовувати як ключ, і підходить для вашої конкретної програми.

Використання методу відправки безпечніше інших методів, наприклад eval(), оскільки воно обмежує команди, дозволені тим, що ви визначили заздалегідь. ls)"; DROP TABLE Students; --Наприклад, жоден зловмисник не збирається проникнути ін'єкцію повз диспетчерський стіл.


5
@Martjin - Чи не можна це назвати реалізацією "Шаблону команд" у такому випадку? Здається, це концепція, яку OP намагається зрозуміти?
Кандидат

3
@PhD: Так, я побудував приклад - реалізація командного шаблону; то dictдіє як диспетчер (менеджер команди, ініціатор виклику, і т.д.).
Martijn Pieters

Чудове пояснення вищого рівня, @Martijn, дякую. Я думаю, що я отримую ідею "відправки".
mdeutschmtl

28

@Martijn Pieters добре зробив пояснення техніки, але я хотів щось уточнити з вашого питання.

Важливо знати, що ви НЕ зберігаєте "ім'я функції" у словнику. Ви зберігаєте посилання на саму функцію. Це можна побачити за допомогою printфункції на.

>>> def f():
...   print 1
... 
>>> print f
<function f at 0xb721c1b4>

fце лише змінна, що посилається на визначену вами функцію. Використання словника дозволяє групувати подібні речі, але це не відрізняється від призначення функції іншій змінній.

>>> a = f
>>> a
<function f at 0xb721c3ac>
>>> a()
1

Аналогічно, ви можете передавати функцію як аргумент.

>>> def c(func):
...   func()
... 
>>> c(f)
1

5
Згадка про функцію першого класу, безумовно, допоможе :-)
Флоріан Маргайн

7

Зауважте, що клас Python - це справді лише синтаксичний цукор для словника. Коли ви робите:

class Foo(object):
    def find_city(self, city):
        ...

коли ви телефонуєте

f = Foo()
f.find_city('bar')

насправді так само, як:

getattr(f, 'find_city')('bar')

яка за роздільною здатністю імені точно така сама, як:

f.__class__.__dict__['find_city'](f, 'bar')

Один корисний прийом - це зіставлення вводу користувача на зворотній зв'язок. Наприклад:

def cb1(...): 
    ...
funcs = {
    'cb1': cb1,
    ...
}
while True:
    input = raw_input()
    funcs[input]()

Це також можна записати в класі:

class Funcs(object):
    def cb1(self, a): 
        ...
funcs = Funcs()
while True:
    input = raw_input()
    getattr(funcs, input)()

який синтаксис зворотного виклику краще залежить від конкретного додатку та смаку програміста. Перший - більш функціональний стиль, другий - більш об’єктно орієнтований. Перші можуть відчувати себе більш природними, якщо вам потрібно буде динамічно змінювати записи у словнику функцій (можливо, спираючись на введення користувача); останні можуть відчувати себе більш природними, якщо у вас є набір різних попередньо встановлених карт, які можна вибирати динамічно.


Я думаю, що ця взаємозамінність - це те, що змусило мене думати «пітонічно», те, що те, що ти бачиш на поверхні, є лише звичайним способом представити щось набагато глибше. Можливо, це не особливо для python, хоча вправи python (і програмісти python?), Схоже, дуже багато говорять про особливості мови.
mdeutschmtl

Ще одна думка: чи є щось особливе для python у тому, як він готовий оцінити те, що схоже на два "терміни", що просто сидять поруч один з одним, посилання на дикт і список аргументів? Чи дозволяють це інші мови? Це свого роду програмування еквівалент стрибка в алгебрі від 5 * xдо 5x(вибачте просту аналогію).
mdeutschmtl

@mdeutschmtl: це не зовсім унікально для Python, хоча мови, у яких відсутня функція першого класу або об'єкт функції, ніколи не можуть мати жодних ситуацій, коли доступ до словника, що супроводжується викликом функції, може бути можливим.
Лі Лі Раян

2
@mdeutschmtl "той факт, що те, що ти бачиш на поверхні, - це лише звичайний спосіб представити щось набагато глибше". - це називається синтаксичним цукром і існує всюди
Ізката

6

Є дві методики, які пригадують мені на думку, про які ви можете посилатися, і жодна з них не є пітонічною, оскільки вона є більш широкою, ніж одна мова.

1. Техніка приховування / інкапсуляції інформації та згуртованості (зазвичай вони йдуть рука об руку, тому я їх збираю разом).

У вас є об'єкт, у якого є дані, і ви приєднуєте метод (поведінку), який сильно поєднується з даними. Якщо вам потрібно змінити функцію, розширити функціональність або внести будь-які інші зміни, абонентам не потрібно буде змінювати (якщо припускати, що додаткові дані не потрібно передавати).

2. Таблиці розсилки

Не класичний випадок, тому що є лише один запис із функцією. Однак диспетчерські таблиці використовуються для організації різної поведінки за ключем, таким чином, щоб вони могли дивитись вгору і викликати динамічно. Я не впевнений, чи ви думаєте про це, оскільки не динамічно посилаєтесь на функцію, але тим не менше ви все одно отримуєте ефективну пізню прив'язку («непрямий» дзвінок).

Компроміси

Варто зазначити, що ваша робота буде добре працювати з відомим простором імен клавіш. Однак ви ризикуєте зіткнутись між даними та функціями з невідомим простором імен ключів.


Інкапсуляція, оскільки дані та функції, що зберігаються в диктаті (іонарі), пов'язані між собою і, таким чином, мають згуртованість. Дані та функції походять від двох дуже різних доменних мереж, тому на перший погляд приклад, здається, згуртовує дико розрізнені сутності.
ChuckCottrill

0

Я розміщую це рішення, яке, на мою думку, є досить загальним і може бути корисним, оскільки воно просто і легко адаптується до конкретних випадків:

def p(what):
    print 'playing', cmpsr

def l(what):
    print 'listening', cmpsr

actions = {'Play' : p, 'Listen' : l}

act = 'Listen'
cmpsr = 'Vivaldi'

actions[act].__call__(cmpsr)

можна також визначити список, де кожен елемент є об’єктом функції, і використовувати __call__вбудований метод. Подяки всім за натхнення та співпрацю.

"Великий художник - спрощувач", Анрі Фредерік Ам'єль

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.