У Perl, як я стисло перевіряю, чи визначена змінна $ і містить рядок не нульової довжини?


83

На даний момент я використовую наступний Perl, щоб перевірити, чи визначена змінна та містить текст. definedСпочатку потрібно перевірити, щоб уникнути попередження „неініціалізоване значення“:

if (defined $name && length $name > 0) {
    # do something with $name
}

Чи є кращий (імовірно, більш стислий) спосіб написати це?

Відповіді:


78

Ви часто бачите перевірку на визначеність, тому вам не доведеться мати справу з попередженням про використання значення undef (а в Perl 5.10 воно повідомляє вам про змінну, що порушує):

 Use of uninitialized value $name in ...

Отже, щоб обійти це попередження, люди придумують всілякі коди, і цей код починає виглядати як важлива частина рішення, а не як жувальна гумка та клейка стрічка. Іноді краще показати, що ви робите, явно вимкнувши попередження, якого ви намагаєтеся уникати:

 {
 no warnings 'uninitialized';

 if( length $name ) {
      ...
      }
 }

В інших випадках використовуйте якесь нульове значення замість даних. За допомогою оператора визначеного або Perl 5.10 ви можете вказати lengthявний порожній рядок (визначений і повернути нульову довжину) замість змінної, яка ініціюватиме попередження:

 use 5.010;

 if( length( $name // '' ) ) {
      ...
      }

У Perl 5.12 це трохи простіше, оскільки lengthдля невизначеного значення також повертається undefined . Це може здатися трохи глупотою, але це радує математика, яким я, можливо, хотів би стати. Це не видає попередження, тому це питання існує.

use 5.012;
use warnings;

my $name;

if( length $name ) { # no warning
    ...
    }

4
Крім того, у версії 5.12 та пізніших length undefвертає undef, замість попередження та повертає 0. У логічному контексті undef так само хибний, як 0, тому якщо ви націлюєтесь на версію v5.12 або пізнішу, ви можете просто написатиif (length $name) { ... }
rjbs

24

Як вказує mobrule, замість невеликої економії ви можете використовувати наступне:

if (defined $name && $name ne '') {
    # do something with $name
}

Ви можете скинути визначений чек і отримати щось ще коротше, наприклад:

if ($name ne '') {
    # do something with $name
}

Але у випадку, коли $nameце не визначено, хоча логічний потік буде працювати так, як передбачалося, якщо ви використовуєте warnings(і вам слід це зробити), ви отримаєте таке застереження:

Використання неініціалізованого значення у рядку ne

Отже, якщо є шанс, який $nameможе бути не визначений, вам дійсно потрібно перевірити визначеність насамперед, щоб уникнути цього попередження. Як зазначає Сінан Унюр, ви можете використовувати Scalar :: MoreUtils, щоб отримати код, який робить саме це (перевіряє визначеність, а потім перевіряє нульову довжину), прямо за допомогою empty()методу:

use Scalar::MoreUtils qw(empty);
if(not empty($name)) {
    # do something with $name 
}

17

По-перше, оскільки lengthзавжди повертає невід’ємне число,

if ( length $name )

і

if ( length $name > 0 )

еквівалентні.

Якщо у вас все в порядку із заміною невизначеного значення порожнім рядком, ви можете використовувати //=оператор Perl 5.10, який призначає RHS LHS, якщо не визначено LHS:

#!/usr/bin/perl

use feature qw( say );
use strict; use warnings;

my $name;

say 'nonempty' if length($name //= '');
say "'$name'";

Зверніть увагу на відсутність попереджень про неініціалізовану змінну, оскільки $nameїй присвоюється порожній рядок, якщо вона невизначена.

Однак, якщо ви не хочете залежати від встановленої версії 5.10, використовуйте функції, надані Scalar :: MoreUtils . Наприклад, вищезазначене можна записати як:

#!/usr/bin/perl

use strict; use warnings;

use Scalar::MoreUtils qw( define );

my $name;

print "nonempty\n" if length($name = define $name);
print "'$name'\n";

Якщо ви не хочете клаббер $name, використовуйте default.


+1 за згадку "// =" (звідки я знав, що це відповідь Синана :)
DVK

4
Я б не використовував // = в цьому випадку, оскільки це змінює дані як побічний ефект. Замість цього використовуйте трохи коротший length( $name // '' ).
brian d foy

@brian d'foy Я думаю, це залежить від того, що робиться у функції.
Sinan Ünür

+1 Оператори //and та //=, можливо, найкорисніші спеціалізовані оператори, що існують.
Кріс Луц

1
Як зазначив @rjbs у своїй відповіді, з v5.12 і пізніше lengthтепер може повернути щось, що не є числом (але не NaN;)
Брайан Д Фой,

6

У випадках, коли мені байдуже, чи є змінна undefрівною '', я зазвичай підсумовую це як:

$name = "" unless defined $name;
if($name ne '') {
  # do something with $name
}

У Perl 5.10 це можна скоротити, саме до $name //= "";цього саме опублікував Сінан.
Кріс Луц

І навіть якщо у вас немає perl 5.10, ви все одно можете писати$name ||= "";
RET

1
@RET: ви не можете використовувати || Оператор тут, оскільки він замінює рядок "0" на "". Ви повинні перевірити, якщо це визначено, не відповідає дійсності.
brian d foy

Кріс, RET: Так, я знаю. Я спеціально намагався припустити, що якщо Джессіка не турбується про різницю між undefі "", вона повинна просто змінити одне на інше і скористатися одним тестом. Це не спрацює в загальному випадку, для якого інші опубліковані рішення набагато кращі, але в цьому конкретному випадку це приводить до акуратного коду. Чи слід переформулювати свою відповідь, щоб зробити це зрозумілішим?
Gaurav,

1

Можна сказати

 $name ne ""

замість

 length $name > 0

7
Це все одно дасть вам попередження. Причина, через яку люди перевіряють визначеність, полягає в тому, щоб уникнути попередження „неініціалізоване значення”.
brian d foy

1

Не завжди можна робити повторювані речі простим та елегантним способом.

Просто робіть те, що завжди робите, коли у вас є загальний код, який тиражується у багатьох проектах:

Шукайте CPAN, можливо, хтось уже має код для вас. Для цього випуску я знайшов Scalar :: MoreUtils .

Якщо ви не знайдете на CPAN чогось, що вам подобається, створіть модуль і помістіть код у підпрограму:

package My::String::Util;
use strict;
use warnings;
our @ISA = qw( Exporter );
our @EXPORT = ();
our @EXPORT_OK = qw( is_nonempty);

use Carp  qw(croak);

sub is_nonempty ($) {
    croak "is_nonempty() requires an argument" 
        unless @_ == 1;

    no warnings 'uninitialized';

    return( defined $_[0] and length $_[0] != 0 );
}

1;

=head1 BOILERPLATE POD

blah blah blah

=head3 is_nonempty

Returns true if the argument is defined and has non-zero length.    

More boilerplate POD.

=cut

Тоді у своєму коді назвіть це:

use My::String::Util qw( is_nonempty );

if ( is_nonempty $name ) {
    # do something with $name
}

Або , якщо ви заперечуєте прототипи і не заперечують проти додаткових дужок, пропустити прототип в модулі, і назвати його як: is_nonempty($name).


2
Хіба це не так, як за допомогою молотка вбити муху?
Зоран Сіміч

4
@Zoran No. Код факторингу, подібний до цього, б'ється, маючи складний стан, відтворений у багатьох різних місцях. Це було б як би за допомогою пінприків вбити слона. @daotoad: Я думаю, вам слід скоротити свою відповідь, щоб наголосити на використанні Scalar::MoreUtils.
Sinan Ünür

@Zoran: Scalar :: MoreUtils - це дуже легкий модуль без залежностей. Його семантика також добре відома. Якщо у вас немає алергії на CPAN, немає великих причин уникати його використання.
Адам Беллер,

1
@Chris Lutz, так, я не повинен. Але прототипи напівзламані - існують прості способи розірвати застосування прототипів. Наприклад, безглузді та / або застарілі підручники продовжують заохочувати використання &sigil під час виклику функцій. Тому я, як правило, не покладаюся на прототипи для виконання всієї роботи. Припускаю, що я міг додати до повідомлення про помилку "і залишити використання & sigil у підвикликах, якщо ви цього не маєте на увазі".
daotoad

1
Простіше вважати прототипи підказками для компілятора perl, щоб він знав, як щось проаналізувати. Вони не є там, щоб перевірити аргументи. Вони можуть бути порушені з точки зору очікувань людей, але так багато речей. :)
brian d foy

1

Відмінна бібліотека Type :: Tiny забезпечує структуру, за допомогою якої можна вбудувати перевірку типу у ваш код Perl. Тут я показую лише найтоншу верхівку айсберга і використовую Type :: Tiny найбільш спрощеним та ручним способом.

Обов’язково перегляньте посібник Type :: Tiny :: для отримання додаткової інформації.

use Types::Common::String qw< NonEmptyStr >;

if ( NonEmptyStr->check($name) ) {
    # Do something here.
}

NonEmptyStr->($name);  # Throw an exception if validation fails

-2

Як на рахунок

if (length ($name || '')) {
  # do something with $name
}

Це не зовсім еквівалентно вашій оригінальній версії, оскільки воно також поверне false, якщо $nameце числове значення 0 або рядок '0', але буде поводитися однаково у всіх інших випадках.

У perl 5.10 (або пізнішої версії) відповідним підходом буде використання замість цього визначеного або оператора:

use feature ':5.10';
if (length ($name // '')) {
  # do something with $name
}

Це вирішить, на основі чого визначати довжину, залежно від того $name, визначено, а не чи відповідає дійсності, тому 0 / '0'буде правильно обробляти ці випадки, але для цього потрібна остання версія perl, ніж доступна багатьом людям.


2
Навіщо вести розбитий розчин, лише кажучи, що він розбитий?
brian d foy

Тому що, як я вже згадував, 5.10 - це "найновіша версія perl, ніж доступна багатьом людям". YMMV, але "це 99% рішення, яке я знаю, що ти можеш використовувати, але є краще, яке ти можеш використати, а може, й не можеш" мені здається кращим, ніж "ось ідеальне рішення, але ти, мабуть, можеш" t використовуйте його, тож ось альтернатива, з якою ви, мабуть, можете обійтись як резервний варіант ".
Dave Sherohman

1
Навіть з попередніми Perls ви можете мати діюче рішення замість зламаного.
brian d foy

-3
if ($ name)
{
    #since undef та '' обидва оцінюють як false 
    # це має працювати лише тоді, коли рядок визначений і не порожній ...
    # якщо ви не очікуєте щось на зразок $ name = "0", що є хибним.
    # зауважте, що $ name = "00" не є хибним
}

1
На жаль, це буде помилкою, коли $ name = 0;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.