Функции, отличные от членов и членов в Python
Я относительно новичок в Python и стараюсь примирить функции языка с привычками, которые я выбрал из своего фона на С++ и Java.
Последняя проблема, с которой я столкнулась, связана с инкапсуляцией, в частности, с идеей, которая лучше всего подытожена в пункте 23 Мейера "Эффективный С++":
Предпочитают функции, не являющиеся членами-не-другу, для функций-членов.
Игнорирование отсутствия механизма friend
на мгновение, - это не-членные функции, которые считаются предпочтительными для функций-членов в Python тоже?
Обязательный, асинственный пример:
class Vector(object):
def __init__(self, dX, dY):
self.dX = dX
self.dY = dY
def __str__(self):
return "->(" + str(self.dX) + ", " + str(self.dY) + ")"
def scale(self, scalar):
self.dX *= scalar
self.dY *= scalar
def scale(vector, scalar):
vector.dX *= scalar
vector.dY *= scalar
Учитывая v = Vector(10, 20)
, мы можем либо вызвать v.scale(2)
, либо scale(v, 2)
, чтобы удвоить величину вектора.
Учитывая тот факт, что в этом случае мы используем свойства, , какой из двух вариантов - если есть - лучше, и почему?
Ответы
Ответ 1
Интересный вопрос.
Вы начинаете с другого места, чем большинство вопросов, исходящих от Java-программистов, которые склонны предполагать, что вам нужны классы, когда вы в основном этого не делаете. Как правило, в Python нет смысла иметь классы, если вы специально не делаете инкапсуляцию данных.
Конечно, здесь, в вашем примере, вы это делаете, поэтому использование классов оправдано. Лично я бы сказал, что, поскольку у вас есть класс, то функция-член лучше всего подходит: вы специально выполняете операцию над этим конкретным векторным экземпляром, поэтому имеет смысл, чтобы функция была методом на Vector.
Если вы захотите сделать его автономной функцией (мы действительно не используем слово "член" или "не-член" ), то вам нужно, чтобы он работал с несколькими классами, которые не обязательно наследуются от друг друга или общую базу. Благодаря утиной печати довольно распространенная практика: укажите, что ваша функция ожидает объект с определенным набором атрибутов или методов и что-то делает с ними.
Ответ 2
Свободная функция дает вам возможность использовать утиную печать для этого первого параметра.
Функция-член дает вам выразительность ассоциирования функциональности с классом.
Выберите соответственно. Как правило, функции создаются равными, поэтому все они должны иметь те же предположения об интерфейсе класса. После публикации бесплатной функции scale
вы эффективно рекламируете, что .dX
и .dY
являются частью открытого интерфейса Vector
. Это, вероятно, не то, что вы хотите. Вы делаете это в обмен на возможность повторного использования одной и той же функции с другими объектами с .dX
и .dY
. Это, вероятно, не будет для вас ценным. Поэтому в этом случае я бы предпочел функцию-член.
Для хороших примеров предпочтения свободной функции нам нужно смотреть не дальше стандартной библиотеки: sorted
- это бесплатная функция, а не член-функция list
, потому что концептуально вы должны иметь возможность создавать список, который является результатом сортировки любой итеративной последовательности.
Ответ 3
Предпочитают функции, не являющиеся членами-членами, для функций-членов
Это философия дизайна и может и должна быть распространена на все языки программирования OOP Paradigm. Если вы понимаете суть этого, концепция понятна.
Если вы можете обойтись без использования частного/защищенного доступа к членам класса, в вашем дизайне нет причины включать функцию, являющуюся членом класса. Чтобы думать об этом другим способом, при разработке класса, после перечисления всех свойств, вам нужно определить минимальный набор поведений, которые были бы достаточными для создания класса. Любая функция-член, которую вы можете написать, используя любой из доступных общедоступных методов/функций-членов, должна быть предана гласности.
Насколько это применимо в Python
В какой-то степени, если вы будете осторожны. Python поддерживает более слабую инкапсуляцию по сравнению с другими языками ООП (например, Java/С++), особенно потому, что нет частных членов. (Есть что-то, называемое Private variables, которое программист может легко написать, префикс "_" перед именем переменной. Это становится приватным классом с помощью функции переключения имен.). Поэтому, если мы буквально принимаем слово Скотта Мейера, считая, что есть тонкий, как между тем, что должно быть доступно из класса, и что должно быть извне. Лучшему разработчику/программисту лучше всего решить, должна ли функция быть неотъемлемой частью класса или нет. Один принцип проектирования, который мы можем легко принять, "Unless your function required to access any of the properties of the class you can make it a non-member function".
Ответ 4
Посмотрите на свой собственный пример - функция, не являющаяся членом, должна получить доступ к элементам данных класса Vector. Это не победа для инкапсуляции. Это особенно важно, так как он изменяет элементы данных переданного объекта. В этом случае было бы лучше вернуть масштабированный вектор и оставить исходный неизменным.
Кроме того, вы не поймете каких-либо преимуществ полиморфизма класса, используя функцию, не являющуюся членом. Например, в этом случае он все еще может справляться только с векторами двух компонентов. Было бы лучше, если бы он использовал возможность векторного умножения или использовал метод для итерации по компонентам.
Вкратце:
- использовать функции-члены для работы с объектами классов, которыми вы управляете;
- использовать функции, не являющиеся членами, для выполнения чисто общих операций, которые реализуются в терминах методов и операторов, которые сами по себе являются полиморфными.
- Возможно, лучше сохранить мутацию объекта в методах.
Ответ 5
Поскольку scale
полагается на умножение по члену вектора, я бы рассмотрел возможность применения умножения в качестве метода и определения scale
более общего:
class Vector(object):
def __init__(self, dX, dY):
self._dX = dX
self._dY = dY
def __str__(self):
return "->(" + str(self._dX) + ", " + str(self._dY) + ")"
def __imul__(self, other):
if other is Vector:
self._dX *= other._dX
self._dY *= other._dY
else:
self._dX *= other
self._dY *= other
return self
def scale(vector, scalar):
vector *= scalar
Таким образом, интерфейс класса богат и оптимизирован, пока поддерживается инкапсуляция.