Новичок задавался вопросом, является ли его код "Pythonic"
Это действительно первое, что я написал на python. Я исхожу из фона Java. Я не хочу просто узнать, как программировать Java-код с синтаксисом Python. Я хочу научиться программировать в питонической парадигме.
Не могли бы вы, ребята, прокомментировать, как я могу сделать следующий код более pythonic?
from math import sqrt
# recursively computes the factors of a number
def factors(num):
factorList = []
numroot = int(sqrt(num)) + 1
numleft = num
# brute force divide the number until you find a factor
for i in range(2, numroot):
if num % i == 0:
# if we found a factor, add it to the list and compute the remainder
factorList.append(i)
numleft = num / i
break
# if we didn't find a factor, get out of here!
if numleft == num:
factorList.append(num)
return factorList
# now recursively find the rest of the factors
restFactors = factors(numleft)
factorList.extend(restFactors)
return factorList
# grabs all of the twos in the list and puts them into 2 ^ x form
def transformFactorList(factorList):
num2s = 0
# remove all twos, counting them as we go
while 2 in factorList:
factorList.remove(2)
num2s += 1
# simply return the list with the 2 back in the right spot
if num2s == 0: return factorList
if num2s == 1:
factorList.insert(0, 2)
return factorList
factorList.insert(0, '2 ^ ' + str(num2s))
return factorList
print transformFactorList(factors(#some number))
Ответы
Ответ 1
Существует превосходный праймер Дэвида Гудгера, называемый "Code Like a Pythonista" здесь. Несколько вещей из этого переименования текста (цитирование):
-
joined_lower
для функций, методов,
атрибуты
-
joined_lower
или ALL_CAPS для
Константы
-
StudlyCaps
для классов
-
camelCase
только для соответствия
ранее существовавшие соглашения
Ответ 2
Просто используйте 'import math' и 'math.sqrt()' вместо 'from math import sqrt' и 'sqrt()'; вы ничего не выигрываете, просто импортируя "sqrt", а код быстро становится громоздким со слишком большим количеством из-импортов. Кроме того, такие вещи, как reload() и издевательство над тестами, ломаются намного быстрее при многократном использовании импорта.
Функция divmod() является удобным способом выполнения как деления, так и по модулю. Вы можете использовать для /else вместо отдельной проверки на numleft. Функция ваших факторов является естественным кандидатом на генератор. xrange() уже упоминался в другом ответе. Здесь все сделано так:
import math
# recursively computes the factors of a number as a generator
def factors(num):
numroot = int(math.sqrt(num)) + 1
# brute force divide the number until you find a factor
for i in xrange(2, numroot):
divider, remainder = divmod(num, i)
if not remainder:
# if we found a factor, add it to the list and compute the
# remainder
yield i
break
else:
# if we didn't find a factor, get out of here!
yield num
return
# now recursively find the rest of the factors
for factor in factors(divider):
yield factor
Использование генератора означает, что вы можете только перебирать результат один раз; если вы просто хотите список (например, вы делаете в translateFactorsList), вам придется обернуть вызов факторам() в списке().
Ответ 3
Еще одна вещь, которую вы, возможно, захотите посмотреть, - это docstring. Например, комментарий для этой функции:
# recursively computes the factors of a number
def factors(num):
Может быть преобразован в это:
def factors(num):
""" recursively computes the factors of a number"""
Это не на самом деле 100% необходимо сделать так, но это хорошая привычка вступать, если вы когда-нибудь начнете использовать что-то по строкам pydoc.
Вы также можете сделать это:
docstring.py
"""This is a docstring"""
в командной строке:
>>> import docstring
>>> help(docstring)
результаты:
Help on module docstring:
NAME
docstring - This is a docstring
FILE
/Users/jason/docstring.py
Ответ 4
Несколько комментариев:
- Я бы заменил
range()
на xrange()
; когда вы вызываете range()
, он выделяет весь диапазон одновременно, тогда как при повторении итерации по xrange()
он возвращает каждый результат по одному, сохраняя память.
- Не помещайте выражения после условных обозначений в одну строку (
if num2s -- 0: return factorList
). Это затрудняет обзор с первого взгляда, что он делает (это блок).
- Не бойтесь использовать модули. В модуле
[sympy][1]
уже есть код для вычисления факторов, который может упростить ваш код, исключив большую часть его.
- Форматирование строки Python прост и эффективен.
Например:
factorList.insert(0, '2 ^ ' + str(num2s))
можно изменить на
factorlist.insert(0, '2 ^ %s' % num2s)
В целом, я не нахожу, что ваш код сильно не-pythonic. Просто убедитесь, что вы хотите использовать разделение полов, потому что это то, что обычно происходит по умолчанию с целыми значениями. В противном случае вам нужно будет исправить оператор деления:
from __future__ import division
Иногда - разочарование в отношении языка.
Ответ 5
from itertools import takewhile
def transform_factor_list(factor_list):
num_2s = len(list(takewhile(lambda e: e == 2, factor_list)))
if num_2s > 1:
factor_list[:num_2s] = ["2 ^ %i" % (num_2s, )]
return factor_list
Что бы я сделал из второй функции.
Большинство pythonic изменений:
- PEP-8 совместимое именование
- slicing (и присвоение срезам)
- итераторы
- форматирование строк
Функция предполагает, что вход упорядочен, что выполняется факторами.
Изменить: удалены специальные случаи для некоторых списков, более компактные таким образом
Ответ 6
На основе ответа на крис слегка упрощается:
- вместо внешнего
- внутренний, чтобы сохранить возможность повторного использования одного и того же делителя.
- использовать itertools.groupby упрощает compress() намного
- исправить небольшую ошибку в tostring()
НТН:
import itertools
def factorize(n):
# ideally an iterator of prime numbers
# this'll work though
divisors = itertools.count(2)
for divisor in divisors:
# This condition is very clever!
# Note that `n` is decreasing, while `divisor` is increasing.
# And we know that `n` is not divisible by anything smaller,
# so this stops as soon as the remaining `n` is obviously prime.
if divisor**2 > n:
yield n
break
while n % divisor == 0:
yield divisor
n //= divisor
def compress(factors):
for (factor, copies) in itertools.groupby(factors):
# The second object yielded by groupby is a generator of equal factors.
# Using list() to count its length.
power = len(list(copies))
yield (factor, power)
def tostring(compressed):
return ' * '.join("%d**%d" % (factor, power) for (factor, power) in compressed)
# test
assert tostring(compress(factorize(12))) == '2**2 * 3**1'
Ответ 7
Не бойтесь понимания списков. Переключение с Java на Python и обнаружение их было хорошим днем.
Для функции факторов может быть что-то вроде этого:
def factors(num):
return [i for i in xrange(1, num+1) if num % i == 0]
Вероятно, это не лучший код, но он короткий и понятный.
Удачи с Python, это отличный язык.
Ответ 8
вот как я это сделаю...
import itertools
import collections
def factorize(n):
# ideally an iterator of prime numbers
# this'll work though
divisors = itertools.count(2)
divisor = divisors.next()
while True:
if divisor**2 > n:
yield n
break
a,b = divmod(n, divisor)
if b == 0:
yield divisor
n = a
else:
divisor = divisors.next()
def compress(factors):
summands = collections.defaultdict(lambda: 0)
for factor in factors:
summands[factor] += 1
return [(base, summands[base]) for base in sorted(summands)]
def tostring(compressed):
return ' * '.join("%d**%d" % factor for factor in compressed)
Ответ 9
Вот что выпрыгивает на меня:
def transformFactorList(factorList):
oldsize = len(factorList)
factorList = [f for f in factorList if f != 2]
num2s = oldsize - len(factorList)
if num2s == 0:
return []
if num2s == 1:
return [2]+factorList
return ['2 ^ %s' % num2s] + [factorList]
Форма [f for f in factorList if f != 2]
называется пониманием списка.
Ответ 10
Поскольку этот пост, кажется, воскрешен Кейси (lol), я добавлю свои 2 цента.
Перейдите все в PEP-8. Это помогло мне существенно, когда у меня были проблемы с форматированием кода.
Ответ 11
Я бы воспользовался списком, чтобы получить два раза:
def transformFactorList(factorList):
twos = [x for x in factorList if x == 2]
rest = [x for x in factorList if x != 2]
rest.insert(0, "2 ^ %d" % len(twos))
return rest
Обратите внимание, что это даст вам 2^0
и 2^1
, которые ваш код не сделал. То, что вы делаете с twos, кажется арбитражным (иногда вы получаете строку, иногда число, иногда ничего), поэтому я решил, что все будет хорошо. Вы можете легко изменить это, если хотите:
def transformFactorList(factorList):
twos = [x for x in factorList if x == 2]
rest = [x for x in factorList if x != 2]
if twos:
rest.insert(0, 2 if len(twos)==1 else "2 ^ %d" % len(twos))
return rest
Ответ 12
Использование рекурсии (где не обязательно) не является питоновой. У Python нет исключения рекурсии хвоста, а плоскость лучше, чем вложенная.
Если вы сомневаетесь, попробуйте import this
update: по популярному запросу здесь идет итеративная факторизация (вздох):
"""returns an iterator of tuples (factor, power) such that
reduce(operator.mul, (factor**power for factor, power in factors(n))) == n """
def factors(n):
i = 2
while n > 1:
p = 0
while n > 1 and n % i == 0:
p += 1
n /= i
if p:
yield (i, p)
i += 1