Варіант 1. Зберігати одну загальну сіль для всіх паролів користувачів поза БД
hash(secret + password)
На перший це виглядає безпечніше, адже якщо хтось отримає доступ до БД, то солі у ньому не буде, і напевно відновити паролі не зможе? ..
Але якщо зловмисник отримає доступ і до БД, і до солі зі змінної середовища, то для добування паролів все одно треба буде будувати райдужну таблицю. І якщо у вас одна сіль на всіх користувачів, то це зробити набагато простіше. Інша проблема з безпекою: якщо зловмисник (або його співучасник/жертва) має доступ до системи як користувач, то він вже знає 1 пароль і його хеш, можливо зможе змінювати пароль і дивитися зміну хеша, знаходити в БД інших користувачів з таким же паролем...
Цей варіант схожий на принцип Security through obscurity (безпека через неясність/заплутування), а цього принципу радять уникати і покладатися на сильніші криптографічні методи.
Варіант 2. Зберігати свою для кожного користувача сіль в БД з хешамі паролів
hash(salt + password)
В цьому випадку, якщо зловмисник отримає доступ до БД, то разом з хешами паролів він отримає і солі до них. Але тепер для отримання паролів треба буде згенерувати не одну райдужну таблицю на всіх, а для кожного користувача свою! При цьому, важливо коректно реалізувати свою систему роботи з користувачами: при зміні пароля треба міняти і сіль, щоб навіть маючи стару версію БД, зловмисник вже не зміг отримати з цього вигоду.
Цей же спосіб використовується в Django, також його радять і обгрунтовують в аналогічному питанні на enSO - раджу почитати, там обговорені різні варіанти і їх обгрунтування.
Питання насправді спірне і залежить від реалізації вашої системи. Якщо у вас лише 1 або кілька серверів з повним або слабо захищеним доступом один до одного, то швидше за все зловмисник отримавши доступ до БД, легко отримає доступ і до ОС зі змінними оточення, і навпаки, що нівелює сенс першого варіанту. Якщо ж у вас велика і продумана інфраструктура, доступи до БД і серверів з кодом розмежовані, налаштовані жорсткі групи безпеки, всюди використовуються користувачі з обмеженнями і в БД, і в ОС, то можна отримати вигоду з першого способу. Тому є наступний варіант.
Варіант 3. Змішати обидва варіанти
Можна поєднати краще від двох світів, наприклад так:
hash(hash(salt + secret) + password)
Хоча є й інші варіанти, деталі можна знайти все в тому ж питанні . Але знову ж таки, користі в цьому не сильно більше, ніж у другому варіанті, особливо якщо ваша серверна інфраструктура слабо захищена.
Методи map, forEach, і reduce дійсно мають багато спільного, що і коли використовувати залежить від того який саме результат за підсумком потрібно отримати.
Якщо нам потрібно просто виконати певну дію для кожного елемента масиву, то тут підійде метод forEach :
locations.forEach(location => {
console.log(location)
// мы отримаємо такий вивід на консоль
// по одному для кожнго елемента масиву
// {country: "Україна", population: 2}
// {country: "Китай", population: 100}
// {country: "США", population: 20}
})
Або так, якщо нам потрібно лише певне значення
locations.forEach(location => {
console.log(location.country)
// Україна
// Китай
// США
})
Або так, по кількості значень в масиві
locations.forEach(() => {
console.log("Як справи?")
// Як справи?
// Як справи?
// Як справи?
})
Якщо ми хочемо трансформувати наш масив і занести результати в змінну, то тут метод map буде доречніше ніж forEach, оскільки перший за підсумком відразу поверне новий масив.
Подивимося на різницю forEach vs map в разі коли нам потрібно "обернути" кожну власну назву країни в HTML тег:
// через forEach
const result = []
locations.forEach(location => {
result.push(`<h1>${location.country}</h1>`)
})
console.log(result)
// ["<h1>Україна</h1>", "<h1>Китай</h1>", "<h1>США</h1>"]
Метод reduce відрізняється від map тим що він повертає фінальне "значення" .
Припустимо ми хочемо порахувати сумарний розмір населення:
const result = locations.reduce((total, location) => {
return total + location.population
}, 0)
console.log(result)
// 122
Це досить простий приклад, але "фінальним значенням" може бути не тільки число, а, наприклад, об'єкт або масив, що робить метод reduce дійсно потужним інструментом трансформацій.
При написанні досить великого шматка коду (неважливо, класу, функції або чого-небудь ще) важливим прийомом є декомпозиція. Ви ділите функціональність на логічні частини, і в головній функції складаєте з них, як з цеглинок, загальну логіку.
При цьому самі цеглинки можуть бути не пристосовані для доступу зовні. Наприклад, відкритий метод може перевіряти вхідні параметри, а допоміжним методам-цеглинам це вже не потрібно, оскільки вони викликаються лише зсередини. Якби ці методи були відкритими, в них потрібно було б реалізовувати перевірку параметрів, і при їх імплементації не можна було б розраховувати на те, що вони будуть викликані в контрольований вами момент.
Крім того, внутрішні методи мають право псувати стан класу, якщо ви знаєте, що код який їх викликає потім цю проблему виправить. Якщо зробити ці методи відкритими, користувачі цього класу зможуть зіпсувати внутрішній стан класу без виправлення.
Потім, внутрішні методи можуть не мати нічого спільного з тим, що повинен надавати клас, з його зовнішнім інтерфейсом. Наприклад, якщо клас представляє машину, то відкритий метод в ньому, що переводить дюйми в сантиметри, виглядав би безглуздо. А ось всередині такий переклад цілком може знадобитися.
Ну і в кінці-кінців, публічний метод - це обіцянка для користувачів. Кожна зміна (видалення, зміна сигнатури, а іноді і додавання) відкритого методу - breaking change для клієнтів, вони повинні переглянути код, який використовує ваш клас. Таким чином, ви не повинні просто так, без особливої на то потреби міняти відкриті методи класу. А ось зміни в закритих методах зазвичай відбуваються при рефакторінгу в масовому порядку: методи спрощуються, об'єднуються, розкладаються на декілька, переносяться вгору-вниз по ієрархії, змінюється їх семантика, і все це ніяк не відбивається на користувачах вашого класу.
У документації по Obj-C використовуються терміни class object і class instance. Якщо їх дослівно перекласти, то вийде якраз те, про що Вас запитували. Причому в українській мові природньо об'єкт класу прийнято вважати його экземпляром. У той час, як очевидно, що означає англійський термін class object - цей об'єкт, в якому зберігається, так би мовити, інформація про клас.
Class Objects
A class definition contains various kinds of information, much of it about instances of the class:
The name of the class and its superclass
A template describing a set of instance variables
The declarations of method names and their return and argument types
The method implementations
This information is compiled and recorded in data structures made available to the runtime system . The compiler creates just one object, a class object , to represent the class. The class object has access to all the information about the class , which means mainly information about what instances of the class are like . It's able to produce new instances according to the plan put forward in the class definition .
Although a class object keeps the prototype of a class instance , it's not an instance itself . It has no instance variables of its own and it can not perform methods intended for instances of the class . However, a class definition can include methods intended specifically for the class object -class methods as opposed to instance methods. A class object inherits class methods from the classes above it in the hierarchy , just as instances inherit instance methods.
Основна ідея - поліпшення переносимості. Не гарантується, що на різних системах виконуваний файл буде лежати по шляху, який вказаний в shebang.
Використання env дозволяє знизити цей ризик за рахунок запуску команди на основі даних зі змінної середовища PATH
Більш того, якщо з яких-небудь причин замість стандартного файлу користувач хоче використовувати свій, то йому достатньо додати шлях до цього файлу в PATH без необхідності виправлення скриптів:
В наведеному вище прикладі я скопіював bash до себе в домашню директорію (перейменувавши при цьому файл в python), додав шлях в PATH і запустив python за допомогою env, яка запустила bash, тому що знайшла його раніше.
Ще одним прикладом є використання віртуальних оточень при розробці на Python (virtualenv). Оскільки вони також перебивають PATH, env дозволяє використовувати потрібну версію виконуваного файлу:
~ $ workon black-box-challenge-2016-py2
~ (venv:black-box-challenge-2016-py2) $ env python
Python 2.7.11 (default, Mar 31 2016, 06:18:34)
[GCC 5.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.executable
/home/soon/.virtualenvs/black-box-challenge-2016-py2/bin/python
>>>
Різниця між PUT і POST - це питання семантики. Оскільки для операцій використовуються різні дієслова, то і сенс у них повинен бути різним.
Уявіть, що ваш сервіс оперує поняттями блокнот (notebook) і запис (post). Один блокнот може містити безліч записів.
Для додавання нового запису в блокнот c ідентифікатором id ви будете використовувати метод POST з URL mydomain/notebooks/id/. Ваш сервіс, орієнтуючись на метод POST, сам присвоїть потрібний ідентифікатор запису, додасть її в блокнот і поверне вам URL створеного запису (для доступу до запису по GET або для видалення по DELETE). При цьому добре б повернути клієнту URL створеної записи.
Припустимо, запис з ідентифікатором post-id вже створено і він доступний по URL mydomain/notebooks/id/posts/post-id. Але клієнт (власник запису) виправив в ній помилку і хоче перезаписати її. Для цього він використовує метод PUT з URL mydomain/notebooks/id/posts/post-id і передає оновлений запис в тілі запиту. Ваш сервіс, орієнтуючись на метод PUT видаляє старий запис і записує новий, при цьому він доступний за тим же URL.
Звичайно, ніхто не заважає вам завжди використовувати метод POST (наприклад HTML 4 дозволяв використовувати тільки методи GET і POST). Але все ж варто дотримуватися рекомендацій з метою однакового трактування методів усіма розробниками.
Рекомендується використовуватися метод POST для створення підлеглого ресурсу (дочірнього по відношенню до іншого ресурсу; приклад блокнота і записи якраз дуже підходить).
Отже, якщо ви хочете порівняти рядки на рівність, слід використовувати equals.
Однак в деяких випадках рядки гарантовано представлені одним і тим же об'єктом завдяки пулу рядків (string interning). Ці випадки явно описані в специфікації мови Java .
Оператор == використовується для перевірки, що два рядки вказують на один і той же об'єкт.
// Ці рядки мають одне і те саме значення
new String("test").equals("test") // --> true
// ...але це різні об'єкти
new String("test") == "test" // --> false
// ...ці рядки також різні об'єкти
new String("test") == new String("test") // --> false
// ...але ці рядки вказують а один і той самий об'єкт
// дому що компілятор додає всі літерали в пул
"test" == "test" // --> true
// Об'єднання літералів токож відбувається на стадії компіляції
// тому вони вказують на один об'єкт
"test" == "te" + "st" // --> true
// але виклик substring() відбувається під час виконання,
// в результаті виходять різні об'єкти
"test" == "!test".substring(1) // --> false
// Рядки з пулу можуть буди отримані за допомогою виклику intern().
"test" == "!test".substring(1).intern() // --> true
Треба відзначити, що == помітно швидше, ніж equals (порівняння посилання замість виклику методу і посимвольного порівняння, якщо рядки різної довжини), тому, якщо ви працюєте з рядками з пулу (або системного, або свого), заміна equals на == може привести до помітного прискорення. Але це трапляється дуже рідко.
Остерігайтеся виклику equals на null! Оператор == прекрасно порівнює рядки, якщо один або більше з них дорівнює null, але виклик методу equals на рядок, що дорівнює null, призведе до виключення.
Для порівняння рядків, які можуть бути рівні null, ви можете скористатися наступним методом:
Він присутній в деяких сторонніх бібліотеках, наприклад, в Apache Commons.
Якщо ви користуєтеся сучасними середовищами розробки, то вони попередять, якщо ви спробуєте порівняти рядки за допомогою оператора ==. Завжди звертайте увагу на подібні попередження.
Річ у тому, що в першому випадку у вас ліниве, а в другому - енергійне обчислення відповіді. Це означає, що елементи вихідної послідовності в енергійному випадку обчислюються всі і відразу, а в ледачому випадку - тільки коли запитані і тільки ті, що запитані.
Подивімося, де з практичного боку є різниця.
Для випадку ледачого обчислення вся послідовність не присутня повністю в пам'яті. Це означає, що при обробці по елементах у нас не виділяється пам'ять, і зберігається cache locality:
IEnumerable GenerateHugeSequenceLazy()
{
for (int i = 0; i < 1000000; i++)
yield return 13 * i;
}
IEnumerable GenerateHugeSequenceEager()
{
var result = new List();
for (int i = 0; i < 1000000; i++)
result.Add(13 * i);
return result;
}
Обчислюємо функцію на всій послідовності, порівнюємо витрата пам'яті:
var seqLazy = GenerateHugeSequenceLazy();
// вичисляємо максимум вручну
var max = 0;
foreach (var v in seqLazy)
if (v > max)
max = v;
var memLazy = GC.GetTotalMemory(forceFullCollection: false);
var seqEager = GenerateHugeSequenceEager();
// вичисляємо максимум вручну
max = 0;
foreach (var v in seqEager)
if (v > max)
max = v;
var memEager = GC.GetTotalMemory(forceFullCollection: false);
Console.WriteLine($"Memory footprint lazy: , eager: ");
Результат:
Memory footprint lazy: 29868, eager: 6323088
Далі, у нас досить великі відмінності в розумінні операцій. Енергійні обчислення проводяться в момент виклику функції, в той час, як ледачі обчислення відбуваються в момент, коли ви користуєтеся результатом. А значить, для реального обчислення ледачої послідовності стан аргументів буде взято на момент перерахування. Ось приклад:
IEnumerable DoubleEager(IEnumerable seq)
{
var result = new List();
foreach (var e in seq)
result.Add(e * 2);
return result;
}
IEnumerable DoubleLazy(IEnumerable seq)
{
foreach (var e in seq)
yield return e * 2;
}
Дивимося на відмінності:
var seq = new List() { 1 };
var eagerlyDoubled = DoubleEager(seq);
var lazilyDoubled = DoubleLazy(seq);
Console.WriteLine("Eager: " + string.Join(" ", eagerlyDoubled));
Console.WriteLine("Lazy : " + string.Join(" ", lazilyDoubled));
// виведе обидва рази 2, поки відмінностей немає
seq.Add(2); // змінюємо вихідну послідовність
Console.WriteLine("Eager: " + string.Join(" ", eagerlyDoubled)); // 2
Console.WriteLine("Lazy : " + string.Join(" ", lazilyDoubled)); // 2 4
Оскільки ліниве обчислення відбувається при перерахуванні, ми бачимо, що при зміні послідовності лінива версія підхоплює зміни.
Інший приклад. Подивимося, що буде, якщо ми не обчислюємо всю послідовність. Обчислимо одну і ту ж послідовність енергійно та ліниво:
IEnumerable Eager10()
{
Console.WriteLine("Eager");
int counter = 0;
try
{
var result = new List();
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Adding: ");
counter++;
result.Add(i);
}
return result;
}
finally
{
Console.WriteLine($"Eagerly computed: ");
}
}
IEnumerable Lazy10()
{
Console.WriteLine("Lazy");
int counter = 0;
try
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Adding: ");
counter++;
yield return i;
}
}
finally
{
Console.WriteLine($"Lazily computed: ");
}
}
Беремо тільки 2 елементи з результату:
foreach (var e in Eager10().Take(2))
Console.WriteLine($"Obtained: ");
foreach (var e in Lazy10().Take(2))
Console.WriteLine($"Obtained: ");
foreach (var e in Lazy10())
{
Console.WriteLine($"Obtained: ");
if (e == 1)
break;
}
Бачите різницю? Лінивий варіант прогнав цикл всього два рази, і не обчислював «хвіст» послідовності.
Ще одна різниця між випадками - коли повідомляються помилки. У разі енергійного обчислення вони повідомляються відразу. У разі ледачого - лише при перерахуванні результату. приклад:
IEnumerable CheckEagerly(int value)
{
if (value == 0)
throw new ArgumentException("value cannot be 0");
return new List { value };
}
IEnumerable CheckLazily(int value)
{
if (value == 0)
throw new ArgumentException("value cannot be 0");
yield return value;
}
Застосовуємо try / catch:
Console.WriteLine("Eager:");
IEnumerable seqEager = null;
try
{
seqEager = CheckEagerly(0);
}
catch (ArgumentException)
{
Console.WriteLine("Exception caught");
}
if (seqEager != null)
foreach (var e in seqEager)
Console.WriteLine(e);
Console.WriteLine("Lazy:");
IEnumerable seqLazy = null;
try
{
seqLazy = CheckLazily(0);
}
catch (ArgumentException)
{
Console.WriteLine("Exception caught");
}
if (seqLazy != null)
foreach (var e in seqLazy)
Console.WriteLine(e);
Отримуємо результат:
Eager:
Exception caught
Lazy:
Unhandled Exception: System.ArgumentException: value cannot be 0
at Program.d__3.MoveNext() in ...\Program.cs:line 59
at Program.Run() in ...\Program.cs:line 45
at Program.Main(String[] args) in ...\Program.cs:line 13
Для того, щоб отримати «найкраще з обох світів», тобто, ліниве обчислення, але енергійну перевірку аргументів, найпростіше розділити функцію на дві: енергійну перевірку і ліниве обчислення без перевірки. Для сучасних версій C# зручно використовувати вкладені функції:
IEnumerable CheckEagerlyEnumerateLazily(int value)
{
if (value == 0)
throw new ArgumentException("value cannot be 0");
return Impl();
IEnumerable Impl()
{
yield return value;
}
}
Перевіряємо:
Console.WriteLine("Recommended way:");
IEnumerable seqLazy = null;
try
{
seqLazy = CheckEagerlyEnumerateLazily(0);
}
catch (ArgumentException)
{
Console.WriteLine("Exception caught");
}
if (seqLazy != null)
foreach (var e in seqLazy)
Console.WriteLine(e);
і отримуємо
Recommended way:
Exception caught
Ще один випадок відмінності - залежність від зовнішніх даних в процесі обчислення. Наступний код намагається впливати на обчислення, змінюючи глобальний стан. (Це не дуже хороший код, не робіть так в реальних програмах!)
bool evilMutableAllowCompute;
IEnumerable EagerGet5WithExternalDependency()
{
List result = new List();
for (int i = 0; i < 5; i++)
{
if (evilMutableAllowCompute)
result.Add(i);
}
return result;
}
IEnumerable LazyGet5WithExternalDependency()
{
for (int i = 0; i < 5; i++)
{
if (evilMutableAllowCompute)
yield return i;
}
}
Використовуємо:
Console.WriteLine("Eager:");
evilMutableAllowCompute = true;
foreach (var e in EagerGet5WithExternalDependency())
{
Console.WriteLine($"Obtained: ");
if (e > 0)
evilMutableAllowCompute = false;
}
Console.WriteLine("Lazy:");
evilMutableAllowCompute = true;
foreach (var e in LazyGet5WithExternalDependency())
{
Console.WriteLine($"Obtained: ");
if (e > 0)
evilMutableAllowCompute = false;
}
-n - не виводити шапку таблиці -i - приймає вказівку розділу, додаємо необхідний розділ після ключа /dev/s... --output - самостійна побудова таблиці виведення PKNAME - ім'я батьківського пристрою
Python широко поширений у багатьох сферах: від системного адміністрування до Data Science.
Веб розробка
Найчастіше Python використовується в веб-розробці. Для роботи з ним підключають фреймворки: Tornado, Pylons, TurboGears, Flask, CherryPy і - найпопулярніший - Django.
Існують і рушії для створення сайтів на Python:
Abilian SBE;
Ella;
Saleor;
Wagtail;
Django-CMS.
Також на Python пишуть парсери для збору інформації в інтернеті.
Програми
Хоч мова і не компілюється, за допомогою неї все одно створюють десктопні програми. Ось, наприклад, що було розроблено на Python:
GIMP - візуальний редактор на Linux;
Ubuntu Software Center - центр застосунків в ОС Ubuntu (один з дистрибутивів Linux);
BitTorrent до 6 версії - менеджер торрент-закачувань (пізніше програму переписали на C++, але мережі peer-to-peer все ще працюють на Python);
Blender - програма для створення 3D-графіки.
Мобільні застосунки
Мобільна розробка на Python менш популярна. Для Android частіше пишуть на Java, C #, C++ або Kotlin, а для iOS - на Swift або Objective-C. На Python зазвичай програмують серверну частину програми. Наприклад, клієнт Instagram для iOS написаний на Objective-C, а сервер - на Python.
Ігри
Багато комп'ютерних ігор були повністю або частково написані на Python. Існує хибна думка, що ця мова не підходить для серйозних проєктів, але насправді вона використовувався в розробці таких хітів, як:
Battlefield 2;
World of Tanks;
Civilization IV;
EVE Online.
Попри можливість реалізації призначеного для користувача інтерфейсу і роботи з графікою, на Python в основному пишуть скрипти - наприклад, взаємодії персонажів, запуску сцен, а також обробки подій.
Вбудовані системи (embedded systems)
На Python розробляють вбудовані системи для різних пристроїв. Наприклад, мова прижилася в Raspberry Pi (комп'ютер розміром з карту пам'яті).
Ще проєкти з вбудованою системою на Python:
The Owl Embedded Python System;
Python Embedded Tools;
Embedded Python.
Мова застосовується у вбудованих системах верстатів з ЧПУ, засобах автоматичного регулювання (температури, витрати рідини, тиску і так далі) і в телекомунікаційному обладнанні.
Створення скриптів
Python підходить для написання плагінів і скриптів до вже готових програмах. Наприклад, для реалізації ігрової логіки або створення додаткових модулів. Скрипти на цьому мови вбудовують і в програми на інших мовах, щоб автоматизувати будь-які завдання.
Системне адміністрування
Системним адміністраторам Python потрібен для автоматизації завдань. Він простий, потужний і підтримує спеціальні пакети, які підвищують його ефективність. І, найголовніше, він за замовчуванням встановлений на всі сервери з ОС Linux.
Завдяки лаконічності Python можна швидко прочитати код і знайти слабкі місця. Форматування в мові - частина синтаксису.
Наукові дослідження
В Python є кілька бібліотек, які стануть в пригоді для проведення досліджень і обчислень:
SciPy - бібліотека з науковими інструментами;
NumPy - розширення, яке додає підтримку матриць і багатовимірних масивів, а також математичні функції для роботи з ними;
Matplotlib - бібліотека для роботи з 2D- і 3D-графікою.
Завдяки бібліотекам і простоті освоєння мови багато вчених вибирають Python - особливо він популярний у математиків і фізиків.
Data Science
Python - один з найбільш використовуваних в Data Science мов. На ньому пишуть алгоритми програм з машинним навчанням і аналітичні програми. За допомогою нього обслуговують сховища даних і хмарні сервіси.
Також він допомагає парсити дані з інтернету. Наприклад, в Google Python застосовують для індексації сайтів.
До вищесказаного можна додати наступне. Далеко не всі пулл-реквести приймаються розробниками. Тут потрібно дотримати ряд правил:
Пулл-реквест (ПР) повинен бути добре оформлений і містити вичерпний опис.
Звичайне правило, один баг - один ПР, одна фіча - один ПР. Не потрібно намагатися запхати відразу купу всього.
Дуже важливо дотримуватися Code Style того проєктe, для якого ви робите ПР. Нехай навіть він здається вам протиприродним (наприклад ви завжди робите відступи у вигляді 4 пробіли, а в проєкті таби).
Не потрібно боятися робити ПР-и, адже допомогти можна навіть в дрібниці. Наприклад ви знайшли помилку перекладу в readme файлі або вам здається що якийсь опис фічі можна зрозумілішо перефразувати.
На гітхабі мільйони проєктів, які живуть виключно на ентузіазмі творців, хороші ПР-и дуже добре стимулюють цей ентузіазм)
Ви зробили форк його сховища (тобто скопіювали до себе).
Ви зробили якісь круті зміни у своєму репозиторії.
Тепер якщо ви хочете, щоб крутий дядько вніс ваші круті зміни у свій крутий код. І ви просите, щоб він взяв ваші зміни, тобто зробив git pull. Це і називається pull request
Алгоритмічна рекурсія - це за визначенням алгоритм, побудований зі стратегії Розділяй та володарюй (Divide-and-Conquer), в якому для зберігання підзадач використана LIFO структура даних, тобто, попросту кажучи, стек.
У таких алгоритмів натуральним чином є прямий хід, коли задача розбивається на дрібніші підзадачі, які заносяться в стек. Потім підзадачі витягуються зі стека по одній і вирішуються за таким же принципом, тобто розбиваються на ще дрібніші підзадачі. Розбиття триває до тих пір, поки підзадача не стане розв'язуватися тривіально.
А також у рекурсивних алгоритмів є зворотний хід, коли все дрібніші підзадачі вже вирішені і їх рішення тепер можна об'єднати в рішення більше завдання.
Наявність зворотного ходу (backtracking) - відмінна риса саме рекурсивних алгоритмів. Тобто в рекурсії завжди є зворотний хід. Ось, власне, "при чому тут рекурсія".
Не всім рекурсивним алгоритмам потрібен цей зворотний хід - деяким алгоритмам просто нічого не потрібно робити на зворотному ході. Але сам процес зворотного ходу в рекурсивному алгоритмі завжди присутній, хай навіть і незримо.
Тому якщо ви розглядаєте якийсь рекурсивний алгоритм (перебору або чого-небудь ще), то там завжди обов'язково буде і "повернення" (тобто зворотний хід). А вже як він використовується в вашому алгоритмі та чи використовується взагалі - залежить від алгоритму.
У рекурсивному алгоритмі розставлення N ферзів на шахівниці, підзадачею є завдання розставлення m ферзів що залишилися (m < = N), коли перші N - m ферзів вже якось розставлені. Зворотний хід в цьому алгоритмі використовується тоді, коли ми раптом з'ясували, що підзадача не має рішення - ми повертаємо цей результат на попередній рівень рекурсії в процесі зворотного ходу і тим самим говоримо йому, що треба спробувати якийсь інший варіант розставлення N - m перших ферзів. А також, якщо вам доручено знайти всі можливі розстановки (а не якусь одну), зворотний хід рекурсії буде використовуватися для тих же цілей незалежно від того, чи успішно вирішена підзадача, чи ні.
Для вибору необхідної версії скористайтеся командою
І далі з запропонованого списку виберіть ту яка вам необхідна
Для цього вам необхідно додати PPA репозиторій
ppa:ondrej/php
Після чого ви зможете встановити останню версію php командою
Це можна зробити наступним чином
Розглянемо варіанти детальніше
Варіант 1. Зберігати одну загальну сіль для всіх паролів користувачів поза БД
На перший це виглядає безпечніше, адже якщо хтось отримає доступ до БД, то солі у ньому не буде, і напевно відновити паролі не зможе? ..
Але якщо зловмисник отримає доступ і до БД, і до солі зі змінної середовища, то для добування паролів все одно треба буде будувати райдужну таблицю. І якщо у вас одна сіль на всіх користувачів, то це зробити набагато простіше. Інша проблема з безпекою: якщо зловмисник (або його співучасник/жертва) має доступ до системи як користувач, то він вже знає 1 пароль і його хеш, можливо зможе змінювати пароль і дивитися зміну хеша, знаходити в БД інших користувачів з таким же паролем...
Цей варіант схожий на принцип Security through obscurity (безпека через неясність/заплутування), а цього принципу радять уникати і покладатися на сильніші криптографічні методи.
Варіант 2. Зберігати свою для кожного користувача сіль в БД з хешамі паролів
В цьому випадку, якщо зловмисник отримає доступ до БД, то разом з хешами паролів він отримає і солі до них. Але тепер для отримання паролів треба буде згенерувати не одну райдужну таблицю на всіх, а для кожного користувача свою! При цьому, важливо коректно реалізувати свою систему роботи з користувачами: при зміні пароля треба міняти і сіль, щоб навіть маючи стару версію БД, зловмисник вже не зміг отримати з цього вигоду.
Цей же спосіб використовується в Django, також його радять і обгрунтовують в аналогічному питанні на enSO - раджу почитати, там обговорені різні варіанти і їх обгрунтування.
Питання насправді спірне і залежить від реалізації вашої системи. Якщо у вас лише 1 або кілька серверів з повним або слабо захищеним доступом один до одного, то швидше за все зловмисник отримавши доступ до БД, легко отримає доступ і до ОС зі змінними оточення, і навпаки, що нівелює сенс першого варіанту. Якщо ж у вас велика і продумана інфраструктура, доступи до БД і серверів з кодом розмежовані, налаштовані жорсткі групи безпеки, всюди використовуються користувачі з обмеженнями і в БД, і в ОС, то можна отримати вигоду з першого способу. Тому є наступний варіант.
Варіант 3. Змішати обидва варіанти
Можна поєднати краще від двох світів, наприклад так:
Хоча є й інші варіанти, деталі можна знайти все в тому ж питанні . Але знову ж таки, користі в цьому не сильно більше, ніж у другому варіанті, особливо якщо ваша серверна інфраструктура слабо захищена.
Методи
map
,forEach
, іreduce
дійсно мають багато спільного, що і коли використовувати залежить від того який саме результат за підсумком потрібно отримати.Уявімо що у нас є масив:
Якщо нам потрібно просто виконати певну дію для кожного елемента масиву, то тут підійде метод
forEach
:Або так, якщо нам потрібно лише певне значення
Або так, по кількості значень в масиві
Якщо ми хочемо трансформувати наш масив і занести результати в змінну, то тут метод
map
буде доречніше ніжforEach
, оскільки перший за підсумком відразу поверне новий масив.Подивимося на різницю
forEach
vsmap
в разі коли нам потрібно "обернути" кожну власну назву країни в HTML тег:З використанням
map
Метод
reduce
відрізняється відmap
тим що він повертає фінальне "значення" .Припустимо ми хочемо порахувати сумарний розмір населення:
Це досить простий приклад, але "фінальним значенням" може бути не тільки число, а, наприклад, об'єкт або масив, що робить метод
reduce
дійсно потужним інструментом трансформацій.При написанні досить великого шматка коду (неважливо, класу, функції або чого-небудь ще) важливим прийомом є декомпозиція. Ви ділите функціональність на логічні частини, і в головній функції складаєте з них, як з цеглинок, загальну логіку.
При цьому самі цеглинки можуть бути не пристосовані для доступу зовні. Наприклад, відкритий метод може перевіряти вхідні параметри, а допоміжним методам-цеглинам це вже не потрібно, оскільки вони викликаються лише зсередини. Якби ці методи були відкритими, в них потрібно було б реалізовувати перевірку параметрів, і при їх імплементації не можна було б розраховувати на те, що вони будуть викликані в контрольований вами момент.
Крім того, внутрішні методи мають право псувати стан класу, якщо ви знаєте, що код який їх викликає потім цю проблему виправить. Якщо зробити ці методи відкритими, користувачі цього класу зможуть зіпсувати внутрішній стан класу без виправлення.
Потім, внутрішні методи можуть не мати нічого спільного з тим, що повинен надавати клас, з його зовнішнім інтерфейсом. Наприклад, якщо клас представляє машину, то відкритий метод в ньому, що переводить дюйми в сантиметри, виглядав би безглуздо. А ось всередині такий переклад цілком може знадобитися.
Ну і в кінці-кінців, публічний метод - це обіцянка для користувачів. Кожна зміна (видалення, зміна сигнатури, а іноді і додавання) відкритого методу - breaking change для клієнтів, вони повинні переглянути код, який використовує ваш клас. Таким чином, ви не повинні просто так, без особливої на то потреби міняти відкриті методи класу. А ось зміни в закритих методах зазвичай відбуваються при рефакторінгу в масовому порядку: методи спрощуються, об'єднуються, розкладаються на декілька, переносяться вгору-вниз по ієрархії, змінюється їх семантика, і все це ніяк не відбивається на користувачах вашого класу.
У документації по Obj-C використовуються терміни
class object
іclass instance
. Якщо їх дослівно перекласти, то вийде якраз те, про що Вас запитували. Причому в українській мові природньо об'єкт класу прийнято вважати його экземпляром. У той час, як очевидно, що означає англійський термінclass object
- цей об'єкт, в якому зберігається, так би мовити, інформація про клас.Основна ідея - поліпшення переносимості. Не гарантується, що на різних системах виконуваний файл буде лежати по шляху, який вказаний в shebang.
Використання
env
дозволяє знизити цей ризик за рахунок запуску команди на основі даних зі змінної середовищаPATH
Більш того, якщо з яких-небудь причин замість стандартного файлу користувач хоче використовувати свій, то йому достатньо додати шлях до цього файлу в
PATH
без необхідності виправлення скриптів:В наведеному вище прикладі я скопіював
bash
до себе в домашню директорію (перейменувавши при цьому файл вpython
), додав шлях вPATH
і запустивpython
за допомогоюenv
, яка запустилаbash
, тому що знайшла його раніше.Ще одним прикладом є використання віртуальних оточень при розробці на Python (virtualenv). Оскільки вони також перебивають
PATH
,env
дозволяє використовувати потрібну версію виконуваного файлу:Різниця між PUT і POST - це питання семантики. Оскільки для операцій використовуються різні дієслова, то і сенс у них повинен бути різним.
Уявіть, що ваш сервіс оперує поняттями блокнот (notebook) і запис (post). Один блокнот може містити безліч записів.
Для додавання нового запису в блокнот c ідентифікатором id ви будете використовувати метод POST з URL mydomain/notebooks/id/. Ваш сервіс, орієнтуючись на метод POST, сам присвоїть потрібний ідентифікатор запису, додасть її в блокнот і поверне вам URL створеного запису (для доступу до запису по GET або для видалення по DELETE). При цьому добре б повернути клієнту URL створеної записи.
Припустимо, запис з ідентифікатором post-id вже створено і він доступний по URL mydomain/notebooks/id/posts/post-id. Але клієнт (власник запису) виправив в ній помилку і хоче перезаписати її. Для цього він використовує метод PUT з URL mydomain/notebooks/id/posts/post-id і передає оновлений запис в тілі запиту. Ваш сервіс, орієнтуючись на метод PUT видаляє старий запис і записує новий, при цьому він доступний за тим же URL.
Звичайно, ніхто не заважає вам завжди використовувати метод POST (наприклад HTML 4 дозволяв використовувати тільки методи GET і POST). Але все ж варто дотримуватися рекомендацій з метою однакового трактування методів усіма розробниками.
Рекомендується використовуватися метод POST для створення підлеглого ресурсу (дочірнього по відношенню до іншого ресурсу; приклад блокнота і записи якраз дуже підходить).
Оператор
==
порівнює посилання.Метод
equals
порівнює значення.Отже, якщо ви хочете порівняти рядки на рівність, слід використовувати
equals
.Однак в деяких випадках рядки гарантовано представлені одним і тим же об'єктом завдяки пулу рядків (string interning). Ці випадки явно описані в специфікації мови Java .
Оператор
==
використовується для перевірки, що два рядки вказують на один і той же об'єкт.Треба відзначити, що
==
помітно швидше, ніжequals
(порівняння посилання замість виклику методу і посимвольного порівняння, якщо рядки різної довжини), тому, якщо ви працюєте з рядками з пулу (або системного, або свого), замінаequals
на==
може привести до помітного прискорення. Але це трапляється дуже рідко.Остерігайтеся виклику
equals
наnull
! Оператор==
прекрасно порівнює рядки, якщо один або більше з них дорівнюєnull
, але виклик методуequals
на рядок, що дорівнюєnull
, призведе до виключення.Для порівняння рядків, які можуть бути рівні null, ви можете скористатися наступним методом:
Він присутній в деяких сторонніх бібліотеках, наприклад, в Apache Commons.
Якщо ви користуєтеся сучасними середовищами розробки, то вони попередять, якщо ви спробуєте порівняти рядки за допомогою оператора
==
. Завжди звертайте увагу на подібні попередження.Відмінності насправді кардинальні.
Річ у тому, що в першому випадку у вас ліниве, а в другому - енергійне обчислення відповіді. Це означає, що елементи вихідної послідовності в енергійному випадку обчислюються всі і відразу, а в ледачому випадку - тільки коли запитані і тільки ті, що запитані.
Подивімося, де з практичного боку є різниця.
Для випадку ледачого обчислення вся послідовність не присутня повністю в пам'яті. Це означає, що при обробці по елементах у нас не виділяється пам'ять, і зберігається cache locality:
Обчислюємо функцію на всій послідовності, порівнюємо витрата пам'яті:
Результат:
Далі, у нас досить великі відмінності в розумінні операцій. Енергійні обчислення проводяться в момент виклику функції, в той час, як ледачі обчислення відбуваються в момент, коли ви користуєтеся результатом. А значить, для реального обчислення ледачої послідовності стан аргументів буде взято на момент перерахування. Ось приклад:
Дивимося на відмінності:
Оскільки ліниве обчислення відбувається при перерахуванні, ми бачимо, що при зміні послідовності лінива версія підхоплює зміни.
Інший приклад. Подивимося, що буде, якщо ми не обчислюємо всю послідовність. Обчислимо одну і ту ж послідовність енергійно та ліниво:
Беремо тільки 2 елементи з результату:
Отримуємо такий висновок на консоль:
Бачите різницю? Лінивий варіант прогнав цикл всього два рази, і не обчислював «хвіст» послідовності.
Ще одна різниця між випадками - коли повідомляються помилки. У разі енергійного обчислення вони повідомляються відразу. У разі ледачого - лише при перерахуванні результату. приклад:
Застосовуємо try / catch:
Отримуємо результат:
Для того, щоб отримати «найкраще з обох світів», тобто, ліниве обчислення, але енергійну перевірку аргументів, найпростіше розділити функцію на дві: енергійну перевірку і ліниве обчислення без перевірки. Для сучасних версій C# зручно використовувати вкладені функції:
Перевіряємо:
і отримуємо
Ще один випадок відмінності - залежність від зовнішніх даних в процесі обчислення. Наступний код намагається впливати на обчислення, змінюючи глобальний стан. (Це не дуже хороший код, не робіть так в реальних програмах!)
Використовуємо:
Результат:
Ми бачимо, що зміна глобальних даних навіть після формального відпрацювання ледачої функції може впливати на обчислення.
(Це ще один аргумент на користь того, що функціональне програмування і мутабельний стан погано поєднуються.)
У Вашому випадку:
Що означає
Команда:
Приклад:
Пропоную використовувати інструмент
lsblk
-n
- не виводити шапку таблиці-i
- приймає вказівку розділу, додаємо необхідний розділ після ключа /dev/s...--output
- самостійна побудова таблиці виведенняPKNAME
- ім'я батьківського пристроюВ результаті виконуємо команду:
Виведе:
Python широко поширений у багатьох сферах: від системного адміністрування до Data Science.
Веб розробка
Найчастіше Python використовується в веб-розробці. Для роботи з ним підключають фреймворки: Tornado, Pylons, TurboGears, Flask, CherryPy і - найпопулярніший - Django.
Існують і рушії для створення сайтів на Python:
Також на Python пишуть парсери для збору інформації в інтернеті.
Програми
Хоч мова і не компілюється, за допомогою неї все одно створюють десктопні програми. Ось, наприклад, що було розроблено на Python:
Мобільні застосунки
Мобільна розробка на Python менш популярна. Для Android частіше пишуть на Java, C #, C++ або Kotlin, а для iOS - на Swift або Objective-C. На Python зазвичай програмують серверну частину програми. Наприклад, клієнт Instagram для iOS написаний на Objective-C, а сервер - на Python.
Ігри
Багато комп'ютерних ігор були повністю або частково написані на Python. Існує хибна думка, що ця мова не підходить для серйозних проєктів, але насправді вона використовувався в розробці таких хітів, як:
Попри можливість реалізації призначеного для користувача інтерфейсу і роботи з графікою, на Python в основному пишуть скрипти - наприклад, взаємодії персонажів, запуску сцен, а також обробки подій.
Вбудовані системи (embedded systems)
На Python розробляють вбудовані системи для різних пристроїв. Наприклад, мова прижилася в Raspberry Pi (комп'ютер розміром з карту пам'яті).
Ще проєкти з вбудованою системою на Python:
Мова застосовується у вбудованих системах верстатів з ЧПУ, засобах автоматичного регулювання (температури, витрати рідини, тиску і так далі) і в телекомунікаційному обладнанні.
Створення скриптів
Python підходить для написання плагінів і скриптів до вже готових програмах. Наприклад, для реалізації ігрової логіки або створення додаткових модулів. Скрипти на цьому мови вбудовують і в програми на інших мовах, щоб автоматизувати будь-які завдання.
Системне адміністрування
Системним адміністраторам Python потрібен для автоматизації завдань. Він простий, потужний і підтримує спеціальні пакети, які підвищують його ефективність. І, найголовніше, він за замовчуванням встановлений на всі сервери з ОС Linux.
Завдяки лаконічності Python можна швидко прочитати код і знайти слабкі місця. Форматування в мові - частина синтаксису.
Наукові дослідження
В Python є кілька бібліотек, які стануть в пригоді для проведення досліджень і обчислень:
Завдяки бібліотекам і простоті освоєння мови багато вчених вибирають Python - особливо він популярний у математиків і фізиків.
Data Science
Python - один з найбільш використовуваних в Data Science мов. На ньому пишуть алгоритми програм з машинним навчанням і аналітичні програми. За допомогою нього обслуговують сховища даних і хмарні сервіси.
Також він допомагає парсити дані з інтернету. Наприклад, в Google Python застосовують для індексації сайтів.
До вищесказаного можна додати наступне. Далеко не всі пулл-реквести приймаються розробниками. Тут потрібно дотримати ряд правил:
Пулл-реквест (ПР) повинен бути добре оформлений і містити вичерпний опис.
Звичайне правило, один баг - один ПР, одна фіча - один ПР. Не потрібно намагатися запхати відразу купу всього.
Дуже важливо дотримуватися Code Style того проєктe, для якого ви робите ПР. Нехай навіть він здається вам протиприродним (наприклад ви завжди робите відступи у вигляді 4 пробіли, а в проєкті таби).
Не потрібно боятися робити ПР-и, адже допомогти можна навіть в дрібниці. Наприклад ви знайшли помилку перекладу в readme файлі або вам здається що якийсь опис фічі можна зрозумілішо перефразувати.
На гітхабі мільйони проєктів, які живуть виключно на ентузіазмі творців, хороші ПР-и дуже добре стимулюють цей ентузіазм)
Поясню на простому прикладі
Тепер якщо ви хочете, щоб крутий дядько вніс ваші круті зміни у свій крутий код. І ви просите, щоб він взяв ваші зміни, тобто зробив
git pull
. Це і називається pull requestАлгоритмічна рекурсія - це за визначенням алгоритм, побудований зі стратегії Розділяй та володарюй (Divide-and-Conquer), в якому для зберігання підзадач використана LIFO структура даних, тобто, попросту кажучи, стек.
У таких алгоритмів натуральним чином є прямий хід, коли задача розбивається на дрібніші підзадачі, які заносяться в стек. Потім підзадачі витягуються зі стека по одній і вирішуються за таким же принципом, тобто розбиваються на ще дрібніші підзадачі. Розбиття триває до тих пір, поки підзадача не стане розв'язуватися тривіально.
А також у рекурсивних алгоритмів є зворотний хід, коли все дрібніші підзадачі вже вирішені і їх рішення тепер можна об'єднати в рішення більше завдання.
Наявність зворотного ходу (backtracking) - відмінна риса саме рекурсивних алгоритмів. Тобто в рекурсії завжди є зворотний хід. Ось, власне, "при чому тут рекурсія".
Не всім рекурсивним алгоритмам потрібен цей зворотний хід - деяким алгоритмам просто нічого не потрібно робити на зворотному ході. Але сам процес зворотного ходу в рекурсивному алгоритмі завжди присутній, хай навіть і незримо.
Тому якщо ви розглядаєте якийсь рекурсивний алгоритм (перебору або чого-небудь ще), то там завжди обов'язково буде і "повернення" (тобто зворотний хід). А вже як він використовується в вашому алгоритмі та чи використовується взагалі - залежить від алгоритму.
У рекурсивному алгоритмі розставлення
N
ферзів на шахівниці, підзадачею є завдання розставленняm
ферзів що залишилися(m < = N)
, коли першіN - m
ферзів вже якось розставлені. Зворотний хід в цьому алгоритмі використовується тоді, коли ми раптом з'ясували, що підзадача не має рішення - ми повертаємо цей результат на попередній рівень рекурсії в процесі зворотного ходу і тим самим говоримо йому, що треба спробувати якийсь інший варіант розставленняN - m
перших ферзів. А також, якщо вам доручено знайти всі можливі розстановки (а не якусь одну), зворотний хід рекурсії буде використовуватися для тих же цілей незалежно від того, чи успішно вирішена підзадача, чи ні.Підписуйтесь на щотижневу розсилку
Отримуйте найкращі статті тижня на поштуПідписуйтесь на щотижневу розсилку