Почему программа функционально в Python?

Так как корень находится в неснабженном префиксом пространстве имен, любой ребенок корня, который хочет быть un-namespaced, должен быть произведен как Ваш пример. Решение состояло бы в том, чтобы снабдить префиксом корневой элемент как так:

<w:root xmlns:w="whatever:name-space-1.0">
   <loner/>
</w:root>

код:

XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement( "w", "root", "whatever:name-space-1.0" );
doc.AppendChild( root );
root.AppendChild( doc.CreateElement( "loner" ) );
Console.WriteLine(doc.OuterXml);
35
задан Paul Hildebrandt 12 December 2009 в 04:46
поделиться

8 ответов

Edit: I've been taken to task in the comments (in part, it seems, by fanatics of FP in Python, but not exclusively) for not providing more explanations/examples, so, expanding the answer to supply some.

lambda, even more so map (and filter), and most especially reduce, are hardly ever the right tool for the job in Python, which is a strongly multi-paradigm language.

lambda main advantage (?) compared to the normal def statement is that it makes an anonymous function, while def gives the function a name -- and for that very dubious advantage you pay an enormous price (the function's body is limited to one expression, the resulting function object is not pickleable, the very lack of a name sometimes makes it much harder to understand a stack trace or otherwise debug a problem -- need I go on?!-).

Consider what's probably the single most idiotic idiom you sometimes see used in "Python" (Python with "scare quotes", because it's obviously not idiomatic Python -- it's a bad transliteration from idiomatic Scheme or the like, just like the more frequent overuse of OOP in Python is a bad transliteration from Java or the like):

inc = lambda x: x + 1

by assigning the lambda to a name, this approach immediately throws away the above-mentioned "advantage" -- and doesn't lose any of the DISadvantages! For example, inc doesn't know its name -- inc.__name__ is the useless string '' -- good luck understanding a stack trace with a few of these;-). The proper Python way to achieve the desired semantics in this simple case is, of course:

def inc(x): return x + 1

Now inc.__name__ is the string 'inc', as it clearly should be, and the object is pickleable -- the semantics are otherwise identical (in this simple case where the desired functionality fits comfortably in a simple expression -- def also makes it trivially easy to refactor if you need to temporarily or permanently insert statements such as print or raise, of course).

lambda is (part of) an expression while def is (part of) a statement -- that's the one bit of syntax sugar that makes people use lambda sometimes. Many FP enthusiasts (just as many OOP and procedural fans) dislike Python's reasonably strong distinction between expressions and statements (part of a general stance towards Command-Query Separation). Me, I think that when you use a language you're best off using it "with the grain" -- the way it was designed to be used -- rather than fighting against it; so I program Python in a Pythonic way, Scheme in a Schematic (;-) way, Fortran in a Fortesque (?) way, and so on:-).

Moving on to reduce -- one comment claims that reduce is the best way to compute the product of a list. Oh, really? Let's see...:

$ python -mtimeit -s'L=range(12,52)' 'reduce(lambda x,y: x*y, L, 1)'
100000 loops, best of 3: 18.3 usec per loop
$ python -mtimeit -s'L=range(12,52)' 'p=1' 'for x in L: p*=x'
100000 loops, best of 3: 10.5 usec per loop

so the simple, elementary, trivial loop is about twice as fast (as well as more concise) than the "best way" to perform the task?-) I guess the advantages of speed and conciseness must therefore make the trivial loop the "bestest" way, right?-)

By further sacrificing compactness and readability...:

$ python -mtimeit -s'import operator; L=range(12,52)' 'reduce(operator.mul, L, 1)'
100000 loops, best of 3: 10.7 usec per loop

...we can get almost back to the easily obtained performance of the simplest and most obvious, compact, and readable approach (the simple, elementary, trivial loop). This points out another problem with lambda, actually: performance! For sufficiently simple operations, such as multiplication, the overhead of a function call is quite significant compared to the actual operation being performed -- reduce (and map and filter) often forces you to insert such a function call where simple loops, list comprehensions, and generator expressions, allow the readability, compactness, and speed of in-line operations.

Perhaps even worse than the above-berated "assign a lambda to a name" anti-idiom is actually the following anti-idiom, e.g. to sort a list of strings by their lengths:

thelist.sort(key=lambda s: len(s))

instead of the obvious, readable, compact, speedier

thelist.sort(key=len)

Here, the use of lambda is doing nothing but inserting a level of indirection -- with no good effect whatsoever, and plenty of bad ones.

The motivation for using lambda is often to allow the use of map and filter instead of a vastly preferable loop or list comprehension that would let you do plain, normal computations in line; you still pay that "level of indirection", of course. It's not Pythonic to have to wonder "should I use a listcomp or a map here": just always use listcomps, when both appear applicable and you don't know which one to choose, on the basis of "there should be one, and preferably only one, obvious way to do something". You'll often write listcomps that could not be sensibly translated to a map (nested loops, if clauses, etc), while there's no call to map that can't be sensibly rewritten as a listcomp.

Perfectly proper functional approaches in Python often include list comprehensions, generator expressions, itertools, higher-order functions, first-order functions in various guises, closures, generators (and occasionally other kinds of iterators).

itertools, as a commenter pointed out, does include imap and ifilter: the difference is that, like all of itertools, these are stream-based (like map and filter builtins in Python 3, but differently from those builtins in Python 2). itertools offers a set of building blocks that compose well with each other, and splendid performance: especially if you find yourself potentially dealing with very long (or even unbounded!-) sequences, you owe it to yourself to become familiar with itertools -- their whole chapter in the docs makes for good reading, and the recipes in particular are quite instructive.

Writing your own higher-order functions is often useful, especially when they're suitable for use as decorators (both function decorators, as explained in that part of the docs, and class decorators, introduced in Python 2.6). Do remember to use functools.wraps on your function decorators (to keep the metadata of the function getting wrapped)!

So, summarizing...: anything you can code with lambda, map, and filter, you can code (more often than not advantageously) with def (named functions) and listcomps -- and usually moving up one notch to generators, generator expressions, or itertools, is even better. reduce meets the legal definition of "attractive nuisance"...: it's hardly ever the right tool for the job (that's why it's not a built-in any more in Python 3, at long last!-).

69
ответ дан 27 November 2019 в 06:24
поделиться

вопрос, который, кажется, здесь в основном игнорируется:

действительно ли функционально программирование Python помогает с параллелизмом?

Нет. Ценность FP для параллелизма заключается в устранении состояния в вычислениях, которое в конечном итоге отвечает за трудноуловимые непреднамеренные ошибки в параллельных вычислениях. Но это зависит от идиом параллельного программирования, которые сами по себе не имеют состояния, что не относится к Twisted. Если существуют идиомы параллелизма для Python, использующие программирование без сохранения состояния, я их не знаю.

9
ответ дан 27 November 2019 в 06:24
поделиться

FP is important not only for concurrency; in fact, there's virtually no concurrency in the canonical Python implementation (maybe 3.x changes that?). in any case, FP lends itself well to concurrency because it leads to programs with no or fewer (explicit) states. states are troublesome for a few reasons. one is that they make distributing the computation hard(er) (that's the concurrency argument), another, far more important in most cases, is the tendency to inflict bugs. the biggest source of bugs in contemporary software is variables (there's a close relationship between variables and states). FP may reduce the number of variables in a program: bugs squashed!

see how many bugs can you introduce by mixing the variables up in these versions:

def imperative(seq):
    p = 1
    for x in seq:
        p *= x
    return p

versus (warning, my.reduce's parameter list differs from that of python's reduce; rationale given later)

import operator as ops

def functional(seq):
    return my.reduce(ops.mul, 1, seq)

as you can see, it's a matter of fact that FP gives you fewer opportunities to shoot yourself in the foot with a variables-related bug.

also, readability: it may take a bit of training, but functional is way easier to read than imperative: you see reduce ("ok, it's reducing a sequence to a single value"), mul ("by multiplication"). wherease imperative has the generic form of a for cycle, peppered with variables and assignments. these for cycles all look the same, so to get an idea of what's going on in imperative, you need to read almost all of it.

then there's succintness and flexibility. you give me imperative and I tell you I like it, but want something to sum sequences as well. no problem, you say, and off you go, copy-pasting:

def imperative(seq):
    p = 1
    for x in seq:
        p *= x
    return p

def imperative2(seq):
    p = 0
    for x in seq:
        p += x
    return p

what can you do to reduce the duplication? well, if operators were values, you could do something like

def reduce(op, seq, init):
    rv = init
    for x in seq:
        rv = op(rv, x)
    return rv

def imperative(seq):
    return reduce(*, 1, seq)

def imperative2(seq):
    return reduce(+, 0, seq)

oh wait! operators provides operators that are values! but.. Alex Martelli condemned reduce already... looks like if you want to stay within the boundaries he suggests, you're doomed to copy-pasting plumbing code.

is the FP version any better? surely you'd need to copy-paste as well?

import operator as ops

def functional(seq):
    return my.reduce(ops.mul, 1, seq)

def functional2(seq):
    return my.reduce(ops.add, 0, seq)

well, that's just an artifact of the half-assed approach! abandoning the imperative def, you can contract both versions to

import functools as func, operator as ops

functional  = func.partial(my.reduce, ops.mul, 1)
functional2 = func.partial(my.reduce, ops.add, 0)

or even

import functools as func, operator as ops

reducer = func.partial(func.partial, my.reduce)
functional  = reducer(ops.mul, 1)
functional2 = reducer(ops.add, 0)

(func.partial is the reason for my.reduce)

what about runtime speed? yes, using FP in a language like Python will incur some overhead. here i'll just parrot what a few professors have to say about this:

  • premature optimization is the root of all evil.
  • most programs spend 80% of their runtime in 20% percent of their code.
  • profile, don't speculate!

I'm not very good at explaining things. Don't let me muddy the water too much, read the first half of the speech John Backus gave on the occasion of receiving the Turing Award in 1977. Quote:

5.1 A von Neumann Program for Inner Product

c := 0
for i := I step 1 until n do
 c := c + a[i] * b[i]

Several properties of this program are worth noting:

  1. Its statements operate on an invisible "state" according to complex rules.
  2. It is not hierarchical. Except for the right side of the assignment statement, it does not construct complex entities from simpler ones. (Larger programs, however, often do.)
  3. It is dynamic and repetitive. One must mentally execute it to understand it.
  4. It computes word-at-a-time by repetition (of the assignment) and by modification (of variable i).
  5. Part of the data, n, is in the program; thus it lacks generality and works only for vectors of length n.
  6. It names its arguments; it can only be used for vectors a and b. To become general, it requires a procedure declaration. These involve complex issues (e.g., call-by-name versus call-by-value).
  7. Its "housekeeping" operations are represented by symbols in scattered places (in the for statement and the subscripts in the assignment). This makes it impossible to consolidate housekeeping operations, the most common of all, into single, powerful, widely useful operators. Thus in programming those operations one must always start again at square one, writing "for i := ..." and "for j := ..." followed by assignment statements sprinkled with i's and j's.
24
ответ дан 27 November 2019 в 06:24
поделиться

Here's a short summary of positive answers when/why to program functionally.

  • List comprehensions were imported from Haskell, a FP language. They are Pythonic. I'd prefer to write
y = [i*2 for i in k if i % 3 == 0]

than to use an imperative construct (loop).

  • I'd use lambda when giving a complicated key to sort, like list.sort(key=lambda x: x.value.estimate())

  • It's cleaner to use higher-order functions than to write code using OOP's design patterns like visitor or abstract factory

  • People say that you should program Python in Python, C++ in C++ etc. That's true, but certainly you should be able to think in different ways at the same thing. If while writing a loop you know that you're really doing reducing (folding), then you'll be able to think on a higher level. That cleans your mind and helps to organize. Of course lower-level thinking is important too.

You should NOT overuse those features - there are many traps, see Alex Martelli's post. I'd subjectively say the most serious danger is that excessive use of those features will destroy readability of your code, which is a core attribute of Python.

7
ответ дан 27 November 2019 в 06:24
поделиться

Этот ответ полностью переработан. Он включает в себя множество наблюдений из других ответов.

Как видите, существует много сильных чувств, связанных с использованием конструкций функционального программирования в Python. Здесь можно выделить три основные группы идей.

Во-первых, почти все, кроме людей, наиболее приверженных чистейшему выражению функциональной парадигмы, согласны с тем, что понимание списков и генераторов лучше и яснее, чем использование map или фильтр . Ваши коллеги должны избегать использования map и filter , если вы ориентируетесь на версию Python, достаточно новую для поддержки понимания списков. И вам следует избегать itertools.imap и itertools. ifilter , если ваша версия Python достаточно нова для понимания генератора.

Во-вторых, в сообществе в целом существует много противоречий по поводу лямбда . Многих действительно раздражает синтаксис в дополнение к def для объявления функций, особенно тот, который включает ключевое слово вроде lambda с довольно странным именем. И людей также раздражает, что в этих небольших анонимных функциях отсутствуют какие-либо хорошие метаданные, описывающие любые другие функции. Это усложняет отладку. Наконец, небольшие функции, объявленные lambda , часто не очень эффективны, поскольку они требуют накладных расходов на вызов функции Python каждый раз, когда они вызываются, что часто происходит во внутреннем цикле.

Наконец, большинство (имеется в виду > 50%, но, скорее всего, не 90%) люди думают, что reduce немного странно и непонятно. Я сам признаю, что использую print reduce .__ doc __ всякий раз, когда я хочу его использовать, что не так уж и часто. Хотя, когда я вижу, что он используется, природа аргументов (т.е. функция, список или итератор, скаляр) говорит сама за себя.

Что касается меня, я попадаю в лагерь людей, которые считают функциональный стиль очень полезным. Но уравновешивает эту мысль тот факт, что Python по своей сути не является функциональным языком. А чрезмерное использование функциональных конструкций может сделать программы странно искаженными и трудными для понимания людьми.

Чтобы понять, когда и где функциональный стиль очень полезен и улучшает читаемость, рассмотрим эту функцию в C ++:

unsigned int factorial(unsigned int x)
{
    int fact = 1;
    for (int i = 2; i <= n; ++i) {
        fact *= i;
    }
    return fact
 }

Этот цикл кажется очень простым и легко понять. И в данном случае это так. Но его кажущаяся простота - ловушка для неосторожных. Рассмотрим этот альтернативный способ написания цикла:

unsigned int factorial(unsigned int n)
{
    int fact = 1;
    for (int i = 2; i <= n; i += 2) {
        fact *= i--;
    }
     return fact;
 }

Внезапно переменная управления циклом больше не изменяется очевидным образом. Вам остается только просмотреть код и тщательно обдумать, что происходит с переменной управления циклом. Этот пример немного патологичен, но есть примеры из реального мира, которые таковыми не являются. И проблема в том, что идея заключается в повторном присвоении существующей переменной. Вы не можете доверять тому, что значение переменной одинаково во всем теле цикла.

Это давно известная проблема, и в Python написание такого цикла довольно неестественно. Вы должны использовать цикл while, и это выглядит неправильно. Вместо этого в Python вы должны написать что-то вроде этого:

def factorial(n):
    fact = 1
    for i in xrange(2, n):
        fact = fact * i;
    return fact

Как видите, то, как вы говорите о переменной управления циклом в Python, не поддается обману внутри цикла. Это устраняет множество проблем с «умными» циклами в других императивных языках. К сожалению, эта идея частично заимствована из функциональных языков.

Даже это поддается странному возню. Например, этот цикл:

c = 1
for i in xrange(0, min(len(a), len(b))):
    c = c * (a[i] + b[i])
    if i < len(a):
        a[i + 1] = a[a + 1] + 1

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

И снова, более функциональный подход к спасению:

from itertools import izip
c = 1
for ai, bi in izip(a, b):
   c = c * (ai + bi)

Теперь, глядя на код, мы получаем явное указание (отчасти благодаря тому, что человек использует этот функциональный стиль), что списки a и b не изменяются во время выполнения цикла. Одной вещью для размышлений меньше.

Последнее, о чем следует беспокоиться, - это странные модификации c. Возможно, это глобальная переменная, которая модифицируется каким-то обходным вызовом функции. Чтобы спасти нас от этого ментального беспокойства, вот чисто функциональный подход:

from itertools import izip
c = reduce(lambda x, ab: x * (ab[0] + ab[1]), izip(a, b), 1)

Очень кратко, и структура говорит нам, что x - это просто аккумулятор. Это локальная переменная везде, где она появляется. Окончательный результат однозначно приписывается c. Теперь беспокоиться гораздо меньше. Структура кода устраняет несколько классов возможных ошибок.

Поэтому люди могут выбрать функциональный стиль. Лаконично и понятно, по крайней мере, если вы понимаете, что делают reduce и lambda . Есть большие классы проблем, которые могут поразить программу, написанную в более императивном стиле, который, как вы знаете, не повлияет на вашу программу функционального стиля.

В случае факториала есть очень простой и понятный способ написать эту функцию в Python в функциональном стиле:

import operator
def factorial(n):
    return reduce(operator.mul, xrange(2, n+1), 1)
15
ответ дан 27 November 2019 в 06:24
поделиться

Карта и фильтр имеют свое место в объектно-ориентированном программировании. Рядом со списком понимания и функций генератора.

Уменьшайте меньше. Алгоритм сокращения может быстро отнять больше времени, чем он того заслуживает; Если немного подумать, написанный вручную цикл сокращения будет более эффективным, чем цикл уменьшения, который применяет плохо продуманную функцию цикла к последовательности.

Лямбда никогда. Лямбда бесполезна. Можно привести аргумент, что он на самом деле что-то делает , поэтому он не полностью бесполезен . Во-первых: лямбда - это не синтаксический «сахар»; это делает вещи больше и уродливее. Во-вторых: один раз из 10 000 строк кода, которые думают, что вам нужна "анонимная" функция, превращается в два раза из 20 000 строк кода, что лишает анонимности значения, превращение его в обязательство по обслуживанию.

Однако.

Функциональный стиль программирования без изменения состояния объекта по-прежнему является объектно-ориентированным. Вы просто создаете больше объектов и меньше обновляете их. Когда вы начинаете использовать функции генератора, большая часть объектно-ориентированного программирования смещается в функциональном направлении.

Кажется, что каждое изменение состояния транслируется в функцию генератора, которая строит новый объект в новом состоянии из старого объекта (ов). Это интересное мировоззрение, потому что рассуждать об алгоритме намного проще.

Но это не призыв использовать сокращение или лямбда.

Кажется, что каждое изменение состояния транслируется в функцию генератора, которая создает новый объект в новом состоянии из старого объекта (ов). Это интересное мировоззрение, потому что рассуждать об алгоритме намного проще.

Но это не призыв использовать сокращение или лямбда.

Кажется, что каждое изменение состояния транслируется в функцию генератора, которая создает новый объект в новом состоянии из старого объекта (ов). Это интересное мировоззрение, потому что рассуждать об алгоритме намного проще.

Но это не призыв использовать сокращение или лямбда.

1
ответ дан 27 November 2019 в 06:24
поделиться

Я программирую на Python каждый день, и я должен сказать, что слишком большое «увлечение» ОО или функциональностью может привести к отсутствию элегантных решений. Я считаю, что обе парадигмы имеют свои преимущества для решения определенных проблем - и я думаю, что тогда вы знаете, какой подход использовать. Используйте функциональный подход, когда он оставляет вам чистое, удобочитаемое и эффективное решение. То же самое и с OO.

И это одна из причин, по которой я люблю Python - тот факт, что это мультипарадигма и позволяет разработчику выбирать, как решить свою проблему.

19
ответ дан 27 November 2019 в 06:24
поделиться

The standard functions filter(), map() and reduce() are used for various operations on a list and all of the three functions expect two arguments: A function and a list

We could define a separate function and use it as an argument to filter() etc., and its probably a good idea if that function is used several times, or if the function is too complex to be written in a single line. However, if it's needed only once and it's quite simple, it's more convenient to use a lambda construct to generate a (temporary) anonymous function and pass it to filter().

This helps in readability and compact code.

Using these function, would also turn out to be efficient, because the looping on the elements of the list is done in C, which is a little bit faster than looping in python.

And object oriented way is forcibly needed when states are to be maintained, apart from abstraction, grouping, etc., If the requirement is pretty simple, I would stick with functional than to Object Oriented programming.

2
ответ дан 27 November 2019 в 06:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: