Архив рубрики: Python

Python использование set с классом

Задача: создадим класс, создадим пустой set. Заполним set экземплярами созданного класса. Для примера пусть экземпляры будут случайным целым числом из диапазона 0-9. Теперь мы хотим удалить из set-а все экземпляры класса значения которых совпадают с «9» и «8»

Где это можно применить на практике? В gamedeveloping игра «Астероид», в set добавляем все летающие по экрану камни, пробегаемся по set-у и сверяем дистанцию между кораблем и камнями:
для этого вычисляем дистанцию между координатами центров фигур
>>> def dist(p, q):
>>>     return math.sqrt((p[0] — q[0]) ** 2 + (p[1] — q[1]) ** 2)
и вычитаем радиус корабля и радиус камня, если получившееся значение <= 0 значит столкновение, то добавляем экземпляр класса камень во временный set. После окончания обхода set-а с камнями - удаляем из этого set-а временный set (один или более камней при столкновении) и уменьшаем счетчик жизней кораблю.
>>> import random

>>> class A:
>>>     def __init__(self, a):
>>>         self.a = a
>>>     def __str__(self):
>>>         return («%s») % self.a
        
>>> st = set()
>>> for i in range(10):
>>>     st.add(A(random.randrange(0, 10)))

>>> print [str(x) for x in st]

['6', '5', '3', '8', '4', '9', '2', '9', '9', '8']

>>> temp = set()
>>> for x in st:
>>>     if str(x) == '9' or str(x) == '8':
>>>         temp.add(x)

>>> print [str(x) for x in temp]

['8', '9', '9', '9', '8']

>>> st.difference_update(temp)
>>> print [str(x) for x in st]

['6', '5', '3', '4', '2']

Автор: Viktor

Подчеркнутая защищенность

Инкапсуляция — одна из основ ООП. Мы договариваемся использовать только часть функциональности класса, а взамен получаем возможность работать с самыми разными типами, даже с теми, которые будут написаны после окончания работы над текущим кодом.

Компилируемые языки реализуют инкапсуляцию методом принуждения. Программист отмечает методы и поля как личные или защищенные, а компилятор играет в большого брата и проверяет что все используется в корректном контексте. На моей памяти война за способ использования private/protected минимум пару раз принимала нешуточный оборот.

Попадая в питон С++/Java-программисты начинают искать замену родным private/protected в этом мире безудержного эксгибиционизма. И, как правило, быстро находят два подчеркивания. Не совсем то, что хотелось бы, но довольно сильно похоже на private. В итоге нижнее подчеркивание быстро становится самым популярным символом в коде.

Я попробую показать, что:

  • '__' — не эквивалент private и решает совсем другие задачи;
  • Можно отлично жить без private/protected/friend. Оружие массового запрещения не единственный способ реализовать инкапсуляцию;
  • При желании можно написать аналог private/protected и даже более гибкий контроль доступа для python (в следующем посте)

Итак зачем в python поля с двумя подчеркиваниями в начале имени. Пусть у нас есть такой код:

Без подсветки синтаксиса

from some_module import SomeClass

class SomeClassChildren(SomeClass):
def __init__(self):
super(SomeClassChildren, self).__init__()
self.some_field = 12

from some_module import SomeClass

class SomeClassChildren(SomeClass):
def __init__(self):
super(SomeClassChildren, self).__init__()
self.some_field = 12

Допустим код SomeClass очень большой или нам не доступен или постоянно неконтролируемо меняется или по любой другой причине мы не может быть уверенны, что какое бы благозвучное имя не было выбрано для some_field мы не можем быть уверенны, что не затрем поле с таким же именем в родительском классе. Компилируемый язык решил бы эту проблему, не позволив нам создать поле, если поле с таким именем уже унаследовано. Это не решает проблему полностью, но избавляет нас от странного поведения.

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

Без подсветки синтаксиса

import dis

class A(object):
def func(self, x):
self.__attr1

class B(A):
def func(self, x):
self.__attr2

class C(A):
def func(self, x):
class C1(object):
def func(self):
x.__attr3

dis.dis(C1.func)

def r(self):
self.__attr4

class D(object):
func = r

dis.dis(A.func)
dis.dis(B.func)
C().func(A())
dis.dis(D.func)
dis.dis(lambda: A.__attr5)

import dis

class A(object):
def func(self, x):
self.__attr1

class B(A):
def func(self, x):
self.__attr2

class C(A):
def func(self, x):
class C1(object):
def func(self):
x.__attr3

dis.dis(C1.func)

def r(self):
self.__attr4

class D(object):
func = r

dis.dis(A.func)
dis.dis(B.func)
C().func(A())
dis.dis(D.func)
dis.dis(lambda: A.__attr5)

    # dis.dis(A.func)
0 LOAD_FAST 0 (self) << object
3 LOAD_ATTR 0 (_A__attr1) << attribute name
# == self._A__attr1

# dis.dis(B.func)
0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (_B__attr2)

# dis.dis(C1.func)
0 LOAD_DEREF 0 (x)
3 LOAD_ATTR 0 (_C1__attr3)

# dis.dis(D.func)
0 LOAD_FAST 0 (self)
3 LOAD_ATTR 0 (__attr4)

# dis.dis(lambda: A.__attr5)
0 LOAD_GLOBAL 0 (A)
3 LOAD_ATTR 1 (__attr5)

Итого '__' приводит к переименованию поля и позволяет использовать поля с одинаковыми именами в разных классах одной иерархии. Но это не мешает добраться до такого поля, например x._X__some_priv_field. BTW — если нужно сделать действительно скрытое поле, то можно и так:

Без подсветки синтаксиса

class MyProxy(object):
def __init__(self, proxifyed):
self.__dict__[self] = proxifyed # <<<

def __getattr__(self, name):
return getattr(self.__dict__[self], name)

class MyProxy(object):
def __init__(self, proxifyed):
self.__dict__[self] = proxifyed # <<<

def __getattr__(self, name):
return getattr(self.__dict__[self], name)

self.__dict__ — обычный словарь и ключами в нем могут быть не только строки. Злоупотреблять таким хаком не стоит поскольку много различных библиотек, например сериализаторы, сильно удивятся увидев в __dict__ нестроковой ключ.

Итак: '__' — это очень специфический аналог частного поля и он предназначен для несколько других целей.

Модификаторы доступа защищают программиста от случайного и преднамеренного доступа к тем частям API, которые разработчик класса захотел скрыть.

Начнем со случайного доступа на примере С++. Случайно в нем можно вызвать:

  • конструктор (присваиванием, в контейнере при копировании, etc)
  • деструктор (по выходу объекта или его владельца из области видимости)
  • оператор преобразования типа
  • опечатавшись, скопировав неправильно код, etc

Последний пункт скорее из области фантастики. Все остальные тоже не применимы к питону. Питон не копирует объекты, все всегда передается и хранится по ссылке, deepcopy/loads не вызывают конструктор. Деструктор вызывается непонятно когда и чаще всего его вызов сложно контролировать. Доступ к имеющиеся преобразования типов бессмысленно запрещать (__str__, __int__). Так что операции, выполняемые питоном без явного указания программиста не особо нуждаются в разграничении доступа.

Кроме того в С++ в указанных случаях мы получим ошибку на этапе компиляции, а в случае с питоном — в этапе исполнения, когда уже будет не очень понятно что с нею делать.

Перейдем к преднамеренному вызову защищенного метода. Если очень хочется, то никакой private/protected не остановит:

    #define protected public
#include "foo.h"
#undef protected

Есть еще 666 способов добраться до защищенного метода. Есть они и в Java (reflections) и в C#, иначе контей

Инвайт для сайта по изучению языка Python

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

Автор: Viktor

Python tic-tac-toe крестики-нолики 3Х3

Классические крестики нолики 3Х3. Функция должна возвращать в случае выигрыша символ победителя «Х» или «О». Если ничья (draw) — «D». На вход функции подается подобный 2-мерный массив список. Если клетка не заполнена, то символ «.»

game_result = [
        u»OOX»,
        u»XXO»,
        u»OXX»]

>>> def check_win(game_result):

# Column and row checking
>>>     for i in range(3):
>>>         if game_result[i][0] == 

               game_result[i][1] == 
               game_result[i][2]:
>>>             return game_result[i][0]
>>>         if game_result[0][i] == 
               game_result[1][i] == 
               game_result[2][i]:
>>>             return game_result[0][i]
# Diagonal checking

>>>     if game_result[0][0] == 
           game_result[1][1] == 
           game_result[2][2]:
>>>         return game_result[1][1]
>>>     if game_result[2][0] == 
           game_result[1][1] == 
           game_result[0][2]:
>>>         return game_result[1][1]
>>>     return «D»

Автор: Viktor

Python преобразование числа [0-999] в текст

Например преобразовать 310 => three hundred ten


Мое решение:

FIRST_TEN = [«zero», «one», «two», «three», «four», «five», «six», «seven», «eight», «nine»]

SECOND_TEN = [«ten», «eleven», «twelve», «thirteen», «fourteen», «fifteen», «sixteen», «seventeen», «eighteen», «nineteen»]

OTHER_TENS = [«twenty», «thirty», «forty», «fifty», «sixty», «seventy», «eighty», «ninety»]

HUNDRED = «hundred»

number = 310

res_n = «»
lst = list(str(number))

if len(lst) == 1:
    res_n = FIRST_TEN[int(lst[0])]
    
if len(lst) == 2:
    if int(lst[0]) == 1:
        res_n = SECOND_TEN[int(lst[1])]
    elif int(lst[0]) > 1:
        if int(lst[1]) == 0:
            res_n = OTHER_TENS[int(lst[0]) — 2]
        elif int(lst[1]) > 0:
            res_n = OTHER_TENS[int(lst[0]) — 2] + ' ' + FIRST_TEN[int(lst[1])]   

if len(lst) == 3:
    if int(lst[1]) == 0:
        if int(lst[2]) == 0:
            res_n = FIRST_TEN[int(lst[0])] + ' ' + HUNDRED
        elif int(lst[2]) > 0:
            res_n = FIRST_TEN[int(lst[0])] + ' ' + HUNDRED + ' ' + FIRST_TEN[int(lst[2])]
    elif int(lst[1]) == 1:
        res_n = FIRST_TEN[int(lst[0])] + ' ' + HUNDRED + ' ' + SECOND_TEN[int(lst[2])]
    elif int(lst[1]) > 1:
        if int(lst[2]) == 0:
            res_n = FIRST_TEN[int(lst[0])] + ' ' + HUNDRED + ' ' + OTHER_TENS[int(lst[1]) — 2]
        elif int(lst[2]) > 0:
            res_n = FIRST_TEN[int(lst[0])] + ' ' + HUNDRED + ' ' + OTHER_TENS[int(lst[1]) — 2] + ' ' + FIRST_TEN[int(lst[2])]

print res_n

three hundred ten

Решение от гуру:

def convert(i):
if i < 20:
result = 'zero,one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve,thirteen,fourteen,fifteen,sixteen,seventeen,eighteen,nineteen'.split(',')[i]
elif i < 100:
result = ',,twenty,thirty,forty,fifty,sixty,seventy,eighty,ninety'.split(',')[i//10]
if i % 10:
result += ' ' + checkio(i % 10)
elif i < 1000:
result = checkio(i // 100) + ' hundred'
if i % 100:
result += ' ' + checkio(i % 100)
return result

Автор: Viktor

Python нахождение наиболее часто встречающегося символа в тексте

Задача: нахождение наиболее часто встречающейся буквы в тексте (использование например в криптографии при дешифровке). Требуется:

  • ищем только буквы
  • нет зависимости от регистра ('a' и 'A' считаются одинаковой буквой)
  • ищем букву которая встречается максимальное количество раз
  • если таких букв несколько, то результат выдаем первую встречающуюся по алфавиту
Решение мое:

>>> dict = {}
>>> text = «AavvvddAsfgh»
>>> letters = map(chr, range(ord('a'), ord('z')))

>>> for ch in text:
>>>     ch_lower = ch.lower()
>>>     if ch_lower in letters:
>>>         if dict.has_key(ch_lower):
>>>             dict[ch_lower] += 1            
>>>         else:
>>>             dict[ch_lower] = 1            

>>> out = [key for key, value in dict.items() if value == max(dict.values())]
>>> out.sort()
>>> print out[0]
a
Решение от гуру:
для Python 3.3

>>> import string
>>> def out_chr(text):
>>>     return max(string.ascii_lowercase, 

                   key=lambda ch: text.lower().count(ch))

для Python 2.7
>>> import re
>>> from collections import Counter

>>> def out_chr(text):
>>>     text = «».join(re.findall(«w», text.lower()))
>>>     return Counter(text).most_common()[0][0]


Автор: Viktor