Русский
Русский
English
Статистика
Реклама

Boilerplate

Из песочницы Пишем свой CLI генерации React компонент, а может быть не только компонент, а может не только React

01.09.2020 12:04:25 | Автор: admin

Всем привет!


В этой статье, я хочу вам рассказать о том, как так получилось, что мне пришлось писать свой CLI генерации React компонент, притом, что готовых решений достаточно много.


Example


Зачем все это?


Ни раз, говоря об этом CLI другим разработчикам, я встречал вопросы, вызванные непониманием того зачем понадобилось писать свой пакет. Ведь во всех современных редакторах есть шаблоны для файлов и этот процесс более или менее можно автоматизировать. И в этом есть логика. Но она хорошо работает, когда у вас небольшой проект и вы единственный человек который им занимается.


Я пришел к решению создать свой пакет, когда я оказался в проекте, в котором все работали в моно-репозиторий с 12-ю разными проектами, но при этом, пытающимися выдерживать какую-то общую структуру.


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


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


Но есть и еще один, не менее важный нюанс, который я пытался решить с помощью CLI. Когда вы работаете над большим проектом вы вынуждены использовать линтеры и другие инструменты ограничения свободы разработчиков, но чаще всего, эти инструменты никак не описывают структуру приложения, то каким должен быть компонент, каким принято его делать в данный момент в данной команде. Очень часто попадая в большой проект, довольно просто найти несколько архитектурных решений к построению компонента и без помощи других разработчиков невозможно разобраться как делать правильно и под что настроить свой IDE.
Кроме того, все эти шаблоны довольно сложно шарятся между разработчиками и нет возможности их централизовано актуализировать. Более того, если так оказывается, что вы сразу работаете над несколькими проектами, довольно проблематично держать в голове где какие правила приняты.


Что мы хотим?


Исходя из вышеперечисленных проблем, в первом приблежении я хотел, чтобы CLI предоставлял следующие возможности:


  1. Возможность указать путь до папки или папок с компонентами любой вложенности
  2. Возможность удобного выбора места для размещения нового компонента в проекте с учетом вложенности
  3. Возможность работать сразу с несколькими проектами в едином формате из корня репозитория
  4. Возможность указания ссылок на файлы для организации импортов и экспортов
  5. Возможность указать любые форматы файлов для стилей (css, scss, less) и для скриптов (ts, tsx, js, jsx)

В наших проектах компонент имел следующую структуру:


  • ComponentName
    index.ts (Реэкспорт компонента)
    ComponentName.tsx (Здесь нужно импортировать стили)
    ComponentName.module.scss
    ComponentName.test.tsx (Здесь нужен импорт компонента)
    ComponentName.stories.tsx (Здесь нужен импорт компонента)

И вы можете представить себе, как "удобно" создавать компонент с такой структурой каждый раз, стараясь нигде не описаться, и не забыть поменять название во всех местах, если вдруг кто-то решил скопировать компонент.


Спойлер: Хотелки на этом не закончились и список возможностей расширился до такой степени что привязка пакета к React носит достаточно условный характер, поскольку при желании её можно настроить на генерацию компонент и для React Native и для каких-то контроллеров в BFF и чего угодно еще, имеющего подобный, компонентный подход.

Как будем решать проблему?


Вопрос генерации файлов и шаблонизации решился просто, я не стал изобретать ничего сверх сложного и на стандартном fs и регулярках сдедал шаблоны для компонента. Самое же интересное это было дать возможность пользователю легко выбирать нужные настройки, не заставляя его печатать кучу флагов, как это сделано во многих существующих CLI. И так как я старался получить хороший UX, то я начал искать подходящее решение для интерактивного создания компонента и как основу для своего CLI я удачно нашел библиотеку prompts. Изначально я её планировал использовать как метод настройки компонента и ввода имени, но потом идея использовать зацикленный селект для выбора папки показалась очень симпатичной и оказалась неверноятно удобной. Потом я и вовсе заменил его на автокомплит, благодаря которому удалось сделать удобный поиск по папкам, благодаря чему выбирать путь стало не сложнее чем через автокомплит в Linux, причем на любой вложенности у папки с компонентами.


Так как все равно кто-то захочет воспользоваться теми же фичами просто вызвав нужную команду, я также добавил библиотеку commander в проект и добавил пробрасывание всех основных опций в обход интерактивного выбора.


Можно ли сделать еще лучше?


Благодаря хорошей комбинации этих библиотек, удалось довольно быстро покрыть те задачи, которые я для себя ставил в MVP, но потом, я подумал что классно было бы иметь возможность генерировать сразу несколько компонент за раз. Показалось крутым дать возможность прицепить какой-то пост-процессинг для файлов. Добавить фичу создания не только компонент, но и чего-то еще. Появилась идея сделать быструю конфигурацию в отдельном режиме передав флаг --init. И благодаря достаточно неплохой архитектуре, которую я изначально заложил в проект, вышло так, что все эти фичи влетали в проект буквально за час-два.


Проект конечно все еще есть куда развивать. Есть идея добавить возможность не только добавлять компоненты, но и докидывать в них то что в них не хватает. Добавили вы, к примеру, в проект Storybook, хотите в уже существующий компонент добавить историю и вместо того чтобы просто использовать шаблон, также как при создании, приходится вновь заниматься ручной работой, что не очень удобно. И эта фича тоже должно довольно легко въехать на существующие рельсы, хочется это только сделать максимально прозрачно для нового пользователя и при этом удобно и быстро для опытного, поэтому я пока думаю над реализацией фичи.


Где можно попробовать?


CLI доступен в npm и yarn и для того чтобы его попробовать в деле не обязательно что-то настраивать. Под копотом уже есть готовый конфиг который позволяет генерировать компонент с CSS-модулями и тестами, но, если вы захотите что-то подправить это делается тоже очень просто. Вызываете npx rcci --init, отвечаете на пару вопросов, изменяете шаблоны и конфиг, и тем самым вы можете заставить CLI генерировать все что вам угодно. А если вам что-то не удастся реализовать, можете завести issue на github и я добавлю эту фичу в кротчайшие сроки.

Подробнее..
Категории: Javascript , Typescript , React , Reactjs , Js , Cli , Scuffolding , Boilerplate , Devtools

Структура React REST API приложения TypeScript Styled-Components

09.03.2021 16:05:49 | Автор: admin

Доброго %время_суток, хабровчане!

Какие-либо статьи я еще не писал, опыта в этом нет от слова совсем. Поэтому, пожалуйста, не судите строго и прошу заранее прощения, если где-то совершу ошибки.

Это для меня совершенно новый опыт, мне никогда еще не доводилось делиться своим опытом и наработками с сообществом.

Предисловие

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

Кто-то советовал делать плоскую структуру, в том числе и для папки, в которой находятся компоненты, кто-то же советовал просто разделять компоненты на "Молекулы", "Организмы" и т.д., но при этом, почти все, мало внимания уделяли сервисам, хукам, а именно тому, что содержало логику, не говорили о том, где лучше и как лучше их хранить.

НО, были и хорошие, одной из которых я вдохновился. В ней говорилось о том, что всю логику хранить в папке "Core". Ссылку на эту статью, на момент публикации, к сожалению, найти не могу, но как найду - обязательно прикреплю, либо отредактирую пост (если такое будет возможно, то на месте этого абзаца будет ссылка на статью), либо оставлю ссылку в виде комментария.

(!) Прошу вас, на время прочтения этой статьи, держать в голове мысль, что то, что тут предлагаю - это всего-лишь одна из идей того, как можно организовать структуру своего приложения. Не стоит воспринимать мои слова, как догму (это относится к тем, кто только начал свое знакомство с React), ведь я и сам новичок.

Буду очень рад, если вы оставите предложения по улучшению данной структуры, как и вашей конструктивной критике.

Components

Начну, пожалуй, с компонентов.

Компоненты, в данной структуре, разделяются на:

  • Умные (Smart)

  • Обычные (Ordinary)

  • Простые (Simple)

  • UI (UI, как ни странно)

  • Контейнеры (Containers)

  • Страницы (Pages)

Первые четыре группы (Smart, Ordinary, Simple и UI) хранятся в папке Components.

Поговорим немного о них:

  • UI компоненты - это те компоненты, которые заменяют нативные (стандартные) компоненты по типу: button, input, textarea, select и так далее.

    • Данные компоненты не могут использовать свое локальное хранилище и обращаться к глобальному.

  • Simple компоненты - это те компоненты, которые являются простыми, иначе говоря компоненты, в которых нет какой-либо логики, которые просто что-то рендерят.

    • Не могут использовать локальное хранилище и обращаться к глобальному.

    • Не могут использовать хуки, кроме тех, что изначально поставляются с React (за исключением useState).

    • Могут использовать в своей реализации UI компоненты.

  • Ordinary компоненты - это те компоненты, которые могут иметь какую-то логику, для отображения чего-либо.

    • Не могу использовать локальное хранилище, как и обращаться к глобальному.

    • Не могут использовать хуки, кроме тех, что изначально поставляются с React (за исключением useState).

    • Могут использовать в своей реализации Simple и UI компоненты.

  • Smart компоненты - это те компоненты, которые могут использовать относительно серьезную логику, для отображения чего-либо

    • Могут использовать локальное хранилище, как и обращаться к глобальному (не изменяя его)

    • Могут использовать все доступные хуки, кроме тех, что взаимодействуют с сетью

    • Могут использовать в своей реализации Ordinary, Simple и UI компоненты.

Структура папки Componets:

. src/     components/        ordinary        simple        smart        ui     ...

Оставшиеся две группы (Containers и Pages) имеют отдельные папки в корне приложения (папка src).

  • Containers - это те компоненты, которые формируют некие контейнеры, которые, в дальнейшем, используются для формирования страниц, которые, к тому же, содержать в своей реализации компоненты остальных четырех групп и при этом взаимодействовать, каким-либо образом, с сервисами или сетью, если говорить обобщенно.

  • Pages - это те компоненты, которые формируются благодаря контейнерам и компонентам из папки Components, если в этом есть необходимость. Могут, как и контейнеры, взаимодействовать с сервисами.

Структура корневой папки:

. src/     components/        ordinary        simple        smart        ui     containers     pages     ...

Сами компоненты должны иметь отдельные папки, есть 2 (это число не является константой) файла:

  • index.tsx - файл, в котором находится сам компонент

  • styled.ts - файл, в котором находятся стилизованные компоненты (его спокойно можно заменить на styles.sсss, либо же styles.css, в зависимости от того, чем вы пользуетесь для стилизации своих компонентов)

Пример компонента Align. Хотелось бы сказать, что этот компонент попадает под группу "Simple", так как он является глупым (не имеет нужды в локальном хранилище) и не заменяет никакой нативный, браузерный, UI компонент.

// index.tsximport React, { memo } from "react";import * as S from "./styled"; // Импортируем стилизованные компонентыconst Align = memo(({ children, axis, isAdaptable = false }: Readonly<Props>) => {return (<S.Align $axis={axis} $isAdaptable={isAdaptable}>{children}</S.Align>);});export { Align };export interface Props {axis: S.Axis;children?: React.ReactNode;isAdaptable?: boolean;}
// styled.tsimport styled, { css } from "styled-components";const notAdaptableMixin = css`width: 100%;height: 100%;max-height: 100%;max-width: 100%;`;const adaptableMixin = css<AlignProps>`width: ${(props) => !props.$axis.includes("x") && "100%"};height: ${(props) => !props.$axis.includes("y") && "100%"};min-width: ${(props) => props.$axis.includes("x") && "100%"};min-height: ${(props) => props.$axis.includes("y") && "100%"};`;export const Align = styled.div<AlignProps>`display: flex;flex-grow: 1;justify-content: ${(props) => (props.$axis.includes("x") ? "center" : "start")};align-items: ${(props) => (props.$axis.includes("y") ? "center" : "start")};${(props) => (props.$isAdaptable ? adaptableMixin : notAdaptableMixin)};`;export interface AlignProps {$axis: Axis;$isAdaptable?: boolean;}export type Axis = ("y" | "x")[] | "x" | "y";

Теперь, поговорим о самом сладком...

Core

Данная папка является "ядром" вашего приложения. В ней хранится все, для взаимодействия с сервером, глобальное хранилище, тема вашего приложения и т.д.

Эта папка содержит:

  • Config - в данной папке хранятся конфигурационные файлы приложения (например в ней можно хранить данные, необходимы для взаимодействия с бэкендом)

  • Constants - в данной папке находятся все константы, что используются в приложении (например сообщения об ошибках и предупреждениях)

  • Hooks - в данной папке хранятся все хуки кастомные хуки (хуки, что были сделаны вами).

  • Models - в данной папке хранятся модели, что приходят с бэкенда.

  • Schemes - в данной папке хранятся схемы форм, таблиц и т.д.

  • Services - в данной папке хранятся сами сервисы, благодаря которым и происходит общение с бэкендом.

  • Store - в данной папке хранятся схемы глобального хранилища (если Вы используете MobX), если же вы отдаете предпочтение Redux, то в данной папке могут хранится экшены, редьюсеры и т.д.

  • Theme (для Styled-Components) - в данной папке хранятся темы приложения.

  • Types - в данной папке хранятся вспомогательные типы, а также декларации модулей.

  • Utils - в данной папке хранятся вспомогательные, простые, функции, которые могут использоваться в хуках, либо же компонентах.

  • api.ts - в данном файле находится экземпляр HTTP клиента (например axios), который используют сервисы и который какой-то мутирует данные запросы (для передачи каких-либо заголовков, например).

Примеры содержимого папок
// config/api.config.tsexport const serverURI = "http://localhost:8080";export const routesPrefix = '/api/v1';// config/routes.config.tsimport { routesPrefix } from "./api.config";export const productBrowserRoutes = {getOne: (to: string = ":code") => `/product/${to}`,search: (param: string = ":search") => `/search/${param}`,};export const productAPIRoutes = {getOne: (code: string) => `${routesPrefix}/product/code/${code}`,search: () => `${routesPrefix}/product/search`,};

Довольно-таки удобно хранить все роуты в одном файле. Если вдруг со стороны бэкенда изменятся роуты, то их легко можно будет изменить в одном файле, не проходясь, при этом, по всему проекту.

// constants/message.constants.tsexport const UNKNOWN_ERROR = "Неизвестная ошибка";
// hooks/useAPI.ts// Хук для взаимодействия с сервисами/* eslint-disable react-hooks/exhaustive-deps */import { useCallback, useEffect } from "react";import { useLocalObservable } from "mobx-react-lite";import type { API, Schema, Take } from "@core/types";function useAPI<F extends API.Service.Function<API.Response<any>>,R extends Take.FromServiceFunction.Response<F>,P extends Parameters<F>>(service: F, { isPendingAfterMount = false, isIgnoreHTTPErrors = false }: Options = {}) {const localStore = useLocalObservable<Store>(() => ({isPending: {value: isPendingAfterMount,set: function (value) {this.value = value;},},}));const call = useCallback(async (...params: P): Promise<R["result"]> => {localStore.isPending.set(true);try {const { data } = await service(...params);const { result } = data;localStore.isPending.set(false);return result;} catch (error) {if (isIgnoreHTTPErrors === false) {console.error(error);}localStore.isPending.set(false);throw error;}},[service, isIgnoreHTTPErrors]);const isPending = useCallback(() => {return localStore.isPending.value;}, []);useEffect(() => {localStore.isPending.set(isPendingAfterMount);}, [isPendingAfterMount]);return {call,isPending,};}export { useAPI };export interface Options {isPendingAfterMount?: boolean;isIgnoreHTTPErrors?: boolean;}type Store = Schema.Store<{ isPending: boolean }>;
// models/product.model.ts// Описание модели товараexport interface ProductModel {id: number;name: string;code: string;info: {description: string;note: string;};config: {isAllowedForPurchaseIfInStockZero: boolean;isInStock: boolean;};seo: {title: string;keywords: string;description: string;};}
// services/product.service.ts// Сервисы для взаимодействия с товарамиimport { api } from "../api";import { routesConfig } from "../config";import type { ProductModel } from "../models";import type { API } from "../types";export function getOne(code: string) {return api.get<API.Service.Response.GetOne<ProductModel>>(routesConfig.productAPIRoutes.getOne(code));}
// theme/index.ts// Тема приложенияimport { DefaultTheme } from "styled-components";export const theme: DefaultTheme = {colors: {primary: "#2648f1",intense: "#151e27",green: "#53d769",grey: "#626b73",red: "#f73d34",orange: "#fdb549",yellow: "#ffe243",white: "white",},};
// types/index.tsx// Вспомогательные типыimport type { AxiosResponse } from "axios";export namespace API {export namespace Service {export namespace Response {export type Upsert<T> = Response<T | null>;export type GetOne<T> = Response<T | null>;export type GetMany<T> = Response<{rows: T[];totalRowCount: number;totalPageCount: number;}>;}export type Function<T extends API.Response<any>, U extends any[] = any[]> = (...params: U) => Promise<AxiosResponse<T>>;}export type Response<T> = {status: number;result: T;};}
// utils/throttle.tsfunction throttle<P extends any[]>(func: (...params: P) => any, limit: number) {let inThrottle: boolean;return function (...params: P): any {if (!inThrottle) {inThrottle = true;func(...params);setTimeout(() => (inThrottle = false), limit);}};}export { throttle };
// store/index.tsximport { createContext } from "react";import { useLocalObservable } from "mobx-react-lite";import { app, App } from "./segments/app";import { layout, Layout } from "./segments/layout";import { counters, Counters } from "./segments/counters";export const combinedStore = { layout, app, counters };export const storeContext = createContext<StoreContext>(combinedStore);export function StoreProvider({ children }: { children: React.ReactNode }) {const store = useLocalObservable(() => combinedStore);return <storeContext.Provider value={store}>{children}</storeContext.Provider>;}export type StoreContext = {app: App;layout: Layout;counters: Counters;};
// api.ts// Экземпляр AXIOS для взаимодействия с серверомimport axios from "axios";import { apiConfig } from "./config";const api = axios.create({baseURL: apiConfig.serverURI,});api.interceptors.request.use((req) => {return {...req,baseURL: apiConfig.serverURI,};});export { api };

Ух ты! Как же много получилось.

И напоследок...

Есть еще несколько, немаловажных папок, которые также следует упомянуть:

  • Assets - в данной папке хранятся все статичные файлы, такие как: иконки, изображения, шрифты и т.д. (их, конечно же, также стоит группировать и разделять на папки)

  • Routes - в данной папке (либо же файле, кому как больше нравится) хранятся все роуты приложения (пример будет ниже).

  • Styles - в данной папке хранятся все глобальные стили, которые применяются ко всем элементам и документу, в том числе.

// routes/index.tsximport { Switch, Route } from "react-router-dom";// Экспортируем страницыimport { Product } from "../pages/Product";...import { NotFound } from "../pages/NotFound";import { routesConfig } from "../core/config";const Routes = () => {return (<Switch><Route exact path={routesConfig.productBrowserRoutes.getOne()}><Product /></Route>{/* Объявляем как-то роуты */}<Route><NotFound /></Route></Switch>);};export { Routes };

Остается еще 2 файла:

  • app.tsx - компонент приложения

Примерно так он может выглядеть:

// app.tsximport React, { useEffect } from "react";// Импортирует роутыimport { Routes } from "./routes";const App = () => {return (<Routes />);};export { App };
  • index.tsx - входной файл вашего приложения

Он же может выглядеть примерно так:

import React from "react";import ReactDOM from "react-dom";import { BrowserRouter } from "react-router-dom";import { ThemeProvider } from "styled-components";// импортируем нашеimport { App } from "./app";// импортируем глобальные стилиimport { BodyStyles } from "./styles";import { StoreProvider } from "../core/store";// импортируем темуimport { theme } from "../core/theme";import reportWebVitals from "./reportWebVitals";const app = document.getElementById("app");ReactDOM.render(<React.StrictMode><ThemeProvider theme={theme}><BodyStyles /><BrowserRouter><StoreProvider><App /></StoreProvider></BrowserRouter></ThemeProvider></React.StrictMode>,app);reportWebVitals();

И на этом, я думаю, стоит закончить.

Итоговая структура выглядит вот так:

. src/     assets/        fonts        icons     components/        ordinary        simple        smart        ui     containers     core/        config        constants        hooks        models        schemes        services        store        theme        types        utils        api.ts     pages     routes     styles     app.tsx     index.tsx

Заключение

Если вам понравилась эта статья и вы узнали что-то интересное для себя, то я очень этому рад.

Ссылка на репозиторий (за такой скудный ридми, мне еще не по силам разговорный английский).

Всем удачи и огромное спасибо за внимание.

Подробнее..

Перевод Fastcore недооцененная но полезная библиотека Python

21.10.2020 16:06:58 | Автор: admin

Недавно я начал оттачивать владение языком программирования Python. Я хотел изучить продвинутые паттерны, идиомы и методы программирования. Начал я с чтения книг по продвинутому Python, но информация, похоже, не откладывалась в голове без применения навыков. Хотелось иметь возможность задавать вопросы эксперту, пока учусь, а такую возможность трудно найти! Тогда ко мне и пришла идея: что, если я найду проект с открытым и достаточно продвинутым кодом и напишу документацию и тесты? Я сделал ставку, что это заставит меня изучать все очень глубоко, а поддерживающие проект люди оценит мою работу и будут готовы ответить на мои вопросы.



Предыстория


Поиском такого проекта и написанием документации с тестами к нему я занимался целый месяц и такое обучение было самым эффективным из всех, что я пробовал. Я обнаружил, что написание документации заставило меня глубоко понять не только то, что делает код, но и то, почему код работает именно так, как он работает, а также исследовать крайние случаи во время написания тестов. Самое главное, что я мог задавать вопросы, когда застрял, а люди были готовы посвятить мне дополнительное время, зная, что их разъяснения служило тому, чтобы сделать код доступнее! Оказывается, выбранная мной библиотека, fastcore одна из самых увлекательных в Python, с которыми я работал. Ее цели и задачи действительно уникальны.

fastcore это основа многих проектов fast.ai. Самое главное: fastcore расширяет Python, стремясь к устранению шаблонного кода и добавлению полезной функциональности для общих задач. В этом посте я выделю некоторые из моих любимых инструментов fastcore вместо того, чтобы делиться тем, что я узнал о языке. Моя задача вызвать интерес к этой библиотеке, и, надеюсь, мотивировать вас к ознакомлению с документацией после прочтения статьи.

Чем интересна fastcore?


  1. Ознакомление с идеями из других языков прямо в Python: Я постоянно слышу, что полезно изучать другие языки, чтобы стать лучшим программистом. Мне было трудно изучать другие языки с практической точки зрения, потому что я не мог применять их на работе. Fastcore расширяет Python, чтобы включить в него паттерны из разных языков: Julia, Ruby и Haskell. Теперь, когда я понимаю эти инструменты, у меня появилась мотивация изучать другие языки.
  2. Новый набор прагматичных инструментов: fastcore включает в себя утилиты, позволяющие писать более лаконичный выразительный код и, возможно, решать новые задачи.
  3. Изучение Python: fastcore расширяет Python, в этом процессе проявляются многие продвинутые понятия. Для мотивированных людей это прекрасный способ увидеть многое о внутренней работе языка.


Пройдемся по fastcore ураганом


Вот некоторые привлекшие мое внимание вещи, которые возможно сделать с помощью fastcore.

Делаем kwargs прозрачными


Я немного поеживаюсь каждый раз, когда вижу функцию с аргументом kwargs. Это потому, что kwargs означает обфускацию API. Мне нужно прочитать исходный код, чтобы понять, какие параметры допустимы. Посмотрим на пример ниже:

def baz(a, b=2, c =3, d=4): return a + b + cdef foo(c, a, **kwargs):    return c + baz(a, **kwargs)inspect.signature(foo)

<Signature (c, a, **kwargs)>

Без чтения исходного кода может быть трудно узнать, что foo также принимает дополнительные параметрыb и d. Это можно исправить с помощью delegates:

def baz(a, b=2, c =3, d=4): return a + b + c@delegates(baz) # this decorator will pass down keyword arguments from bazdef foo(c, a, **kwargs):    return c + baz(a, **kwargs)inspect.signature(foo)

<Signature (c, a, b=2, d=4)>

Поведение этого декоратора настраивается. Например, вы можете передать аргументы и сохранить при этом kwargs:

@delegates(baz, keep=True)def foo(c, a, **kwargs):    return c + baz(a, **kwargs)inspect.signature(foo)

<Signature (c, a, b=2, d=4, **kwargs)>

Аргументы можно исключать. Например, ниже мы исключаем из делегирования аргумент d:

def basefoo(a, b=2, c =3, d=4): pass@delegates(basefoo, but= ['d']) # exclude `d`def foo(c, a, **kwargs): passinspect.signature(foo)

<Signature (c, a, b=2)>

Возможно делегирование между классами:

class BaseFoo:    def __init__(self, e, c=2): pass@delegates()# since no argument was passsed here we delegate to the superclassclass Foo(BaseFoo):    def __init__(self, a, b=1, **kwargs): super().__init__(**kwargs)inspect.signature(Foo)

<Signature (a, b=1, c=2)>

Для получения дополнительной информации прочтите документацию о delegates.

Избегаем шаблонного кода при установке атрибутов экземпляра


Вы когда-нибудь задумывались, можно ли избежать шаблонного кода, связанного с установкой атрибутов в __init__?

class Test:    def __init__(self, a, b ,c):         self.a, self.b, self.c = a, b, c

Ой! Это было больно. Посмотрите на все эти повторяющиеся имена переменных. Неужели действительно нужно повторять всё это при определении класса? Уже нет! Посмотрите на store_attr:

class Test:    def __init__(self, a, b, c):         store_attr()t = Test(5,4,3)assert t.b == 4

Вы также можете исключить определенные атрибуты:

class Test:    def __init__(self, a, b, c):         store_attr(but=['c'])t = Test(5,4,3)assert t.b == 4assert not hasattr(t, 'c')

Есть гораздо больше способов настройки и использования store_attr, чем я показываю. Ознакомьтесь с документацией для получения более подробной информации.

P.S. Вы можете подумать, что классы данных тоже позволяют избежать такого шаблонного кода. Хотя в некоторых случаях это верно, store_attr более гибок. 1

Например, store_attr не полагается на наследование, а это значит, что вы не застрянете, используя множественное наследование, когда применяете его со своими собственными классами. Кроме того, в отличие от классов данных, store_attr не требует python 3.7 или выше. Вы можете использовать store_attr в любой момент в жизненном цикле объекта и в любом месте вашего класса, чтобы настроить поведение в том, как и когда хранятся переменные. Подробности здесь.

Избегаем бойлерплейта подклассов


Одна вещь, которую я ненавижу в python связанный с подклассами шаблонный код __super__ ().__init__ (). Например:

class ParentClass:    def __init__(self): self.some_attr = 'hello'class ChildClass(ParentClass):    def __init__(self):        super().__init__()cc = ChildClass()assert cc.some_attr == 'hello' # only accessible b/c you used super

Мы можем избежать такого кода, используя метакласс PrePostInitMeta. Как? Определив новый класс под названием NewParent обертку вокруг ParentClass:

class NewParent(ParentClass, metaclass=PrePostInitMeta):    def __pre_init__(self, *args, **kwargs): super().__init__()class ChildClass(NewParent):    def __init__(self):passsc = ChildClass()assert sc.some_attr == 'hello' 

Диспетчеризация типа


Диспетчеризация типа или множественная диспетчеризация позволяет изменить поведение функции в зависимости от типов получаемых входных данных. Это характерная особенность некоторых языков программирования, таких как Julia. Вот концептуальный пример того, как работает множественная диспетчеризация в Julia. В зависимости от типов входных данных x и y возвращаются разные значения:

collide_with(x::Asteroid, y::Asteroid) = ... # deal with asteroid hitting asteroidcollide_with(x::Asteroid, y::Spaceship) = ... # deal with asteroid hitting spaceshipcollide_with(x::Spaceship, y::Asteroid) = ... # deal with spaceship hitting asteroidcollide_with(x::Spaceship, y::Spaceship) = ... # deal with spaceship hitting spaceship

Диспетчеризация типа может быть особенно полезна в Data Science, где возможно разрешить различные типы ввода (т.е. массивы numpy и фреймы данных Pandas) в обрабатывающей данные функции. Типовая диспетчеризация позволяет иметь общий API у выполняющих похожие задачи функций. К сожалению, Python не поддерживает такую функциональность из коробки. К счастью, у нас есть декоратор @typedispatch. Этот декоратор полагается на подсказки типа, чтобы маршрутизировать входные данные к правильной версии функции:

@typedispatchdef f(x:str, y:str): return f'{x}{y}'@typedispatchdef f(x:np.ndarray): return x.sum()@typedispatchdef f(x:int, y:int): return x+y

Ниже показывается диспетчеризации типа при работе для функции f:

f('Hello ', 'World!')

'Hello World!'

f(2,3)

5

f(np.array([5,5,5,5]))

20

У этой функциональности есть ограничения (также как у других способов использования этой функции) и о них вы можете прочитать здесь. В процессе изучения типовой диспетчеризации я также нашел библиотеку Python под названием multipledispatch, написанную Mathhew Rocklin создателем Dask.

После применения этой функции я теперь мотивирован изучать такие языки, как Julia, чтобы обнаружить, каких еще парадигм мне может не хватать.

Лучшая версия functools.partial


functools.partial отличная утилита, создающая функции из других функций и позволяющая устанавливать значения по умолчанию. Возьмем, к примеру, функцию, которая фильтрует список таким образом, чтобы он содержал значения больше либо равное val:

test_input = [1,2,3,4,5,6]def f(arr, val):     "Filter a list to remove any values that are less than val."    return [x for x in arr if x >= val]f(test_input, 3)

[3, 4, 5, 6]

Из этой функции вы можете создать новую функцию с помощью partial, которая устанавливает значение по умолчанию: 5:

filter5 = partial(f, val=5)filter5(test_input)

[5, 6]

Одна из проблем с partial заключается в том, что она удаляет исходную строку документации и заменяет ее общей строкой документации:

filter5.__doc__

'partial(func, *args, **keywords) - new function with partial application\n    of the given arguments and keywords.\n'


fastcore.utils.partialler исправляет это и обеспечивает сохранение строки документации таким образом, чтобы новый API был прозрачным:

filter5 = partialler(f, val=5)filter5.__doc__

'Filter a list to remove any values that are less than val.'

Композиция функций


Распространенный в функциональных языках программирования метод композиция функций, когда вы связываете несколько функций вместе, чтобы достичь определенного результата. Это особенно полезно в различных преобразований данных. Рассмотрим игрушечный пример, где у меня три функции: первая удаляет элементы списка меньше 5 (из предыдущего раздела), вторая добавляет 2 к каждому числу, третья суммирует все числа:

def add(arr, val): return [x + val for x in arr]def arrsum(arr): return sum(arr)# See the previous section on partialleradd2 = partialler(add, val=2)transform = compose(filter5, add2, arrsum)transform([1,2,3,4,5,6])

15

Почему это полезно? Вы можете подумать, что я могу сделать то же самое вот так:

arrsum(add2(filter5([1,2,3,4,5,6])))

Все верно! Однако, композиция дает удобный интерфейс на случай, когда вы захотите сделать что-то такое:

def fit(x, transforms:list):    "fit a model after performing transformations"    x = compose(*transforms)(x)    y = [np.mean(x)] * len(x) # its a dumb model.  Don't judge me    return y# filters out elements < 5, adds 2, then predicts the meanfit(x=[1,2,3,4,5,6], transforms=[filter5, add2])

[7.5, 7.5]

Более подробную информацию о compose читайте в документации.

__repr__, но полезнее


В Python __repr__ помогает получить информацию об объекте для логирования и отладки. Ниже приведено то, что вы получите по умолчанию, когда определите новый класс. Примечание: мы используем store_attr, который обсуждался выше.

class Test:    def __init__(self, a, b=2, c=3): store_attr() # `store_attr` was discussed previouslyTest(1)

<__main__.Test at 0x7ffcd766cee0>

Мы можем использовать basic_repr, чтобы быстро получить более разумное значение по умолчанию:

class Test:    def __init__(self, a, b=2, c=3): store_attr()     __repr__ = basic_repr('a,b,c')Test(2)

Test(a=2, b=2, c=3)

Обезьяньи патчи через декоратор


Это может быть удобно для обезьяньих патчей с помощью декоратора, что особенно полезно, когда вы хотите пропатчить импортируемую внешнюю библиотеку. Мы можем использовать декоратор @patch из fastcore.foundation вместе с подсказками типа примерно так:

class MyClass(int): pass  @patchdef func(self:MyClass, a): return self+amc = MyClass(3)

Теперь в MyClass есть дополнительный метод под названием func:

mc.func(10)

13

Я еще не убедил вас? Тогда покажу вам еще один пример такого патча в следующем разделе.

pathlib.Path


Увидев эти расширения в pathlib.path, вы больше никогда не будете работать с vanilla pathlib! В pathlib добавлен ряд дополнительных методов, таких как:

  • Path.readlines: то же, что with open ('somefile', 'r') as f: f.readlines ()
  • Path.read: то же, что with open ('somefile', 'r') as f: f.read ()
  • Path.save: сохраняет файл как pickle
  • Path.load: загружает файл pickle
  • Path.ls: показывает содержимое пути в виде списка.
  • и так далее.

Подробнее об этом здесь. Вот демонстрация ls:

from fastcore.utils import *from pathlib import Pathp = Path('.')p.ls() # you don't get this with vanilla Pathlib.Path!!

(#7) [Path('2020-09-01-fastcore.ipynb'),Path('README.md'),Path('fastcore_imgs'),Path('2020-02-20-test.ipynb'),Path('.ipynb_checkpoints'),Path('2020-02-21-introducing-fastpages.ipynb'),Path('my_icons')]

Подождите! Что здесь происходит? Мы только что импортировали pathlib.Path почему мы получили новую функциональность? Потому, что импортировали модуль fastcore.utils, который патчит pathlib.Path с помощью упомянутого выше декоратора @patch. Чтобы довести дело до конца и показать, чем полезен @patch, я пойду дальше и прямо сейчас добавлю еще один метод в Path:

@patchdef fun(self:Path): return "This is fun!"p.fun()

'This is fun!'

Волшебно, правда? Вот почему я пишу об этом!

Еще более лаконичный способ написать лямбду


Self с заглавной буквы S это еще более лаконичный способ написания вызывающих методы объекта лямбд. Например, создадим лямбду для получения суммы массива Numpy:

arr=np.array([5,4,3,2,1])f = lambda a: a.sum()assert f(arr) == 15

Вы можете таким же образом использовать Self:

f = Self.sum()assert f(arr) == 15

Давайте создадим лямбду, которая будет делать группировку и возвращать максимальный элемент фрейма данных Pandas:

import pandas as pddf=pd.DataFrame({'Some Column': ['a', 'a', 'b', 'b', ],                  'Another Column': [5, 7, 50, 70]})f = Self.groupby('Some Column').mean()f(df)

Another Column
Some Column
a 6
b 60

Подробнее о Self читайте в документации.

Функции блокнота


Они просты, но удобны и позволяют узнать, выполняется ли код в блокноте Jupyter, Colab или через оболочку IPython:

from fastcore.imports import in_notebook, in_colab, in_ipythonin_notebook(), in_colab(), in_ipython()

(True, False, True)

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

Замена стандартного списка


Вы можете быть довольно счастливы со стандартным list в Python. Это одна из тех ситуаций, когда вы не знаете, что вам нужен список лучше, пока кто-то не покажет его вам. L как раз такой список с большим количеством доработок.

Лучший способ описать L сделать вид, что у list и numpy родился милый ребенок. Определите список (посмотрите на приятный __repr__, показывающий длину списка!)

L(1,2,3)

(#3) [1,2,3]

Перемешайте список:

p = L.range(20).shuffle()p

(#20) [8,7,5,12,14,16,2,15,19,6...]

Индекс [прим.перев. скорее позиция position] в списке:

p[2,4,6]

(#3) [5,14,2]

L имеет разумные умолчания, например, вот добавление элемента в список:

1 + L(2,3,4)

(#4) [1,2,3,4]

L может гораздо больше. Читайте документацию, чтобы узнать больше.

Но подождите Это еще не всё!


Есть еще кое-что, что я хотел бы показать вам, но это никак не вписываются в пост. Вот список некоторых любимых вещей, которые я не показывал в этом посте:

Утилиты


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

  • mk_class: быстро добавляет кучу атрибутов в класс
  • wrap_class: добавление новых методов в класс с помощью простого декоратора
  • groupby: похоже на groupby из Scala
  • merge: слияние словарей
  • fasttuple: кортеж на стероидах
  • Infinite Lists: полезно для увеличения размеров массивов и тестирования
  • chunked: упаковка и организация элементов

Многопроцессорная обработка


Раздел Многопроцессорная обработка расширяет соответствующую библиотеку Python, предлагая такие возможности:

  • Шкала прогресса
  • Возможность сделать паузу, чтобы смягчить состояние гонки с помощью внешних сервисов
  • Пакетная обработка для каждого воркера, если у вас нет векторизованных операций для выполнения в блоках (chunk)

Прокачивать себя в Python стало проще, ведь специально для хабравчан мы сделали промокод HABR, дающий дополнительную скидку 10% к скидке указанной на баннере.

image




Рекомендуемые статьи


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru