Всем привет. В данной статье я попытаюсь ответить на вопросы и задания по Python с сайта pyobject.ru, а так же объяснить почему я так сделал. Сами вопросы доступны тут.
Disclaimer:
Предупреждаю сразу: пост написан нубом в питоне и программировании в общем. Если у вас будут уточнения и замечания (а они, я надеюсь, будут), просьба писать.
Ответы будут разбиты по тем же категориям, что и вопросы.
P.S. Питон у меня версии 2.6.6
Итак, начнем.
Типы данных, основные конструкции
Для удобства проверки создадим небольшой тестовый класс:
class Foo(object):
"""docstring for Foo"""
arg1 = None
_arg2 = None
__arg3 = None
def __init__(self):
super(Foo, self).__init__()
def bar(self):
pass
1. Как получить список всех атрибутов объекта
print dir(Foo)
2. Как получить список всех публичных атрибутов объекта
В Python для обозначения protected атрибутов используют "_", для private — "__" перед названием переменной. Следовательно, для получения списка только публичных атрибутов, список все атрибутов нужно отфильтровать. Сделать это можно или с помощью списковых выражений (list comprehension):
print [arg for arg in dir(Foo) if not arg.startswith('_')]
или воспользоваться функцией filter:
print filter(lambda x: not x.startswith('_'), dir(Foo))
Как по мне, то первый вариант является более предпочтительным по причине большей читабельности.
3. Как получить список методов объекта
Поскольку функции и методы в Python являются объектами первого рода (вроде правильно написал?), то для проверки будем использовать функцию getattr, которая возвращает сам аттрибут объекта и функцию callable, которая и осуществляет проверку.
print [arg for arg in dir(Foo) if callable(getattr(Foo, arg))]
# или
print filter(lambda arg: callable(getattr(Foo, arg)), dir(Foo))
4. В какой «магической» переменной хранится содержимое help?
В атрибуте __doc__. В данную переменную заносится комментарий сразу после
объявления класса/метода/функции (см. тестовый класс).
print Foo.__doc__
Так же можно воспользоваться функцией help в интерактивном режиме:
>>> help(int)
5. Есть два кортежа, получить третий как конкатенацию первых двух
print (2, 5) + (4, 6)
6. Есть два кортежа, получить третий как объединение уникальных элементов первых двух кортежей
В данном задании я видел 2 подхода:
1. писать циклы для проверки вхождения элемента в кортежи
2. воспользоваться встроенным типом set (по сути — хеш), над которым можно применять логические операции.
Решение с использованием второго подхода (используется XOR):
print tuple(set((2, 3, 4)) ^ set((4, 5)))
7. Почему если в цикле меняется список, то используется for x in lst[:], что означает [:]?
[:] — обозначение среза в питоне. Про них можно почитать, например, тут. В кратце: [:] создает копию lst и изменения в первом никак не влияют на итерацию по исходным значениям.
8. Есть два списка одинаковой длины, в одном ключи, в другом значения. Составить словарь.
Будем использовать функцию zip, которая делает кортежи из пары значений и dict, которая создает словарь из переданных аргументов.
a = ('John', 'Peter')
b = (1, 2)
print dict(zip(a, b))
9. Есть два списка разной длины, в одном ключи, в другом значения. Составить словарь. Для ключей, для которых нет значений использовать None в качестве значения. Значения, для которых нет ключей игнорировать.
a = ('John', 'Peter', 'Daniel')
b = (1, 2)
print dict((len(a) > len(b)) and map(None, a, b) or zip(a, b))
# или вариант с if/else, введенный в Python 2.5
print dict(map(None, a, b) if (len(a) > len(b)) else zip(a, b))
В данном случае будем использовать функции zip, map. Особенностью zip является то, что возвращаемый результат ограничен самым коротким итерируемым. То есть это прекрасно подходит нам для случая, когда значений больше чем ключей. Во второй ветке python у map есть одна документированная особенность, а именно — если какое-либо из итерируемых значений короче других, оно дополняется с помощью None. Если вместо функции передано None, выполняется объединение и на выходе мы получаем те же кортежи.
Как вариант, можно рассмотреть использование функции itertools.izip_longest, которая была добавлена в 2.6.
10.Есть словарь. Инвертировать его. Т.е. пары ключ: значение поменять местами — значение: ключ.
a = {'John': 1, 'Peter': 2}
print dict((val, key) for (key, val) in a.iteritems())
Как вариант — опять использовать функцию zip.
a = {'John': 1, 'Peter': 2}
print dict(zip(a.itervalues(), a.iterkeys()))
P.S. прошу гуру подсказать, что правильней использовать в данном случае — zip или itertools.izip. Тоже самое относится и к values/itervalues.
11. Есть строка в юникоде, получить 8-битную строку в кодировке utf-8 и cp1251
Писал прямо во время написания статьи, но если правильно понял задание, то:
greeting = u'Привет'
print greeting.encode('utf-8')
print greeting.encode('cp1251')
12. Есть строка в кодировке cp1251, получить юникодную строку
Аналогично:
greeting = 'Привет'
print greeting.decode('cp1251')
Функции
1. Написать функцию, которой можно передавать аргументы либо списком/кортежем, либо по одному. Функция производит суммирование всех аргументов.
Решение вижу следующим: итерация по переданному списку. В случае если элемент списка — имеет атрибут __len__, а значит является итерируемым, разворачиваем его и передаем в нашу функцию.
def sum(*args):
"""
Returns sum of the args.
>>> sum(1)
1
>>> sum(2, 2)
4
>>> sum(1, 2, (3, 4))
10
>>> sum(1, ())
1
>>> sum(-1, 1.0)
0.0
"""
result = 0
for arg in args:
if hasattr(arg, '__len__'):
result += sum(*arg)
else:
result += arg
return result
2. Написать функцию-фабрику, которая будет возвращать функцию сложения с аргументом.
Так как функции являются объектами первого рода (если лажать, так по-крупному), то одними из вариантов их использования являются возврат их из других функций или методов и передача в качестве в аргументов. Суть сводится к тому, что мы должны вернуть функцию сложения, один из аргументов
которой задан при ее создании, а второй может варьироваться. Как я понимаю, это — замыкание — доступ к переменным, объявленным вне тела функции.
def addition_inner(val):
"""
>>> add5 = addition_inner(5)
>>> add5(3)
8
>>> add1 = addition_inner(-1)
>>> add1(3)
2
>>> add_str = addition_inner('s')
>>> add_str(3)
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'int' and 'str'
"""
def func(arg):
return arg + val
return func
def addition_lambda(val):
"""
>>> add5 = addition_lambda(5)
>>> add5(3)
8
>>> add1 = addition_lambda(-1)
>>> add1(3)
2
>>> add_str = addition_lambda('s')
>>> add_str(3)
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +: 'int' and 'str'
"""
return lambda x: x + val
3. Написать фабрику, аналогичную п.2, но возвращающей список таких функций
В данном случае есть смысл возвращать список функций, которые мы написали выше.
Выглядеть будет примерно так:
def additions(start, end):
"""
Returns list of addition functions
>>> for addition in additions(0, 5):
... print addition(1)
1
2
3
4
5
>>> print additions(5, 0)
[]
>>> print additions(0, 2) # doctest: +ELLIPSIS
[<function func at 0x...>, <function func at 0x...>]
"""
return [addition_inner(val) for val in range(start, end)]
4. Написать аналог map:
— первым аргументом идет либо функция, либо список функций
— вторым аргументом — список аргументов, которые будут переданы функциям
— полагается, что эти функции — функции одного аргумента
Данное задание я реализовал с использованием встроенного map (как вариант — заменен циклом). Так же, для проверки типа переданного значения использовал функцию isinstance и модуль collections, как аналог hasattr и магическому методу __len__.
def mymap(func, args):
"""
Applies list of functions for args.
>>> add0 = addition_inner(0)
>>> add1 = addition_inner(1)
>>> add2 = addition_inner(2)
>>> add5 = addition_inner(5)
>>> print mymap(add5, [1, 2, 3])
[6, 7, 8]
>>> print mymap([add0, add1, add2], [1, 2, 3])
[(1, 2, 3), (2, 3, 4), (3, 4, 5)]
>>> print mymap([], [])
[]
>>> print mymap(sum, [(3, 4, 5, 6, (7,))])
[25]
"""
if isinstance(func, collections.Iterable):
return [tuple(map(f, args)) for f in func]
# или
#return [tuple(f(arg) for arg in args) for f in func]
else:
return [func(arg) for arg in args]
Итераторы
Маленькое отступление. Я не видел особого смысла писать код итераторов, т.к.
их код описан в документации к модулю itertools. Врядли я напишу лучше.
Итераторы основываются на генераторах, о которых есть прекрасная статья.
Модули
1. У нас есть импортированный модуль foo, как узнать физический путь файла, откуда он импортирован?
Путь хранится в аттрибуте __file__ модуля.
2. Из модуля foo вы импортируете модуль feedparser. Версия X feedparser'а есть в общесистемном каталоге site-packages, версия Y — рядом с модулем foo. Определена переменная окружения PYTHONPATH, и там тоже есть feedparser, версии Z. Какая версия будет использоваться?
Будет импортирована версия Y.
Согласно документации (6 раздел туториала), порядок импорта следующий:
1. директория рядом со скриптом, который был запущен
2. PYTHONPATH
3. системный каталог
3. Как посмотреть список каталогов, в которых Python ищет модули?
>>> import sys
>>> sys.path
4. У вас есть модуль foo, внутри него импортируется модуль bar. Рядом с модулем foo есть файлы bar.py и bar/__init__.py Какой модуль будет использоваться.
Будет использован второй, т.е. пакет. Как я понял, происходит рекурсивный обход директорий, и пакеты импортируются первыми. Буду благодарен за уточнение.
5. Что означает и для чего используется конструкция __name__ == '__main__'
Используется для определения был ли файл импортирован или запущен. Если мы его запускаем, то значение будет __main__, если импортируем — имя модуля.
Автор: gagoman