Как стать автором
Обновить

Комментарии 27

я в подопечных проектах использую, как правило, hexdi, фиксацию интерфейсов и «правильное» покрытие тестами, как замену этапа компиляции))
Почему бы не добавить параметру `callback_url` дефолтное значение? Тогда ничего не сломается.

Паттерн синглтон. Лучше использовать это понятие, а не глобальный словарь.

Синглтон не нужен, когда можно импортировать непосредственно объект.
Вот вот эта вот хрень на очень много строк, что вы написали это что угодно, вовсе не питонический подход. Напоминает java — флуд
Пример гибкого импорта:
options.py содержит опции:
import numpy as np

class mynumpy():

def array(self, arr):
print('wohhoo, another method !!!' , arr)

np = mynumpy()
import django
django.setup()
#etc...


usage.py импортирует опции всего лишь:

from options import *

a = np.array([1,2,3])


Проблема вот какая: класс TextProcessor зависит от класса ShortenerClient — и сломается при изменении интерфейса ShortenerClient.

ещё один из питонических подходов был бы:
def shorten_link(self, *args, **kwargs):
не зависящий от интерфейса.
Автор, пишите ещё!!!

ещё один из питонических подходов был бы:
def shorten_link(self, *args, **kwargs):
не зависящий от интерфейса.

Это прекрасно. Как вообще угадать что в этот shorten_link нужно передать при вызове?

передавай что хочешь. было:
shorten_link(self, link, callback_url):
response = requests.post(
url='https://fstrk.cc/short',
headers={'Authorization': self.api_key},
json={'url': url , 'callback_on_click': callback_url}
)

кстати, почему прописан постоянный url?
стало:
#выше в классе дефолт - параметры:
default_shorten_link_kwargs = {'url' = ''https://fstrk.cc/short',
'Authorization' : some_key ,
'callback_on_click' : None ,
#другие параметры}
#а можешь и сет:
json_keys = {'url', 'callback_on_click'}
shorten_link(self,**kwargs) :
opts = dict(default_shorten_link_kwargs) #копия дефолт- опций
opts.update(kwargs) # заменяем дефолт на то что вызвал в функции
response = requests.post( url = opts['url'],
headers={'Authorization': opts['Authorization']},
json={x : opts[x] for x in json_keys }
)

изменить то что в json или вообще в opts можно независимо друг от друга как в классе где shorten_link так и со стороны вызывающего, просто изменив словарь или сет. Вот что такое гибкость. А то что в статье с диаграммой это подход жабистов с флудом и чрезмерным выделением классов

Вы путаете гибкость и "всё мясо наружу":


  1. Почему у вас URL настраивается, но метод запроса POST прибит гвоздями?
  2. Каким образом в запрос добавить свои хедеры?
  3. Если я не хочу использовать requests, как подключить туда свою библиотеку?

В мире Java это традиционно делается через какие-нибудь там RequestFactory и RequestExecutor, которые пользователь при желании может реализовать самостоятельно. И там уж хоть кукис в запрос добавляй в RequestFactory, хоть retry логику реализовывай в RequestExecutor — полная свобода. А у вас просто мясо наружу.

В Питоне всё мясо наружу, стоит только вызвать class.__dict__

Почему у вас URL настраивается, но метод запроса POST прибит гвоздями?
Каким образом в запрос добавить свои хедеры?
я не в курсе именно этого API requests, как именно там что вызывать, поэтому предлагаю универсально через словарь. Будет время посмотрю именно этот API как там лучше использовать и что спрятать.

С гибкими параметрами реквеста будет вроде карты:
requests_post_args = {'url' : {'url'},
'Authorization' : {'Authorization'} ,
'json' : {'url', 'callback_on_click'}}


и применить:
requests.post(** { key, {x : opts[x] for x in value} for key, value in requests_post_args.items()}

Если я не хочу использовать requests, как подключить туда свою библиотеку?
Питон гениален: просто подсунуть другой обьект requests с таким же методом post Ну типа как я подменил нампи np
Питон гениален: просто подсунуть другой обьект requests с таким же методом post

Каким образом этот "другой объект requests" передать в shorten_link если этот самый shorten_link — мне не принадлежит? Какой-то финт с манки-патчем импортов? Или код, который вы постите, пока к этому не готов — и нужно добавить ещё один параметр у shorten_link?


Как вы задокументируете, что ваш API принимает "другой объект requests с методом post"? Будучи насколько-то знакомым с библиотекой requests, я предположу, что там такой же космос возможных параметров у одного этого метода — вы их все потребуете поддерживать, или возьмётесь писать простыню текста, которая рассказывает, что в принципе нужны только хедеры, а content-type всегда application/json, и т.д.?


По-моему то, что вы пишете — крайне неудачные идеи. Код совсем не прячется за интерфейсом ("принимаю какие-то параметры, возвращаю какой-то результат") — хочешь меня использовать, изучай что я делаю внутри.

Питонячий подход, ага…
Wildcard imports (from import *) should be avoided, as they make it unclear which names are present in the namespace, confusing both readers and many automated tools.
www.python.org/dev/peps/pep-0008
это можно делать специально если голова на плечах. В конце концов это питон, где нет например защиты приватной переменной и любой может влезть

Кваргс я люблю. Уважаемый мной Реймонд Хеттингер продвигает этот подход под названием Cooperative Multiple Inheritance, т.к. это позволяет чему угодно унаследоваться от чего угодно и не сломать сигнатуру.


но что теперь, во все методы всех классов дописывать *args и **kwargs?

ну а что, если ты не уверен в интерфейсе и думаешь потом а давай-ка я его изменю, как автор, хоть вообще-то интерфейс должен меняться гораздо гораздо реже чем реализация, по крайней мере так принято у жабистов.
> ещё один из питонических подходов был бы:
> def shorten_link(self, *args, **kwargs):
> не зависящий от интерфейса.

Вы это серьёзно? «Питонический подход» для вас обозначает исключение любых возможностей по статическому анализу кода, пусть даже визуальному?
«Почему-то» весь мир активно стремится к повышению возможностей такого анализа, вон в Python завезли возможность аннотаций типов, и активно развивают средства вроде mypy (и уже под десяток их).

> Автор, пишите ещё!!!

Хотел сказать «взаимно», но мне очень не нравится сарказм такого рода. Лучше — не пишите, не рекомендуйте людям плохого.

В последнем проекте делал примерно также.
Был dict с алиасом класса и его зависимостями (в виде алиасов). Был декоратор, который подставлял зависимости в контроллеры (циклически создавая и сохраняя зависимости зависимостей)

Ужасно. Ужасно, насколько можно не понимать любимый мною и элегантный Python. Еще хуже только неумение читать документацию.

Начну с вопроса джангистам. Часто ли вы пишете вот эти две строчки?
import django; django.setup()

Ответ джангиста: Джангисты, обычно, не пишут эти строки. Они изначально работают с Django. Скорее это вопрос к Python-разработчикам, кто хочет использовать объекты Django «Stanalone»
С этого нужно начать файл, если вы хотите поработать с объектами django, не запуская сам вебсервер Django. Я постоянно пишу эти две строчки.

Нет. Не каждый. Более того, это должно быть написано только один раз в проекте согласно документации.

Вероятно автор ооочень любит что-то писать многократно, и подзабыл правило DRY: Если хотя бы две повторяющихся строчки можно превратить в одну, то надо так и сделать:
Можно сделать хелпер i_love_import_django_everywhere.py.
import django as _dj; dj.setup(); Django=_dj 

и импортировать везде в файлах:
from i_love_import_django_everywhere.py import Django


Импорт константы. Вместо привычного места хранения констант в Settings (или где вы там организовали хранилище настроек), автор предлагает захардкодить значение настройки в коде.

Представляю: приходит новый сотрудник, изучает структуру, ага питон код, ага объекты из Django, ага для изменения поведения Объекта из Django надо поменять настройки в Django. Меняет… и ничего не происходит.
Потому, как предыдущий разработчик узнал про то, как плохо делать импорт зависимостей. И про паттерны в ООП узнал.
И, похоже, забыл узнать про Инкапсуляцию, которая предлагает убрать все изменяемые данные туда, где они реально меняются. в Django для этого есть Settings.

Автор отмечает, что история с зависимостью от Django — это пример проблемы, с которой он сталкивается каждый день.
Однако: Если зависимость от Django ежедневная, то, либо стоит просто начать писать под Django, либо стоит отказаться от Django,… либо это просто не проблема.

На выдуманную проблему «сломанного интерфейса» прекрасно ответил kommie2050
def shorten_link(self, *args, **kwargs)
— Это действительно «more pythonic».
Я предлагаю дополнить это решение так:

def shorten_link(self, *args, **kwargs):
    response = requests.post(
        url=settings.SHORTER_URL,
        headers={'Authorization': self.api_key},
        json= kwargs,
    )
    return response.json()['url']

Теперь shorten_link готов принимать в качестве Kwargs любое количество управляющих переменных.
А вместо захардкоренного в коде URL я предлагаю вынести это значение в хранилище настроек. Адреса сервисов, знаете ли, иногда меняются…

Все что предложено автором ниже — это кропотливо скопированный код из книги по паттернам OOP для Java. Очень похоже на стиль из серии учебников Head First.

Только автор забыл, что речь идет о Python, где тотальное следование OOP бессмысленно.
Например, есть шикарный доклад "паттерны OOP в Python? Вам это не нужно"

В общем, могу порекомендовать автору статьи поработать с наставником, консультантом, тем, кто понимает Python и у кого хватит сил объяснить, что Python это не Java.
+
он криво обьясняет подход с громким именем inversion of controll, не зная что это есть в каждом вызове функции или конструктора в Питоне.
url=settings.SHORTER_URL,
headers={'Authorization': self.api_key},

Каким образом вы принимаете решение, что SHORTER_URL берётся из settings, а api_key — из self?

Я не принимаю решение. Это код из статьи. API Key передается при инициализации класса и Я даже не знаю что это такое и откуда. А вот то, что ссылка захардкожена в коде функции, скорее всего недоработка. Как собственно и метод Post и заголовки и класс респонса. И ключ по которому получаем результат.

Классный доклад you don’t need that! Спасибо! Я ищу способы не применять DI в питоне — и любой аргумент ценен) правда, в этом докладе делается акцент на «в тестах мы можем все замокать, нам не нужен DI для тестов”, но ничего не говорится об организации командной работы с большой кодовой базой. Если вдруг вам вспомнятся еще доклады — скиньте, пожалуйста, я правда хочу найти повод избавиться от DI. На наставника я времени не найду, но доклады смотреть люблю.


Насчет копирования книги про ООП — точно подмечено) я прочитал «чистую архитектуру» боба мартина и, собственно, это попытка переложить его идеи на наш проект.

Предположу, что понравится доклад и наработки про разделение бизнес логики на апрельском Джанго конф 2019. В моей статье на Хабре, ссылка есть, в третий день конференции.

Ага! Я понял, о чем вы. Вашу статью я читал и даже цитировал в одном из докладов :)

1.
def shorten_link(self, *args, **kwargs)
Как знать что принимает и возращает эта функция?
Перечитать её код? А если там еще 5 таких же "гибких" функций? Перечитать рекурсивно все n тисяч строчек кода?


2.
return response.json()['url']
Почему вы думаете что любой сервис с settings.SHORTER_URL имеет ключ url?
Что вы будете делать, если будет два сервиса с разными названиеми свойств?

> Теперь у нас есть надежный способ зафиксировать интерфейс класса.

Я бы не назвал его надёжным. Если определение этого интерфейса, опять же, ведёт чужая команда, то ничего не изменится в самой возможности ситуации, что они его поменяли, а сломалось — у вас.

Эта проблема не решается технически независимо от того, какой вообще метод связи интерфейсов с реализацией. Может, это протокол поверх UDP + ASN.1 — всё равно будет то же самое, и решается только административно.
Как знать что принимает и возращает эта функция?

В приведенном мною моем решении — никак. Полагаю, что для этой функции будет достаточно методов самодокументации с говорящими именами и можно добавить docstring. Вариантов много, ADR, предлагайте свои.

return response.json()['url']

Конечно же как и url так и ответы от разных сервисов, скорее всего, отличаются. И перенастройка только URL может привести к ошибке KeyError.

Как логично заметил js605451 и позже еще и я отметил, в исходной функции много что прибито гвоздями, в том числе и ключ значения в ответе. Думаю, благодаря комментариям, автор статьи увидел это более четко.
Всем привет!

Отдельное спасибо автору статьи за упоминание нашего проекта dry-python (https://github.com/dry-python).

В статье действительно хорошо описаны проблемы жёстких зависимостей между разными частями кода.

Причём чем больше кодовая база — тем больнее их решать.

Проект dry-python это не только dependency injection, но и набор библиотек для построения Clean Architecture и Domain-Driven Design. Есть библиотеки для слоя сервисов (stories и returns), есть библиотеки для слоя репозиториев (mappers).

Хочу дополнительно осветить причины по которым мы выбрали dependency injection вместо service locator в виде глобального дикта.

1. Допустим есть два класса PlaceOrder и FinishOrder из сервисного слоя.
2. Они оба зависят от GLOBAL_DICT['SmsGateway'].
3. Через некоторое время понадобится поменять детали отправки sms'ок в FinishOrder для пользователей из страны Х.
4. Понадобится поправить код и FinishOrder и GLOBAL_DICT.

В то время как при использовании DI понадобится поправить только контейнер FinishOrderInjector. К тому же детали, такие как «активный залогиненный пользователь» будет проще через DI прокинуть, чем через весь стек вызовов. В FinishOrder, например, этого пользователя может и не быть.

Если вдруг кого-то заинтересовал проект dry-python — можно посмотреть больше видео тут dry-python.org/talks
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации