#python #oop
#python #ооп
Вопрос:
Я выполняю некоторую обработку данных и построил несколько конвейеров, каждый из которых состоит из нескольких функций, которые в целом изменяют словари на каждом шаге. Поскольку разные конвейеры работают с одними и теми же данными и имеют схожие функции, я пытался преобразовать его в более ориентированную на ООП структуру. Однако, прежде чем я начну, я немного завязал себя в узлы.
Возьмем следующий упрощенный пример:
for f in foos:
y = extract_y_info(f)
z = extract_z_info(f)
*some code that does something with y and z*
def extract_y_info(f):
return *some code that extracts y info from f*
def extract_z_info(f):
return *some code that extracts z info from f*
Мне кажется, есть несколько способов, которыми я мог бы приблизиться к переносу этого в структуру ООП. Первый подход очень похож на подход «функция за функцией».
class foo():
def __init__(self, x):
self.x = x
def extract_y_info(self):
return *some code that extracts y info from self.x*
def extract_z_info(self):
return *some code that extracts z info from self.x*
for f in foo_instances:
y = b.extract_y_info()
z = b.extract_z_info()
*some code that does something with y and z*
Другим вариантом является изменение экземпляров класса:
class foo():
def __init__(self, x):
self.x = x
def extract_y_info(self):
self.y = *some code that extracts y info from self.x*
def extract_z_info(self):
self.z = *some code that extracts z info from self.x*
for f in foo_instances:
f.extract_y_info()
f.extract_z_info()
*some code that does something with f.y and f.z*
Является ли любой из этих вариантов лучшей практикой, чем другой? Есть ли лучший третий способ?
Комментарии:
1. Честно говоря, я думаю, что вы могли бы лучше провести время только со свободными функциями, если только вам не нужно наследование (и ничто не мешает вам смешивать и сопоставлять классы и функции в вашем конвейере)…
Ответ №1:
Это действительно зависит от того, каков ваш общий дизайн и в каком состоянии вы ожидаете, что ваши экземпляры будут находиться в любой момент времени, и что вы с ним делаете (другими словами, означает ли существование y
самого атрибута, но … первое кажется мне в целом более безопасным. Вы вызываете и получаете значение, вам не нужно отслеживать, вызвал ли я метод и в каком состоянии находится тот или иной атрибут? Обратите внимание, однако, что вы действительно должны определять атрибуты экземпляра в конструкторе, иначе доступ может быть не просто неожиданным, но и фатальным ( AttributeError
) .
Теперь аккуратным решением, учитывающим некоторые из приведенных выше пунктов и, возможно, соответствующим тому, что вы, похоже, делаете здесь для доступа к значениям, может быть свойство, которое, по сути, позволяет вам получать доступ к значению, возвращаемому методом, как если бы это был атрибут экземпляра:
class foo():
def __init__(self, x):
self.x = x
def extract_y_info(self):
return #some code that extracts y info from self.x
y = property(extract_y_info)
for f in foo_instances:
print(f"value of f.y = {f.y}")
Или вы можете сделать то же самое, используя property
декоратор метода as:
@property
def y(self):
return #some code that extracts y info from self.x
Если получение y
было дорогостоящим и его значение не менялось в течение срока службы экземпляра, вы также можете использовать запуск Python 3.8 cached_property
.
Комментарии:
1. Спасибо за это! Один быстрый вопрос… вы написали «вы действительно должны определять атрибуты экземпляра в конструкторе» — означает ли это, что я всегда должен определять
self.y
иself.z
в__init__()
, даже если для них установлено значениеNone
для начала?2. ДА. Веская причина: ваш интерфейс экземпляра (его атрибуты) легко понять, посмотрев на конструктор и атрибуты, установленные в constructor . Разница в том, что вы тогда спросили, также заключается в том, что, если вы не назначены в конструкторе и получили доступ к методу, который бы это сделал,
f.z
возниклоAttributeError
бы, если бы вы назначилиNone
ему в constructor (__init__
) , вы бы получилиNone
, даже если бы он не имел никакого особого значения (но и для этого я бы все равно обычнопредпочитаю:return
над комбинацией манипулирования / доступа, если я хочу получить значение.