ORACLE PL/SQL 変数の使い方の違いによるパフォーマンス確認

こんにちは、石川さんです。

最近ORACLEのPL/SQLでストアドプログラムを作っています。どっちのパフォーマンスが良いのか、ちょっと気になったので調べてみました。

作っていたのは、ファイルを1行ずつ読み込んで、諸々チェックして、問題なければテーブルにINSERTする、というよくある単純な処理です。このとき、呼び出し側からOUTパラメータを指定して、登録件数とチェックしたエラー件数をもどす、という要件があるときの変数の使い方として、以下の二通りを考えました。どっちが早いのでしょうね?

  1. OUTパラメータを直接インクリメントする
  2. 別途定義したローカル変数をインクリメントして、最後にOUTパラメータへ結果をセットする

実験

わからないことは、すぐに実験しましょう。と、いうことで以下のスクリプトを作ってみました。

set serveroutput on

DECLARE
  counter NUMBER := 0;
  s1 TIMESTAMP;
  s2 TIMESTAMP;
  e1 TIMESTAMP;
  e2 TIMESTAMP;
  PROCEDURE inner_procedure1(an_counter OUT NUMBER) IS
  BEGIN
    an_counter := 0;
    LOOP
      an_counter := an_counter + 1;
      EXIT WHEN an_counter > 10000000;
    END LOOP;
  END;
  PROCEDURE inner_procedure2(an_counter OUT NUMBER) IS
    counter NUMBER := 0;
  BEGIN
    an_counter := 0;
    LOOP
      counter := counter + 1;
      EXIT WHEN counter > 10000000;
    END LOOP;
    an_counter := counter;
  END;
BEGIN
  s1 := SYSTIMESTAMP;
  inner_procedure1(counter);
  e1 := SYSTIMESTAMP;
  s2 := SYSTIMESTAMP;
  inner_procedure2(counter);
  e2 := SYSTIMESTAMP;
  DBMS_OUTPUT.PUT_LINE('START:'||TO_CHAR(s1,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE('END  :'||TO_CHAR(e1,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE(e1 - s1);
  DBMS_OUTPUT.PUT_LINE('START:'||TO_CHAR(s2,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE('END  :'||TO_CHAR(e2,'YYYY-MM-DD HH24:MI:SS.FF'));
  DBMS_OUTPUT.PUT_LINE(e2 - s2);
END;
/

inner_procedure1が、OUTパラメータをインクリメントする「1.」のケースで、inner_procedure2が、ローカル変数をインクリメントする「2.」のケースになります。

では何回か実行します。そして、結果は、、、

(・・・略・・・)
12:41:11  39  /
START:2022-11-30 12:41:11.901000000
END  :2022-11-30 12:41:12.665000000
+000000000 00:00:00.764000000
START:2022-11-30 12:41:12.665000000
END  :2022-11-30 12:41:13.149000000
+000000000 00:00:00.484000000

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.26
12:41:13 SQL> /
START:2022-11-30 12:41:18.841000000
END  :2022-11-30 12:41:19.492000000
+000000000 00:00:00.651000000
START:2022-11-30 12:41:19.492000000
END  :2022-11-30 12:41:19.872000000
+000000000 00:00:00.380000000

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.03
12:41:19 SQL> /
START:2022-11-30 12:41:23.800000000
END  :2022-11-30 12:41:24.439000000
+000000000 00:00:00.639000000
START:2022-11-30 12:41:24.439000000
END  :2022-11-30 12:41:24.818000000
+000000000 00:00:00.379000000

PL/SQLプロシージャが正常に完了しました。

経過: 00:00:01.02
12:41:24 SQL>

「2.ローカル変数をインクリメントして、最後にOUTパラメータへ結果をセットする」方がおよそ1.5倍ほど、はやい!ということがわかりました。

と、いうことで今後はOUTパラメータへのアクセスは最後の一回のみ、ということにしたいと思います。

結論

ローカル変数をインクリメントして、最後にOUTパラメータへ結果をセットした方がはやい、というのは結果からみるとそのとおりなのですが、実は、10000000回(1千万回)ループしているのですよね。その上で、早くなったのは、0.3秒未満なので、大量データを何度も扱わない限りは気にしなくても良さそうです。億超えのデータをしょっちゅう扱うような人は、ちょっとずつ効いてきますので、気にしましょう!
ぼくはとっても気になります!!!

Oralce テーブルのカラムの順番を入れ替える

 今日も見に来てくださってありがとうございます。石川さんです。

 今日は、Oracleにおいてのテーブルのカラムの順番を入れ替える方法について書いておきます。昔だと、テーブル再作成してデータを入れ直す、という方法しかなかったのですが、別のやり方もあります、というお話です。Google先生に聞いたら、新しいやり方については簡単に出てこなかったので、周知しなければ、というモチベーションです。(笑)

Invisibleにして、Visibleにする

 結論を先に知りたい人のために、項目をInvisibleにして、visibleに設定し直すと、項目がテーブルの最後に移動します。この特性を使って順番を入れ替える、ということになります。

-- テーブルを作成します。POIに意味はありませんが、キーボード上に並んでいて、「ポイ」という感じが気に入っていてテストのときによく使っています。
SQL> create table poi (
  2    a number,
  3    b number,
  4    e number,
  5    d number
  6  );

表が作成されました。

-- 「C」列を追加し忘れました。よく見ると「E」「D」の列の順番も間違っていました。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 E                                                  NUMBER
 D                                                  NUMBER

-- 「C」列を追加します。
SQL> alter table poi add (c number);

表が変更されました。

-- 確認すると、最後に追加されています。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 E                                                  NUMBER
 D                                                  NUMBER
 C                                                  NUMBER

-- 「E」「D」列を「invisible」に設定します。
SQL> alter table poi modify ( e invisible, d invisible );

表が変更されました。

-- 確認すると、確かに「E」「D」列が見えなくなっています。ちなみにこの状態でINSERT INTO POI VALUES ( 1, 2, 3 );を実行すると、見えない列はNULLになりました。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 C                                                  NUMBER

-- 順番が大事ですので、まずは「D」列を見えるようにします。
SQL> alter table poi modify ( d visible );

表が変更されました。

-- そして、次に「E」列を見えるようにします。
SQL> alter table poi modify ( e visible );

表が変更されました。

-- 確認すると、順番が正しく入れ替わりました。
SQL> desc poi
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 A                                                  NUMBER
 B                                                  NUMBER
 C                                                  NUMBER
 D                                                  NUMBER
 E                                                  NUMBER

SQL> insert into poi values ( 1, 2, 3, 4, 5 );

1行が作成されました。

-- INSERT文を実行するときに項目を省略してみましたが、入れ替わった順番どおり値がセットされていました!
SQL> select * from poi;

         A          B          C          D          E
---------- ---------- ---------- ---------- ----------
         1          2          3          4          5

経過: 00:00:00.01
SQL>

 テーブルの項目をInvisibleに設定するオプションですが、Oracle 12cから登場したようです。これを実行すると文字通り見えなくなります。

追記:開発中のテーブルで実際に項目追加が必要になってやってみましたが、VISIBLEに設定するのは手抜きして以下のように一気にやっても順番通りに戻りました。

alter table poi modify ( e visible, d visible );

まとめ

 Oracleのテーブルのカラム定義の順番を入れ替えるのに、かつては再作成とデータ投入が必要でしたが、ALTER TABLE文だけで項目が入れ替えられるようになっていました!権限やインデックス、データ投入など、必要な手間を考えると圧倒的にこちらのほうが良い気がします。

Angular始めました – リアクティブフォームを使ってみる。そして、stackblitz.comのプログラムを組み込んでみる

 今日も見に来てくださってありがとうございます。石川さんです。
先日に引き続き、Angularを勉強しています。今回はリアクティブフォームを使って、サンプルプログラムを作ってみました。

出来上がりイメージ

  今回は、たまたまニュートン-ラフソン法について質問があったので、Angularで実装してみました。こんな感じになります。ルートを求めたい値「k」を入力して、Calcボタンをクリックすると、漸化式を繰り返します。二乗した結果が「k」との誤差0.01未満になったら終了します。

ニュートン-ラフソン法の実行結果

 今回も、statsblitz.comを利用してみました。作業中に、デフォルトで用意されているコンポーネントの「hello.component.ts」を削除してみたところ、以下のエラーがでました。しばらく解決できなくて、何かの設定があるのかと、ウロウロしてしまったので、回避策をメモしておきます。

Error in src/app/hello.component.ts (1:1)
File '/~/src/app/hello.component.ngtypecheck.ts' not found.

 何のことはありません、おかしな状態になっているだけ、ということのようでした。画面のプロジェクトエクスプローラーの左下の「DEPENDENCIES」をポイントすると、くるりとなった矢印が登場するので、そちらをクリックしたところ、しばらく待って、エラーが消えました。

DEPENDENCIESの解決

ソースコード

ソースコードは以下のとおりです。

まずは、コンポーネントのスクリプトです。リアクティブフォームのポイントは「FormGroup」ですね。TypeScript側で「myGroup」として定義しています。また、今回はFormBuilderを使って「k」を定義しました。また、Validatorsを使って、正の数をチェックするようにしました。
onSubmit()呼び出し時、漸化式で計算を実行して、二乗した結果がkに近づいて差が0.01を下回ったところで処理を完了します。

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-x2',
  templateUrl: './x2.component.html',
  styleUrls: ['./x2.component.css'],
})
export class X2Component implements OnInit {
  myGroup: FormGroup;
  results: string[];
  guess: number;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myGroup = this.fb.group({
      k: [24, Validators.min(0)],
    });
  }

  get k() { return this.myGroup.get('k'); }

  onSubmit() {
    console.log('Executed!')
    this.results = [];
    let k: number = this.k.value;
    let epsilon: number = 0.01;
    let guess: number = k / 2;
    let newGuess: number;
    while (Math.abs(guess * guess - k) >= epsilon) {
      newGuess = guess - (guess ** 2 - k) / (2 * guess);
      if (this.results.length == 0) {
        this.results.push(guess.toString() + ' => ' + newGuess.toString());
      } else {
        this.results.push(' => ' + newGuess.toString());
      }
      guess = newGuess;
    }
    this.guess = guess;
  }
}

 そして、コンポーネントのHTMLファイルです。

<p>
ニュートン-ラフソン法でkの根を求めます。
</p>
<p>
f(x) = x<sup>2</sup> - k<br>
x<sub>n + 1</sub> = x<sub>n</sub> - f(x) / f'(x) = x<sub>n</sub> - (x<sub>n</sub><sup>2</sup> - k)/2x<sub>n</sub>
</p>
<form [formGroup]="myGroup" (submit)="onSubmit()">
  <table>
    <tr>
      <th>k</th>
      <td><input type="number" formControlName="k"></td>
    </tr>
    <tr>
      <th></th>
       <td *ngIf="k.invalid" [style.color]="'red'">正の数を入力してください。</td>
    </tr>
    <tr>
      <th></th>
      <td>
        <input type="submit" value="Calc" [disabled]="myGroup.invalid">
      </td>
    </tr>
  </table>
</form>
<ul>
  <li>まず、guess = k / 2 = {{k.value}} / 2 = {{k.value / 2}} とします。<br>guess - (guess<sup>2</sup> - {{k.value}})/ (2 × guess)がより近い値になるので、計算結果の誤差が0.01になるまで繰り返します。<br>
    <p *ngIf="!results">Calcを押して続行します。</p></li>
  <li *ngFor="let ans of results">{{ans}}</li>
</ul>
<p *ngIf="guess">{{guess}} * {{guess}} = {{guess * guess}}</p>

WordPressへの組み込み

 stackblitz.comを見ていると、「Share」メニューがあったので、クリックして見ると、「Embed」タブが出てきましたので、WordPressに組み込めるのではないか、と、調べて組み込んでみました。

 組み込みブロックはあるのですが、stackblitz.com用のものがなく、調べているとここに書いてありました。iframeタグを使って、ということだったので、実験してみましたところ、以下のとおり、実行できました!

 ただ、編集のプレビュー画面では「Calc」ボタンは動きませんでした。iframeではsubmitが無効になっているようです。ただ、確認画面では動作しました。こちらの原因はまた機会があったら調べてみます。公開された後に実行できるかどうかわかりませんので、stackblitz.comのソースコードはこちら、実行結果はこちらです。

まとめ

 Angularのリアクティブフォームを使って、ページを作ってみました。

Angular始めました – stackblitz.comを使ったAngularの簡単な紹介

 今日も見に来てくださって、ありがとうございます。石川さんです。

 先日「Angularはじめました。」ということで、「AngularによるモダンWeb開発 基礎編 第2版」を読み込んで簡単な仕組みを作ってみよう、と、意気込んでいましたがずいぶん経ってしまいました。まずは、この書籍のサンプルをダウンロードして動作確認しようと取り組んでみたところ、もろもろ躓きまして。この本、初学者にはおすすめできませんね。あ、ぼくはすぐに何とか解決しましたよ。プロですからね。エヘン!でも、ちょっと初心者には難しかったかも知れない、と、追加の書籍を二冊購入して色々と勉強していました。学習効果が高いのはアウトプット、ということは分かっているのですが、どうにも本が好きなのでしょうねぇ。すぐに次の本、と買ってしまい割とコレクターみたいなところがあります。で、今日は、Angularってどんなものなの、ということを簡単に紹介したいと思います。そうそう、アウトプットです!

Angularとは何か、そのセットアップについて

 一言でAngularとは何か、というと、ステキなWebアプリを開発するためのフレームワーク、と言ってよいのかな、と、思います。ステキなWebアプリと書きましたが、流行の専門用語ではこれをPWA(Progressive Web Application)と呼んでいて、デスクトップアプリケーションのような体験ができるWebアプリ、ということのようです。これらはGoogleが開発を進めているオープンソースのフロントエンドフレームワークで、半年に一度はメジャーバージョンが更新される、という活発な開発状況です。書籍の数が比較的少ないのは、この頻繁なバージョンアップのせいではないか、ということをぼくは勝手に疑っています。

 ローカルで開発するためには、node.jsというjavascriptを実行するための環境を用意する必要があります。なので、まずはnode.jsをインストールします。node.jsのインストールが終わったら、そのnode.jsの中のインストールコマンドで、Angularをインストールするということになります。コマンドプロンプト、またはターミナルから以下のコマンドを実行してください。

npm install -g @angular/cli

これで、Angularがインストールされたことになります。その後、プロジェクトを作るために、任意のフォルダで以下のコマンドを実行することで、新しいプロジェクトのためのフォルダ(myProject)を作成します。

ng new myProject

開発は、ここから「ng serve」を実行して、作られた結果をブラウザで確認しながら、という感じで進められていくことになります。結構面倒ですよね。もっとお手軽にどんなものか知りたい、というAngular初心者の方々のために、stackblitz.comが便利なので紹介したいと思います。

stackblitz.comを使ってみましょう

 stackblitz.comはブラウザで利用できる統合開発環境(IDE)のクラウドサービスのひとつです。ホームページを見てみるとわかりますが、いくつかの開発に対応しています。今回はAngularですので、以下のアイコンを探してクリックしてみてください。

stackblitz.comのAngular開発環境入口

 すると、ブラウザの中に統合開発環境(IDE)がAngularの新規プロジェクトを開いた状態で開始します。左がフォルダとソースコードの一覧で、真ん中がソースコード、右がAngularを実行した結果、という感じです。

stackblits.comのAngular開発環境(初期画面)

 Angularでは基本的に、コンポーネントという部品の単位で開発することになります。初期表示では「app.component.ts」ファイルが開かれています。このファイルがコンポーネントの処理部分を担います。拡張子が「.html」のファイルが表示内容の骨格部分、「.css」が表示内容の飾りの部分、という感じで考えていただければ、と思います。

2022年2月3日 追記
 stackblitz.comにて本日再び新しいAngularプロジェクトを作成してみたところ、Angularのバージョンが13になっていました。保存してあるリポジトリもバージョンアップしましたので、以降の内容は12→13で読み替えるようお願いいたします。たしかこのバージョンは、半年に一回は更新されるんだよね。。。

 「Start editing to see some magic happen :)」と記載がありますので、ここの部分、さっそく直してみましょう。まずは、日本語使えるのかな、ということでこの部分を日本語にしてみます。左側の「app.component.html」ファイルをクリックして中央に開きます。英語部分を日本語にしてみましょう。

リアルタイムで修正が反映される

 HTMLファイルを編集すればわかりますが、修正と同時に右側のアプリケーション部分が更新されます。日本語もちゃんと表示されましたね。

画面遷移なしで使える機能を盛り込んでみた

 リアクティブフォーム以外で画面遷移しない範囲の簡単な項目をもろもろ盛り込んでみました。完全に自分用の備忘録です。忘れたときに参照しようと思って書いていますので、説明は省きたいと思います。出来上がりイメージは以下のとおりです。

できあがりイメージ

ソースコードは「app.components.ts」と「app.components.html」の二つだけ変更しました。以下の通りです。

import { Component, VERSION } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  name = 'Angular ' + VERSION.major;
  today = new Date();
  clicked: boolean;
  message: string;
  count: number = 0;
 list: string[] = [
    "AngularによるモダンWeb開発",
    "Angular超入門",
    "Angularデベロッパーズガイド",
  ];
  ch1: boolean = false;
  ch2: boolean = false;
  ch3: boolean = true;
  opt: string;
  sel: string;
  mul: string;

  onClick() {
    this.clicked = !this.clicked;
  }

  keyup(val: string) {
    this.message = val;
  }

  countup() {
    this.count++;
  }

}
<hello name="{{ name }}"></hello>
<p>
  編集を開始して、魔法が起こるのを確認してください☺
</p>
<ol>
  <li>変数が利用できます。name = 「{{name}}」</li>
  <li>計算もできます。3 + 3 = {{3 + 3}}</li>
  <li>日時を表示できます。{{today | date: "YYYY-MM-dd hh:mm:ss"}}</li>
  <li *ngIf="clicked" (click)="onClick()" [style.background-color]="'cyan'">クリックに反応します。A</li>
  <li *ngIf="!clicked" (click)="onClick()" [style.background-color]="'lightblue'">クリックに反応します。B</li>
  <li><input type="text" #f1 (keyup)="keyup(f1.value)">入力を下に反映します。</li>
  <li>↑の入力をここ「{{message}}」に瞬時に反映させます。</li>
  <li>{{count}}回クリックしました。<button (click)="countup()">Click</button></li>
  <li>ngForで繰り返し処理ができます。</li>
  <ol>
    <li *ngFor="let item of list;let i = index">{{item + ":i = " + i}}</li>
  </ol>
  <li>チェックボックスが使えます。
    <input type="checkbox" [(ngModel)]="ch1">
    <input type="checkbox" [(ngModel)]="ch2">
    <input type="checkbox" [(ngModel)]="ch3"></li>
  <ul>
  <li *ngIf="ch1">一つ目がチェックされています。</li>
  <li *ngIf="ch2">二つ目がチェックされています。</li>
  <li *ngIf="ch3">三つ目がチェックされています。</li>
  </ul>
  <li>ラジオボタンが使えます。
    <label><input type="radio" [(ngModel)]="opt" value="男">男</label>
    <label><input type="radio" [(ngModel)]="opt" value="女">女</label>
    <label><input type="radio" [(ngModel)]="opt" value="不明">不明</label></li>
  <ul>選択値は「{{opt}}」です。</ul>
  <li>プルダウンが使えます。
  <select [(ngModel)]="sel">
    <option *ngFor="let item of list">{{item}}</option>
  </select></li>
  <ul>選択値は「{{sel}}」です。</ul>
  <li>複数選択リストが使えます。
    <select [(ngModel)]="mul" multiple size=3>
      <option *ngFor="let item of list">{{item}}</option>
    </select>
  </li>
  <ul>選択値は「{{mul}}」です。</ul>
</ol>

stackblitz.comとgithub.comで共有できます

  stackblitz.comは、github.comと連携することでソースコードを保存することができます。そして、保存すると他の人と共有することが可能になります。今回はこちらがソースコードの共有です。そして、こちらが実行結果の共有です。

まとめ

 Angularを使うと、これまで簡単にできなかったことが、そこそこ簡単にできるようになりました。

Angular始めました – モダンWeb開発 PWA(Progressive Web Application)始めますよ~!

 今日も見に来てくださって、ありがとうございます。石川さんです。

 ご縁があって、近々Webシステムの開発をすることになるかも知れない、ということになりましたので、諸々検討して、Angularの勉強を始めることにしました。

Angularによる モダンWeb開発

 Webシステムが出始めた当時、4GLの開発をしていたこともあって、あの複雑なだけで開発生産性が高くもなく、操作性の少しも良くない仕組みがどうしても好きになれず、ずっと避けてきていたのですよね。そう、もっさりとしていて遅いし、ユーザーインターフェースも雑で細やかさがなくて、嫌だったのですよね。アプリケーションのインストールがない、というメリット以外には良さを感じられなかったのです。様子が変わってきたのはGoogle Mapあたりからで、それまでのWebアプリケーションらしからぬ動きはかなりの衝撃でしたね。そして、GmailにGoogleドキュメントと、かつてのWebアプリケーションとは違う感じになってきているなぁ、というのは知っていましたが、とうとうWebアプリケーションに手を出す決意をしました。

 と、いうのも、Angularのホームページのチュートリアルに感動して、これは本格的に取り組まねば、という気持ちになりました。まず、実行できるチュートリアルがすべてWebで完結しているのですよね。Visual Studio CodeのようなIDEが自動的に開始します。先日gitPodでも体験していましたがブラウザだけで開発できるのは素晴らしいですね!そして、コンポーネント指向開発という新しい開発方法もなかなか面白いですね。部品が多くなり過ぎたら再利用が難しくなるかも知れないなぁ、という気持ちになりましたが、もうちょっとやれば、何かつかめるかも知れませんね。

 それと、Comppassの「Angular日本ユーザー会のYoutubeライブ!」を視聴しました。予想通りですが、まったくついていけませんでした!まだ開発もしてないですからねぇ。Angularのバージョン13リリースに関して、追加、更新、削除された機能について、語られていました。現状がまったくわからないので、まあついていけないのは当然ですね。参加して、引っ掛かりができるようになれば、という感じです。

 Angularのホームページと、画像の書籍「AngularによるモダンWeb開発 基礎編 第2版」を読み込んだら、簡単な仕組みを作ってみようと思います。

 さあ、がんばるぞー!

実行時エラー’5′:プロシージャの呼び出し、または引数が不正です。

 今日も見に来てくださって、ありがとうございます。石川さんです。
Excelの実行時エラー、1年ほど前に書いた記事のアクセスが一番多いので、みなさんのお役に立っているようです。と、いうことで先日職場にて発生した実行時エラー、今度は「5」です。こちらが発生しました。今回は全くつまづくこともなく、すぐに解決できたのですが、ひとによっては悩みに悩んで解決できない、ということもあるかも知れないと思って記事にすることにしました。(Excelはニーズが多いですからね。アクセスが増えるかもしれない、と調子に乗っております。)

実行時エラー ‘5’:プロシージャの呼び出し、または引数が不正です。

 急いでいる方に結論だけ、お知らせします。こちらは以下のスクリプトで再現できます。

Sub test()
    'B1セルの内容がA1セルより小さいとき、B1セルの背景をグレーにしてフォントを太字にする条件付き書式を設定します
    With Range("B1").FormatConditions.Add(Type:=xlExpression, Formula1:="=AND($B1<$A$1)")
        .Font.Bold = True
        .Interior.Color = RGB(166, 166, 166)
    End With
End Sub

 ちなみにこちらのスクリプト、以前は問題なく実行できていたのですけど、ある日突然「実行時エラー’5’」が発生するようになりました、というお問い合わせでした。ただ、こちらのスクリプト、メールで受信して、ダウンロードして実行しても、ぼくの環境ではエラーにならなかったのです。不思議でしょ。なので、上記のスクリプトを記述してもエラーにならない方もいるかも知れません。その場合は、違う原因かも知れません。

 それで、エラーの出ている人のエクセルとぼくのエクセルをよくよくを見てみますと、セルの列の表記が違うのですよね。

エラーが出ている方のエクセルの列の表記
エラーが発生していないエクセルの列の表記

 そう、列が「1、2、3、4、5、、、」となっているか「A、B、C、D、E、、、」となっているかの違いです。こちらは「ファイル(F)」の「オプション」を選択することで表示される「Excel のオプション」画面から「数式」を選択することで表示される「数式の処理」の「R1C1 参照形式を使用する(R)」をチェックすることで切り替えられます。チェック時は「1、2、3、4、5、、、」で、チェックオフ時は「 A、B、C、D、E、、、」と表示されるようになります。

 今回はこの設定が悪さをしていました。スクリプト作成時はR1C1参照形式を使わない設定になっていたのに、途中で切り替えて保存した、ということのようです。こちらの「R1C1 参照形式を使用する(R)」の設定は、Excel全体の設定ではなくて、ファイルごとに設定を保持するようです。ちょっと動作確認してみたところ、新規作成時は前回エクセル終了時の設定を利用して、既存ファイルを開いたときはそのファイルの設定を利用するようです。

まとめ

 マクロ実行時に今まで動作していたのにこのエラーが出るようになったら、「R1C1 参照形式を使用する(R)」の設定を疑ってみるといいかも知れませんね。マクロで条件付き書式の設定をするときには、参照形式をこの設定にそろえておかないといけない、ということですね。

Google Cloud PlatformのCloud SQL(MySQL)のインスタンスをつくってみました

 今日も見に来てくださって、ありがとうございます。石川さんです。今夜は中秋の名月です。と言っても書き始めたのが遅いし、記事も長くなりそうなので、書き終わって公開するときには、中秋の名月は終わっているでしょうね。

 さて、今回は、ちょっと要望がありまして、GCPのCloud SQLを使ってみることにしました。ぼくはOracleマスターということもあり、普段データベースを使うと言えば、Oracleを使っています。そう、RDBMSシェアNo1のオラクルさまです。ただ、GCPではOracleのインスタンスが作れるようになっていないのですよね。Oracleもクラウドサービスを展開していますから、ライバル関係ということになるのかなぁ。そんなこともあり、GCPのCloud SQLのラインナップには登場しそうにありませんよね。

 所有している書籍やWeb上のGCPのドキュメントを色々と読んで、バッチリ理解してから始めようと思っていたのですが、いつまでたっても一向に始められません。こういう時は、まずやってみる、ということが大切ですよね。ということで、スクリーンショットを取りながら進めてみたいと思います。

新規プロジェクトの作成

 では、まずはGCPコンソールへ行って、プロジェクトを作成しましょう。三角形をクリックします。

Google Cloud Platformダッシュボードでプロジェクトを新規作成

 今回は、プロジェクト名を「Sample-DB」にしてみます。上記の三角形をクリックすると以下のようなウィンドウが表示されなりますので、「新しいプロジェクト」をクリックします。

新しいプロジェクトを作成します

 プロジェクト名は、「Sample-DB」に入力しなおして、あとはデフォルトのまま「作成」ボタンをクリックします。

 すると、アイコンが表示されたり、なにやら作成中の雰囲気を出したあと、プロジェクトのダッシュボードが表示されました。が、、、これは今作ったプロジェクトではありませんね。そこで下向きの三角形をクリックしてプロジェクト一覧を表示します。

作ったプロジェクトではないプロジェクトが選択されてしまった

 すると、以下のような一覧が見えますが、先ほど作ったプロジェクトが見当たりませんね。以下の「すべて」のところをクリックするか、虫眼鏡アイコンの隣の「プロジェクトとフォルダを検索」へ「Sample」と入力すれば、先ほど作成したプロジェクトが現れます。

なぜか先ほど作ったプロジェクトが見つからない

 では、「すべて」をクリックしてみましょう。

先ほどつくったSample-DBプロジェクトが見つかりました

 見つかりましたので、「Sample-DB」を選択して「開く」をクリックしてみましょう。以下の通り、プロジェクトの作成に成功しました。プロジェクトを作っておくと請求情報をプロジェクト単位でみられる等、管理しやすくなると思います。

Sample-DBプロジェクトができました

Cloud SQLのMySQLインスタンスを作成してみる

 では、次にCloude SQLのMySQLを作成してみます。ナビゲーションメニューの「データベース」カテゴリから「SQL」を選択します。左上の横線三本のハンバーガーマークをクリックするとナビゲーションメニューが表示されますので、メニューを下の方へ下げて行って、「SQL」を探してください。

「SQL」を見つけました

 「SQL」をクリックするとCloude SQL インスタンスを作成するためのウィザードが始まりました。次は、当然「インスタンスを作成」ですね。既存システムのデータを持っていれば、データの移行もできるようですね。企業システムでのリプレース案件だと結構利用するタイミングがありそうなので、気になりますね。いやいや、今回はまずインスタンスを作成しますよー。(ちょっと調べてみたら、今のところ、MySQLとPostgreSQLで利用可能で、SQL Serverは、まもなく利用可能ということです。2021年9月23日現在)

インスタンスを作成ウィザードが開始されました

 よく読んでみると、「MySQL」「PostgreSQL」「SQL Server」のインスタンスが作成できるようですね。では、「インスタンスを作成」ボタンをクリックします。以下のような表示になりました。今回は「MySQL」を選択します。

データベースエンジンの選択ができます

 「MySQL を選択」をクリックすると以下のような画面になりました。

インスタンスを作成するには、まずCompute Engine APIを有効にする必要があります。

 ふーむ、何々、「インスタンスを作成するには、まずCompute Engine APIを有効にする必要があります。」ですか。なるほど。と、いうことはMySQLインスタンスはCompute Engineを利用して実現しているのですね。いきなりMy SQLインスタンスって、どうやって起動するんだろうか、と、思っていましたが、仮想マシンを実行して、その上で動かす、ということなのでしょうね。ま、完全な想像ですけど。そして、このホームページはCompute Engineを利用しているので、この設定は不要なのでは、と、思いましたが、プロジェクトごとに設定が必要なのか、APIとは別の設定なのかのどちらかでしょうね。いずれにせよ、APIを有効にしないとどうにもならないので、「APIを有効にする」をクリックします。

APIを有効にしています。

 しばらく上記のようにくるくるしてから、以下のようになりました。

MySQLインスタンス作成の初期状態

 必須入力は、「インスタンスID」と「パスワード」のみですね。今回はお試しで作ってみるだけなので、最小構成にしたいですね。データベースのバージョンは、8.0、5.7、5.6から選べました。おそらく初期値の「5.7」は安定したバージョンなのでしょうね。今回はお試しなので、8.0を選択しておくことにします。インスタンスが作成され、常時存在することになるので、インスタンスを起動している間は使用料がかかります。金額については、概算ですが、Cloud SQL for MySQL の料金で、計算できるようです。リージョンの選択によって若干金額が変わってきます。現時点のアイオワでは、1仮想CPUあたり、1か月で$30.15、1GBあたり、1ヶ月で$5.11です。東京は$39.19、$6.64なので、やはり若干高くなっていますね。業務で利用する場合は、ネットワークの距離の影響もあるので、慎重に選ぶ必要があるかと思いますが、今回は安い方のアイオワで問題ありませんね。そして「ゾーンの可用性」ですがこちらは「シングルゾーン」に変更しましょう。複数のゾーン(高可用性)を選択すると、およそ料金が二倍になります。ま、二台用意しておいて、一台がダメになったとき切り替えてダウンタイムを極小にしよう、という取り組みですから、ま、そうなりますよね。そして、vCPU数が4に、メモリ、26GBもいらないでしょう。こちらは、「構成オプションを表示」を選択することで設定できるようになっていました。

構成オプション

 へえ、インスタンス構成は、あとから更新することもできるのですね。ま、ちゃっちゃと最小構成を目指して片づけてしまいましょう。まずは、マシンタイプですね。

マシンタイプ(ハイメモリ)

 おや、4 vCPU、26GBが最小構成なのでしょうか、、、あ、違いました。「ハイメモリ」が選択されている状態でしたね。ちょっとクリックして中身を覗いてみましょう。

マシンタイプの一覧

 なるほどねぇ、順番的には「共有コア」が一番軽量なのでしょうか。以下に、一覧にしてみました。

  • 共有コア
    • 1vCPU、0.614GB
    • 1vCPU、1.7GB
  • 軽量
    • 1vCPU、3.75GB
    • 2vCPU、3.75GB
    • 4vCPU、3.75GB
    • カスタム※
  • 標準
    • 1vCPU、3.75GB
    • 2vCPU、7.5GB
    • 4vCPU、15GB
    • カスタム※
  • ハイメモリ
    • 4vCPU、26GB
    • 8vCPU、52GB
    • 16vCPU、104GB
    • カスタム※

※カスタムは、vCPUが1~96まで入力可能(1より大きい場合は偶数)で、メモリがvCPU数(N)に応じておよそ、3.75+1.75×(N-4)/2+0.25×INT((N-2)/10)[N<6は3.75]~6.5×N GBの範囲で変更されました。

 と、いうことで、今回は「共有コア」の1vCPU、0.614GBを選択します。

 次は、ストレージですね。

ストレージのデフォルトの設定値

 SSDとHDDですね。やはりHDDの方が安価ですね。どれくらい違うのかというと、1GBあたり、アイオワリージョンでは、SSDが$0.170/月、HDDが$0.090/月、東京リージョンでは、SSDが$0.221/月、HDDが$0.117/月ということのようです。パフォーマンス要件はありませんので、ま、ここはHDDを選択しておきましょう。

 ストレージ容量は、10GB、20GB、100GB、200GB、カスタムですが、こちらも最小要件の10GBを選択します。容量を増やすほどパフォーマンスが向上する、ということが書いてありますが、どうしてでしょうね。もしかしたら、ディスクへのアクセスをうまく分散できるのかも知れませんね。ストレージの自動増量を有効にする必要は、まったくありませんが、ま、利用しなければ影響ありませんので、そのままにしておきましょう。

 次は、「接続」ですね。

「接続」の設定

 おすすめは、Cloud SQL Ploxyを使って接続してください、ということのようです。プライベートIPはVPCのみの内部(プライベート)IPアドレスで、インターネットにアクセスできるのが外部(パブリック)IPアドレスになります。Webアプリケーションの一部としてのデータベースなら、プライベートIPアドレスのみ有効にすればよいでしょうか、これは構成によりますね。今回はお試しなので、このままの設定でよいでしょう。

 次に、バックアップの設定ですね。今回のお試しではバックアップなしでよいのですが、ちょっと内容は確認しておきましょう。

バックアップオプション

「バックアップを自動化する」はデフォルトでチェックが入っていますが、時間枠で選択するようですね。マルチリージョンを選択しておけば複数のリージョンにバックアップされるのでとりあえず安心ですね。リージョンを選択すると、特定のロケーションをセットする必要があります。バックアップの数が7にセットされていますが、1週間であれば、特定のタイミングまで戻せますよ、という感じでしょうか。ポイントタイムリカバリを有効にすると、詳細オプションにログを保持する日数を設定できるようになりました。デフォルトは7日でした。
 ま、ここは、「バックアップを自動化する」はチェックを外しておきます。サンプルですから、バックアップ必要ありません。実際に稼働するシステムならもっとまじめに考えないといけませんけどね。あ、このチェック外すと「ポイントインタイムリカバリを有効にする」は触れなくなりました。普通のバックアップも取得していないのに、復元はムリですよ、ということですね。

 次は、メンテナンス項目ですね。ちょっと見てみましょう。なるほどねぇ、デフォルトだと、短期間だけど中断してしまうのですね。

メンテナンスオプション

 メンテナンスの時間枠は、「おまかせ」以外は「土曜日」~「金曜日」でした。曜日単位で指定するのですね。一般的な企業なら、土日が選ばれるのでしょうね。更新の順序は「任意」「後で」「早め」の三択でした。これは面白いですねぇ。バージョンアップを適用する時期を早めにするのか後でやるのか、それともお任せなのか、設定するのですね。あと、メンテナンス不要期間も設定できるのですね。今回はお試しで、すぐ削除してしまおうと思っているので、設定はこのままにしておきます。

フラグの設定

 「フラグ」では、MySQLのパラメータやオプション、インスタンス構成や調整など、様々な設定ができるようです。これはMySQLに精通しないとなかなか設定は難しそうですね。今回は不要ですが、チューニングや調査が必要な局面があるときにはここで設定可能ですね。いざというときのために覚えておきましょう。

 やっと、最後の項目、「ラベル」です。

ラベルの設定

 「ラベル」を追加すると、インスタンスを区別したり、請求料金を分析したりすることができるようです。今回は放置しておきます。

やっとひととおりの設定を眺め終わりました。

インスタンスの作成

 やっと、「インスタンスを作成」をクリックしました!ここまで読んでくれたひと、いますかねぇ。。。

 あら、インスタンスIDと、パスワードがない、と、叱られました。もうすっかり失念しておりました。

エラーが発生しました

 インスタンスIDは、何でもよいのでしょうかねぇ。「Sample-DB」を入れてみます。そして、パスワードは「生成」をクリックしてみましょう。

またもやエラーです!

 ちょっと理解に苦しみますが、、、これは、大文字がダメだった、ということでしょうか。小文字に変えてみます。予想通り、小文字なら大丈夫でした。「生成」も成功したので、今度こそ、「インスタンスを作成」をクリック!

 一瞬、「インスタンスを作成しています。」のようなメッセージが出た後、画面が切り替わり、しばらく「アップロードとSample-DBのオペレーション」の「sample-dbを作成しています」が右下に表示されます。

インスタンスを作成しています

 まだ、作成中のような気がしますが、一応、できたのかな。いや、もうしばらく待ってみましょう。。。と、いうことでインスタンス作成完了まで、11分とちょっとかかりました。できました!

データベースの作成

 次はデータベースを作成します。MySQLはデータベースを複数作成できる、ということで作成してみます。

 Cloud SQLの「データベース」メニューを選択します。

データベースを選択したところ

 「データベースの作成」をクリックしてみます。

データベースの作成

 「データベース名」は何にしましょうかねぇ。ルールがあるようですが、とりあえず「Sample-DB」と入力してみます。

Sample-DBではエラーになりました

 おお、なるほど、英数字とアンダースコアなので、ハイフンがダメなのですね。ハイフンをアンダースコアに変更してみます。

Sample_DBならOKでした

 オッケーでした。「文字セット」と「照合」を決めなければいけませんね。utf8はいいけど、「照合」って何でしょうね。選択値は「utf8_bin」とか「utf8_czech_ci」とか文字コードっぽいですね。選択した「文字セット」によって、選択値が変わりました。ま、後で変更できるようなので、デフォルトでも問題ないでしょう。「作成」をクリックします。一瞬作成中のような雰囲気になりましたが、すぐにできました。「照合」は「utf8_general_ci」が選択されたようですね。

データベース一覧に「Sample_DB」が追加されました。

テーブルの作成

 次にテーブルを作成します。まずは、MySQLに接続しなければいけないのですが、、、どうやるのでしょうか。「概要」の「このインスタンスと接続」にありました!

このインスタンスとの接続

 インスタンスに接続するさまざまな方法については、ドキュメントに書いてあるようですが「詳細」はあとにして、「CLOUD SHELLを開く」をクリックしてみました。

ターミナルが起動しました

gcloud sql connect sample-db –user=root –quiet

というコマンドで接続できるようです。Enterキーを入力したところ、、、

Cloud Shellの承認画面

 Cloud Shellの承認画面が表示されました。「承認」をクリックします。

エラー発生!

 おお、アクティブなアカウントが選択されていない、と申されましても。。。現在どのアカウントでログインしているのでしょうか。試しにもう一回実行してみます。おや、エラーが変わりました。

違うエラーが出ました!

 今度は「Cloud SQL Admin API」がこのプロジェクトではまだ使われておらず、有効になっていない、ということのようです。APIを有効にするには、表示されたURLをクリックせよ、ということでクリックしてみますと、ブラウザで別のタブが開きました。

Cloud SQL Admin APIを有効にしよう

 なるほど、「gcloud sql」コマンドを使うには、このAPIを有効にしないといけないのですね。承知いたしました。「有効にする」をクリックします。くるくるして、次の画面が表示されました。

Cloud SQL Admin APIの画面

 とりあえず、Cloud SQL Admin APIが有効になったようです。ターミナルから再度実行してみます。

5分、待ちます

 おお、コネクションのために5分必要なのでしょうか。。。パスワードを聞かれたので、先ほど自動生成したパスワードを入力して、Enterキーを押下すると、、、できました!!!

接続できました!

 先ほど作ったデータベースを確認してみましょう。「show databases;」と入力してみます。

データベースの確認結果

 次に、データベースの選択ですね。「use Sample_DB;」を実行して、テーブルが無いことを確認します。「show tables;」を入力してEnterキーを押下します。

データベースの切り替えとテーブル一覧の表示

 やっと、テーブルが作成できます!「create table sample_table ( id int auto_increment, t text, primary key(id) );」実行してみます。さらに結果を「show tables;」で表示してみます。

sample_tableを作ってみました。

 データをインサートしてみます。

insertを実行

 入りました。念のため、結果を確認します。

SELECT成功!

できました!

インスタンスの停止

 サンプルでしか使わないインスタンスですので、すぐに終了します。インスタンスが存在していると、利用していなくても放置しているだけで、課金されてしまいます。

 念のため、先ほどの接続ですが、データをコミットして、終了します。

commit;してexit;

 「概要」から「停止」をクリックします。

「停止」メニュー

 すると、以下のようなメッセージが表示されますので、「停止」をクリックします。

 停止しました。

追記(2021-09-30)

 作成直後に確認したときは、請求予定金額が0円でした。作成には請求がかからないようになっている、というのもちょっと変な話なので、タイミングを改めて請求金額を確認してみようと思っていて、確認してみました。

お支払いレポート

 およそ一週間で176円、ということでした。データベースインスタンスを停止したので、もう課金は発生しないはず、というふうに思っていたのですが、予想が外れました。一体何に課金されているのでしょうか。ということで見てみました。

  • Cloud SQL for MySQL: Zonal – IP address reservation in Americas 139.62 hour ¥154
  • Cloud SQL for MySQL: Zonal – Low cost storage in Americas 1.97 gibibyte month ¥19
  • Cloud SQL for MySQL: Zonal – Micro instance in Americas 2.19 hour ¥3

 上から青い線、赤い線、オレンジの線、の順番です。オレンジは最初だけですが、他のは安定していますね。青はIPアドレスの予約、ということなので、利用していなくても課金されてしまう、ということなのでしょうね。赤は、安いストレージ、ということなので、容量を使用している分が、課金されている、ということですね。オレンジはインスタンスなので、停止したから課金されない、ということのようです。微々たる金額ではありますが、使わなくても存在しているだけで請求が発生することがわかりました。IPアドレスは毎日およそ30円なのですね。

まとめ

 やはり、書籍やWebで調べることも大事ですが、実際に試してみてよかったです。かなり長くなってしまいましたが、ここまで読んでくださって、ありがとうございます。Cloud SQLのMySQLインスタンスの作り方がわかりました。実際の業務で利用しようと思うと、クライアントのPCから接続する方法とか、他のGoogle App Engineサービスなどから接続する方法など、もうちょっと調べる必要があると思います。とりあえず、試せる環境ができました。

骨伝導ヘッドセットAFTERSHOKZ OPENCOMMを購入しました。

 今日も見にきてくださって、ありがとうございます。石川さんです。暑い日が続きますが、みなさまいかがお過ごしでしょうか。

 ついに、買いました!そう、タイトルの通り、骨伝導のヘッドセットAFTERSHOKZ OPENCOMMです。随分前になりますが、骨伝導のヘッドフォンが発売されていたのをビックカメラの店頭で見かけて、「欲しい!」と思っていたのですけど、普通のヘッドフォンと比べるとかなりお高くて、ずっと保留にしていたのでした。この度ご縁があってウェブセミナーのようなことを始めることになりまして、お仕事で使うんだから、ヘッドセットもいいやつを買っちゃいましょう!と、ポチリとやっちゃいました。19,998円、やっぱり高いですねぇ。

 そして、今日、届きました。外観はこんな箱に入ってます。

 箱を開けてみたところ、意外にケース付きでした。

 なるほどね、ぴったりケースにはまるように作られているわけですね。これで移動中に曲げて壊したりする心配はありませんね。ただ、ケースがちょっと大きいからジャマかもなぁ。他にちょっと気になるのは、電源部分ですね。付属の専用コードがないと充電できません。マグネット式でカチッと収まるのはいいのですけど、USB-Cとかじゃないのですね。ま、仕方ありませんね。

 ペアリングは、電源をオフにした状態から開始、「+」ボタンが電源兼用になっていて、これを長押し(5秒)すると電源が入ってペアリングモードになる、と。そして、端末側で選択すれば、オッケー、と、簡単ですね。iPad ProでもAndroid端末でも、すぐに設定できました。

 ちょっと使ってみた感じでは違和感もなく、いい感じです。後頭部にワイヤーがあるので、背もたれにもたれられませんが、気になるのはそれくらいでしょうか。やっぱり最大のメリットは、耳を塞いでいない、ということですよねぇ。耳を塞いでいないのに、音が聞こえてくる、という体験は、不思議な感じです。第2の耳ができた、という感じですかね。

 また気が向いたら使用感などレポートしてみます。

Python プログラミング Anaconda3 再起動後、なぜか Spyder が起動できない!

 いつも見にきてくれてありがとうございます。石川さんです。スイカの季節ですね。いや、今ならオリンピックの話題が優先でしょうか。オリンピック、あんまり興味が持てないんだよねぇ。せっかく東京でやるのにね。

概要

 Anacondaに付属してくるSpyder IDEを使っていたのですが、パソコン再起動後、突然起動しなくなりました。原因はプロジェクトを利用するときに、日本語のファイル名を使ったことでした。Bugのようですが、対応方法について記載しておきました。

経緯

 最近、Pythonのスクリプトの編集、実行には、AnacondaをインストールするとついてくるSpyderを使っています。しかし、今日は、パソコンを再起動したあとに、Spyderを起動するとなぜか立ち上がらなくなってしまいました。えっ、どうした、Spyderよ。立ち上がって来いよ~。そんなに貧弱じゃないだろ?と、言いつつ、Anacondaプロンプトですらヒストリが読めなくて起動時にエラーを出力していたことを思い出しました。何か、入力しちゃいけない文字コードを入力してしまったのでしょうか。しかし、起動した後に、スプラッシュ画像が出て、そのままお亡くなりになるなんて、聞いてないよ~。少なくともエラーメッセージを出して欲しいよ。どうしようもないじゃない、Spyderさん。

まずはSpyderを開始できるようにします

 さて。気を取り直して、実行プログラムのありかを探して、まずはそこからでしょうか、と、思って実行プログラムを見ると、おや、今まで気にもならなかったプログラムがあるじゃないですか!そう、「Reset Spyder Settings (anaconda3)」です。こんなの、今まで、気にしたこともなかったなぁ。

Reset Spyder Settings(anaconda3)

 さて、実行したところ、何やら設定がリセットされたような気がしますので、再度実行してみます。心なしかスプラッシュ画像が小さくなったような気がします。何とか立ち上がりました。アイコンも心なしか小さくなったような気がしますねぇ。。。ま、それはいいか。

アイコンが小さくなったような気がする

原因を追究します

 直前に作業していたのは、、、と、記憶をたどると、そうそう、新しくプロジェクトを作成して、そこにドキュメントフォルダをつくって、Sphinxのドキュメントを構築しようとしていたんだった、と、いうことを思い出しました。ということで、その新しいプロジェクトを再度開いてみると、、、おお、エラーか?。。。エラーですね。「Issue reporter」なる画面が起動されました。ふむふむ「Spyder has encountered an internal problem!」ということですね。内部の問題に直面しました、ということなのですが、問題レポートを送ればいいのかな?

Issue reporter

 いや、送る前に、包括的なトラブルシューティングガイドで相談してください、と、書いてありますね。ほとんどの問題が解決されるらしいです。あとは、分かっているバグ、を検索してということだけど、検索しようにも何のメッセージも出てないしなぁ。。。そもそもこの「Issue report」は出てきたんだけど、エラーメッセージは何にも出力されていないのですよね。だからレポートを書くとしたら、「プロジェクトを開こうとしたら、内部的な問題に出会いました」くらいしか書きようがないですよね。とりあえず、「Close」でよいのかな?あ、「Close」ボタンの隣に「Show details」ボタンを発見しました!このボタンを押して詳細を見てみよう。と、ポチリとした結果、下部にエラーメッセージが出てまいりました。

Show detailsを押した結果

テキストは一部分だけが見える状態でしたので、以下に全文を張り付けておきます。これだけ情報があれば、なんとかなりそうですね。

Traceback (most recent call last):
  File "C:\Users\mitsu\anaconda3\lib\site-packages\spyder\plugins\projects\plugin.py", line 129, in <lambda>
    triggered=lambda v: self.open_project())
  File "C:\Users\mitsu\anaconda3\lib\site-packages\spyder\plugins\projects\plugin.py", line 402, in open_project
    project_type_class = self._load_project_type_class(path)
  File "C:\Users\mitsu\anaconda3\lib\site-packages\spyder\plugins\projects\plugin.py", line 813, in _load_project_type_class
    config.read(fpath)
  File "C:\Users\mitsu\anaconda3\lib\configparser.py", line 697, in read
    self._read(fp, filename)
  File "C:\Users\mitsu\anaconda3\lib\configparser.py", line 1017, in _read
    for lineno, line in enumerate(fp, start=1):
UnicodeDecodeError: 'cp932' codec can't decode byte 0x81 in position 269: illegal multibyte sequence

 想定の範囲内ですが、UnicodeDecodeErrorですね!記憶にないけど、プロジェクト作るときに、日本語を使っちゃったのかなぁ。configparser.pyでエラーが発生しているのと、起動時は問題なくてプロジェクトを開くときにエラーが発生したので、プロジェクトのフォルダを見に行ってみます。ありましたね「.spyproject」フォルダが怪しいです。ちょっと覗いてみます。

エラーになったプロジェクトのフォルダ

 なるほど、「config」フォルダがあって、その中に複数の「.ini」ファイルがあります。この中をひとつずつ見ていきましょう。

エラーになったconfigフォルダ

 見つけました。「workspace.ini」ファイルですね。ここにマルチバイト文字がありました。そうそう、Sphinxで使う「.rst」のファイル名を日本語にしたのを思い出しました。このファイル自体はUTF-8で保存されていたので、うまく開けないのはどうしてでしょうね。

[workspace]
restore_data_on_startup = True
save_data_on_exit = True
save_history = True
save_non_project_files = False
project_type = empty-project-type
recent_files = ['..\\..\\..\\Users\\mitsu\\.spyder-py3\\temp.py', 'doc\\source\\index.rst', 'doc\\source\\このドキュメントについて.rst', 'doc\\source\\conf.py']

[main]
version = 0.2.0
recent_files = []

さらに原因を追究します

 configparser.pyのソースファイルを覗いてみたところ、ファイルをencoding=Noneとしてオープンしていることがわかりました。encoding=Noneとした場合は、ロケールのデフォルトエンコーディングが使用されます。つまり、日本語のWindows10環境では「cp932」が使われる、ということになります。configparser.pyの中にはファイルへの書き込みのためのオープンが見当たらなかったので、どのような設定になっているのかはちょっと不明だったのですが、上述のとおりファイルの内容はUTF-8で書き込まれていたので、書き込み時はUTF-8でオープンされているのだと思います。

では、修正しましょう

 さて、どう修正するのがよいでしょうか。気持ちとしてはconfigparser.pyのencoding=”utf-8″としたいところですが、影響範囲がはかり知れないので、Spyderに限って考えたいと思います。そうすると、エラーメッセージで指摘されている、C:\Users\mitsu\anaconda3\lib\site-packages\spyder\plugins\projects\plugin.pyの813行目に、encoding=”utf-8″を追加するのがよいでしょうか。

        config.read(fpath)  # 修正前
        config.read(fpath,encoding="utf-8")  # 修正後

 さて、修正してみたまではよかったのですが、果たして反映されるのでしょうか。Spyderって、実行形式で配布されている訳じゃないのかな?と、迷っている間に実行できそうなので、ささっとSpyderを再起動してみます。おっ、できました。エラーは発生しませんでした。

エラーなく日本語ファイルが開けました

まとめ

 結論としては、UTF-8で保存したファイルをローカルロケールでオープンして読み込んだためにエラーが発生した、ということでしたね。なので、パソコン再起動が原因ではなくて、Spyderの再起動が原因、ということでした。Pythonはバージョン3から完全UTF-8対応したはずなのに、どうしてこんなことが起きるのかというと、デフォルトエンコーディング、大混乱しているようですね。「Fluent Python Pythonicな思考とコーディング手法」を参考に、Pythonのデフォルトエンコーディングに影響を与える設定を確認してみました。

Python 3.8.8 (default, Apr 13 2021, 15:08:03) [MSC v.1916 64 bit (AMD64)]
Type "copyright", "credits" or "license" for more information.

IPython 7.22.0 -- An enhanced Interactive Python.

In [1]: import sys, locale

In [2]: locale.getpreferredencoding()
Out[2]: 'cp932'

In [3]: my_file = open("dummy","w")

In [4]: type(my_file)
Out[4]: _io.TextIOWrapper

In [5]: my_file.encoding
Out[5]: 'cp932'

In [6]: sys.stdout.isatty()
Out[6]: False

In [7]: sys.stdout.encoding
Out[7]: 'UTF-8'

In [8]: sys.stdin.isatty()
Out[8]: False

In [9]: sys.stdin.encoding
Out[9]: 'cp932'

In [10]: sys.stderr.isatty()
Out[10]: False

In [11]: sys.stderr.encoding
Out[11]: 'UTF-8'

In [12]: sys.getdefaultencoding()
Out[12]: 'utf-8'

In [13]: sys.getfilesystemencoding()
Out[13]: 'utf-8'

In [14]: my_file.close()

 書籍はブラジル向けにローカライズされたWindows 7の結果が示されており、結果はなんと4種類のエンコーディングが出力されていました。それに比べると日本語のWindows10環境は「cp932」と「utf-8」の二つしかないので、まだマシな方かも知れませんね。(苦笑)

Python プログラミング Anaconda3でのpytestの警告を回避、iPad Proの a-Shell で pytest 始めてみました

 今日も見にきてくださってありがとうございます。石川さんです。梅雨入りしてジメジメが続きますね。

 さて、先日翔泳社の『テスト駆動Python』という書籍を購入しまして、さ〜て、やりますか、と張り切っていたのですが、1章の1ページ目からうまく動かず。実行はできたのですけど、何故か警告が出力されてしまうのですよね。ぼくの環境は、Windows 10 & Anaconda3です。

(base) C:\work\pytest\ch1>pytest test_one.py
================================================= test session starts =================================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: C:\work\pytest\ch1
plugins: anyio-2.2.0
collected 1 item

test_one.py .                                                                                                    [100%]

================================================== warnings summary ===================================================
..\..\..\ProgramData\Anaconda3\lib\site-packages\pyreadline\py3k_compat.py:8
  C:\ProgramData\Anaconda3\lib\site-packages\pyreadline\py3k_compat.py:8: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working
    return isinstance(x, collections.Callable)

-- Docs: https://docs.pytest.org/en/stable/warnings.html
============================================ 1 passed, 1 warning in 0.08s =============================================

(base) C:\work\pytest\ch1>
警告が出力される

 なるほど、Anaconda3のpyreadlineモジュールで警告ですね。これは問題だと言われているファイル、py3k_compat.pyの8行目、「return isinstance(x, collections.Callable)」のところの「collections.Callable」を「collections.abc.Callable」に修正することで回避できました。そういえば、ちょっと前にもこちらの記事でpyreadlineのモジュール、修正していましたね。警告が出なくなってスッキリしたのですが、ふと、最近購入した、iPad Proでもできるんじゃないかな、と、やってみました。

実行環境のセットアップ

 iPad Proでのpythonというと、大体は「Pythonista 3」か、jupyter notebook系だと「Juno」「Carnets」がおすすめされることがほとんどだと思います。でも、今回使ったのは、「a-Shell」というアプリで、iOSで簡単なUnixコマンドが使えるシェルです。ここから「python」や「ipython 」を実行することができます。App Storeの検索で「a-Shell」を入力してぐるぐるのアイコンを探してインストールします。

a-Shellのアイコン

 インストールできたら、実行します。黒いコマンドラインがフルスクリーンで登場します。pytestを実行してみましたが、コマンドが存在しない(command not found)、と、エラーになります。pytestを使えるようにするためには、インストールが必要ですね。pipコマンド「pip install pytest」で簡単に実行できるようになりました。

$ pytest
pytest: command not found
$ pip install pytest
Defaulting to user installation because normal site-packages is not writeable
Collecting pytest
  Downloading pytest-6.2.4-py3-none-any.whl (280 kB)
     |████████████████████████████████| 280 kB 4.0 MB/s 
Requirement already satisfied: packaging in /private/var/containers/Bundle/Application/10B03820-2240-4A08-AA93-CE205988D553/a-Shell.app/Library/lib/python3.9/site-packages (from pytest) (20.9)
Requirement already satisfied: attrs>=19.2.0 in /private/var/containers/Bundle/Application/10B03820-2240-4A08-AA93-CE205988D553/a-Shell.app/Library/lib/
python3.9/site-packages (from pytest) (20.3.0)
Requirement already satisfied: toml in /private/var/containers/Bundle/Application/10B03820-2240-4A08-AA93-CE205988D553/a-Shell.app/Library/lib/python3.9/site-packages (from pytest) (0.10.2)
Collecting py>=1.8.2
  Downloading py-1.10.0-py2.py3-none-any.whl (97 kB)
     |████████████████████████████████| 97 kB 10.5 MB/s 
Collecting iniconfig
  Downloading iniconfig-1.1.1-py2.py3-none-any.whl (5.0 kB)
Collecting pluggy<1.0.0a1,>=0.12
  Downloading pluggy-0.13.1-py2.py3-none-any.whl (18 kB)
Requirement already satisfied: pyparsing>=2.0.2 in /private/var/containers/Bundle/Application/10B03820-2240-4A08-AA93-CE205988D553/a-Shell.app/Library/lib/python3.9/site-packages (from packaging->pytest) (2.4.7)
Installing collected packages: py, pluggy, iniconfig, pytest
Successfully installed iniconfig-1.1.1 pluggy-0.13.1 py-1.10.0 pytest-6.2.4

実行してみます

  書籍の内容を元に、test_one.pyファイルを作成して、以下の通り実行できました。

$ pytest test_one.py
================================================================= test session starts ==================================================================
platform darwin -- Python 3.9.2+, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /private/var/mobile/Containers/Data/Application/117A9320-8100-4E66-A829-2B31D0457140/Documents/work/pytest/ch1
collected 1 item                                                                                                                                       
test_one.py .                                                                                                                                    [100%]
================================================================== 1 passed in 0.01s ===================================================================
$ 
実行結果

はい、見事に成功しましたね。pytestでテスト対象として自動的に実行されるためには、

  • ファイル名が test_*.py または、*_test.py という名前であること
  • 関数名やメソッド名は、test_* という名前であること
  • クラス名は Test* という名前であること

ということのようです。最初、テスト関数名を「est_passing」と「t」を欠落して作ってしまっていました。その欠落が原因で実行されなかったのですが、「t」の欠落に気づくまでは、警告があると実行できないのか〜、と勘違いしてしばらくウロウロしちゃいました。無事、解決できてよかったです。

お気に入り

 最近は、iPad Pro がお気に入りで、色々と遊んでいます。手書きの性能がとっても良いのが特にお気に入りで、近頃は手帳の替わりにノートアプリの「Goodnotes5」を使うようになりました。手書きのいいところと、電子のいいところの両方が享受できていて、大満足です。
 で、せっかく持ち歩けるのだから、pythonを使ったプログラミングもできないかなぁ、と、色々と検索してたどり着いたのが、今回紹介した「a-Shell」です。ただ、こちら tkinter が使えないので、しばらくは使い道がないなぁ、と思っていたのですが、GUIを利用しない今回のようなケースであれば活用できるはずだ、と、今回張り切って記事にしてみました。

まとめ

 Anaconda3 で pytest を実行するときにでた警告の修正と、iPad Proで pythonを使って pytest を実行する方法について記載しました。これでしばらくは気軽に pytest で遊ぶことができそうです。
 ちなみに、今回の記事は、iPad Pro を使って書いてみました。って、どこ見てもそんなこと分かりませんね。失礼しました!