Pythonのユーザ定義関数を「y=2x+1」を使って解説する
はじめに
以前ブログで「VBAのFunctionプロシージャを「y=2x+1」を使って解説する」という記事を書きました。
今回はそのPython版です。
VBAの記事のときも書いたのですが、以前は「自分で関数を作れる」ということがどういうことなのか、正直よくわかりませんでした。「引数」と「戻り値」のことも、解説を読んでも理解しにくかったです。
そんなときにユーザ定義関数を一次関数で表したら、なんとなくわかりやすくなった気がしたので、Pythonでも書いてみようと思いました。
ちなみに一次関数とは、以下のように定義されています。
a、bを定数とするときy=ax+b(a≠0)で表される関数
※コトバンクより抜粋 https://kotobank.jp/word/%E4%B8%80%E6%AC%A1%E9%96%A2%E6%95%B0-433347
この記事のタイトルの一部の「y=2x+1」は一次関数です。
◆目次◆
用語のおさらい
用語 | 意味 |
---|---|
ユーザ定義関数 | ユーザーが独自に定義した関数 |
引数 | 関数に渡す値 |
戻り値 | 関数から受け取る値 |
ユーザ定義関数のおさらい
Pythonのユーザ定義関数は以下のように作ります。
def 関数名(引数): 処理 return 戻り値
引数に値をいれて関数を呼び出したいときは、以下のようにな呼び出し用のスクリプトを作ります。
関数名(引数の値)
「y=2x+1」をユーザ定義関数で作る
では、さっそく「y=2x+1」をPythonのユーザ定義関数で書いてみます。今回は2種類の書き方を紹介します。
まずは1つ目のユーザ定義関数です。処理の部分に「y=2x+1」を書きます。
def linear_function(x): y = 2 * x + 1 return y
「linear_function(直訳:一次関数)」という関数名のユーザ定義関数を作ります。 行いたい処理は「y = 2 * x + 1」です。答えはyの値なので、変数yを戻り値にします。
呼び出し用のスクリプトは、例えば「x=2」の場合は、以下のように書きます。
linear_function(2)
2つ目のユーザ定義関数です。戻り値に処理を書きます。
def y(x): return 2 * x + 1
「y」という関数名のユーザ定義関数を作ります。
「2 * x + 1」の処理結果を戻り値とするので、「2 * x + 1」が戻り値となります。つまり「return 2 * x + 1」と書けばいいのです。
このような簡単な処理であれば、戻り値に、行いたい処理を設定してもいいと思います。
呼び出し用のスクリプトは、例えば「x=2」の場合は、以下のように書きます。
y(2)
さいごに
こうやって並べると、1つ目のユーザー定義関数の方がぱっと見でわかりやすいですね(「y = 2 * x + 1」がそのまま入っているので)。
1つ目の書き方だと、複雑な処理も設定できるので、個人的にはこちらの書き方をする場合が多いです。(もちろん、ケースバイケースですが)
このように一次関数で「引数はx、戻り値はy(または処理結果)」と考えていただくと、少しわかりやすくなるかもしれません。
Excelライブラリを学ぶ
はじめに
しばらくブログを書いていなかったので、リハビリとして、2か月前に受けたVBA中級講座の第4回の「Excelライブラリ」の内容について、(リハビリで選ぶには、ハードな題材だけど・・・)一部まとめを書きます。
◆目次◆
VBAで出てくる用語のおさらい
まずはExcelライブラリと向き合うにあたって、出てくる用語の意味を調べなおします。
用語 | 意味 |
---|---|
ライブラリ | モジュールをまとめたもの |
クラス | クラスモジュールにオブジェクトとそのメンバーを定義したもの |
モジュール | 宣言ステートメントをまとめたもの |
オブジェクト | 操作対象のすべてのもの(セル、シート、ブック、グラフなど) |
コレクション | 同じ種類のオブジェクトをまとめたもの |
メンバー | コレクションの1つ1つのオブジェクトのこと |
グローバル | どこからでも同じように参照できるもの (対義語は「ローカル」、あるいは「プライベート」) |
これらをふまえて、Excelライブラリとは何かを確認します。
Excelライブラリとは
Excelライブラリの概要
Excelを操作するためのライブラリです。
Excel VBAでは初期状態で「Microsoft Excel 16.0 Object Library」が参照可能な状態になっています。
Excelライブラリでは、Excelのさまざまなものが、クラスとして定義されています。
このExcelライブラリを使ってVBAで操作することを「Excel VBA」といいます。
VBAではExcelを操作するときに、上位のオブジェクトから配下のオブジェクトをたどって操作対象のオブジェクトを取得します。
Excelライブラリの階層構造はこんな感じです。
Applecation | |- WorkSheets | |- Sheets | |- Range
Excelライブラリの参照
Excelライブラリはすべてクラスで構成されています。言い換えると、Excelライブラリはすべてオブジェクトモジュールでできています。
Excelライブラリには、取得するオブジェクト型と同名のプロパティ名が存在します。配下のオブジェクトを取得するには、以下のようにコードを書きます。
オブジェクト.プロパティ
でも、VBAで以下のようにコードを書いてもエラーにはならないですよね(上位オブジェクトが省略できます)。
Sub test1() Debug.Print Range("A1") End Sub
本来であれば、Rangeオブジェクトを操作するには、Applecationクラスからたどるように、コードを書かないといけないはずです。
なぜエラーにならないのでしょうか。
それは、VBAでは「グローバルのメンバーは上位オブジェクトを省略できる」というルールがあるからです。
(すべてのメンバーを表示しきれていませんが)以下の赤枠内「<グローバル>のメンバー」は、上位オブジェクトを省略できます。
以下のとおり、RangeはExcelライブラリのグローバルのメンバーです。
上位オブジェクトを省略できたのは、こういう背景があったのですね。
上位オブジェクト省略時の注意事項
さて、Excelライブラリのグローバルのメンバーであれば、上位オブジェクトを省略できることはわかりました。
しかし、むやみに省略すると問題が発生する場合あります。大きな理由は以下の2点です。
- モジュールによって省略の意味が変わるから(シートモジュールと標準モジュールでは結果が異なる場合がある)
- ユーザー操作の干渉を受けるから(ユーザーが選択しているオブジェクトによって結果が異なる場合がある)
さいごに
Excelライブラリについて、講座の内容をひとつずつかみ砕きながら解き進めました。今回の肝はここでした。
それは、VBAでは「グローバルのメンバーは上位オブジェクトを省略できる」というルールがあるからです。
実は、講座では続きがあって、「既定のメンバー」について解説を受けました。
が、長くなるので、それはまた別の機会に書きます(そのときは_Defaultプロパティについて書くのか、大変だなあ・・・)。
VBAツール ※この記事は個人的な備忘録です
はじめに
- この記事は個人的な備忘録です。
- 業務で使用するためのサンプルツール用のコードなどを記述します。書きかけのコードもあります。
- あくまでサンプルのため、コードの実践は自己責任でお願いします。
概要
転記ツール
'[Module1] Enum header 旧型番 = 1 旧製品名 旧受注停止日 旧出荷停止日 新型番 新製品名 新受注停止日 新出荷停止日 製品名 掲載順判定 End Enum Sub 掲載用販売停止情報作成() Call 新型番表シート.最終行確認 Call 旧型番表シート.配列作成 Call 掲載順シート.最終行確認 Call 掲載用_販売停止情報シート.最終行確認 Call 対象外型番判定 Call 販売停止情報転記 Call 掲載順整理 Call 掲載用_販売停止情報シート.セル結合 Call 掲載用_販売停止情報シート.製品名挿入 End Sub Function 旧製品名検索一致(ByVal i As Long) As String Dim j As Long For j = 2 To UBound(旧型番表シート.配列, 1) If 新型番表シート.Cells(i, 11).Value = 旧型番表シート.配列(j, 1) Then 旧製品名検索一致 = 旧型番表シート.配列(j, 3) End If Next j End Function Function 掲載順置換(ByVal i As Long) As String Dim order As String Dim j As Long For j = 2 To 掲載順シート.最終行_製品名 If Left(新型番表シート.Cells(i, 11).Value, 6) = 掲載順シート.Cells(j, 1).Value Then order = 掲載順シート.Cells(j, 3).Value End If Next j order = order & "_" & 新型番表シート.Cells(i, 9).Value For j = 2 To 掲載順シート.最終行_分類 If 新型番表シート.Cells(i, 2).Value = 掲載順シート.Cells(j, 4).Value Then order = order & "_" & 掲載順シート.Cells(j, 5).Value End If Next j 掲載順置換 = order End Function Sub 対象外型番判定() Dim i As Long For i = 2 To 新型番表シート.最終行 If Mid(新型番表シート.Cells(i, 1).Value, 4, 1) = "P" Then MsgBox 新型番表シート.Cells(i, 1).Value & "はPシリーズ製品です。処理を中止するので" & i & "行目を削除してください。" Exit Sub ElseIf 新型番表シート.Cells(i, 11) = "-" Then MsgBox 新型番表シート.Cells(i, 1).Value & "は前バージョンがない製品です。処理を中止するので" & i & "行目を削除してください。" Exit Sub End If Next i End Sub Sub 販売停止情報転記() Dim num As Long '販売停止情報シートのカウンタ関数 num = 1 Dim i As Long For i = 2 To 新型番表シート.最終行 num = num + 1 With 販売停止情報シート .Cells(num, header.旧型番).Value = 新型番表シート.Cells(i, 11).Value .Cells(num, header.旧製品名).Value = 旧製品名検索一致(i) .Cells(num, header.旧受注停止日).Value = 旧製品受注出荷停止日シート.Cells(2, 1).Value .Cells(num, header.旧出荷停止日).Value = 旧製品受注出荷停止日シート.Cells(2, 2).Value .Cells(num, header.新型番).Value = 新型番表シート.Cells(i, 1).Value .Cells(num, header.新製品名).Value = 新型番表シート.Cells(i, 3).Value .Cells(num, header.新受注停止日).Value = 新型番表シート.Cells(i, 7).Value .Cells(num, header.新出荷停止日).Value = 新型番表シート.Cells(i, 8).Value .Cells(num, header.製品名).Value = 製品名抽出(i) .Cells(num, header.掲載順判定).Value = 掲載順置換(i) End With Next i End Sub Function 製品名抽出(ByVal i As Long) As String Dim j As Long For j = 2 To 掲載順シート.最終行_製品名 If Left(新型番表シート.Cells(i, 11).Value, 6) = 掲載順シート.Cells(j, 1).Value Then 製品名抽出 = 掲載順シート.Cells(j, 2).Value End If Next j End Function Sub 掲載順整理() Dim 転記範囲 As Range Set 転記範囲 = 販売停止情報シート.Range("A1").CurrentRegion With 転記範囲 .Sort Key1:=.Range("J1"), Order1:=xlAscending, header:=xlYes .Resize(.Rows.Count - 1).Offset(1, 0).Copy End With 掲載用_販売停止情報シート.Range("B9").PasteSpecial Paste:=xlPasteValues End Sub
[旧型番表シート] Public 配列 As Variant Sub 配列作成() 配列 = Cells(1, 1).CurrentRegion.Value End Sub
[掲載順シート] Public 最終行_製品名 As Variant Public 最終行_分類 As Variant Sub 最終行確認() 最終行_製品名 = Cells(Rows.Count, 1).End(xlUp).Row 最終行_分類 = Cells(Rows.Count, 4).End(xlUp).Row End Sub
[掲載用_販売停止情報シート] Public 最終行 As Long Sub 最終行確認() 最終行 = Cells(Rows.Count, 2).End(xlUp).Row End Sub Sub セル結合() Call 最終行確認 Application.DisplayAlerts = False Dim i As Long For i = 9 To 最終行 Step 3 Range(Cells(i, 3), Cells(i + 2, 3)).Merge Range(Cells(i, 7), Cells(i + 2, 7)).Merge Next i Application.DisplayAlerts = True Range("C:C").WrapText = True Range("G:G").WrapText = True End Sub Sub 製品名挿入() Call 最終行確認 Dim i As Long For i = 最終行 To 9 Step -1 If Cells(i, 10) <> Cells(i - 1, 10) And Cells(i - 1, 10) <> "" Then Rows(8).Copy Rows(i).Insert Shift:=xlDown Application.CutCopyMode = False Cells(i, 2).Value = Cells(i + 1, 10).Value ElseIf Cells(i - 1, 10) = "" Then Cells(i - 1, 2).Value = Cells(i, 10).Value End If Next i Range("J:K").Delete Rows(最終行 & ":1000").Delete End Sub
[新型番表シート] Public 最終行 As Long Sub 最終行確認() 最終行 = Cells(Rows.Count, 1).End(xlUp).Row End Sub
シート名など
Excel VBAのクラスを学ぶ3 カプセル化と1~3回目までの講座の振り返り
はじめに
前回までの2回はVBA中級講座の受講内容についてまとめてました。
その過程でクラスについていろいろ調べていたところ、結構気になることが出てきたので、別の記事でまとめることにしました。
今回は、「オブジェクト指向の3大要素って何?」「カプセル化って何?」という疑問の消化と、これまでの講座の振り返りです。普段の記事よりも個人的な備忘録の要素が強く、いろんな方のブログの引用が多めです。
◆目次◆
オブジェクト指向の3大要素とは
VBAの場合、継承と多態性は使えなくて、カプセル化だけを意識すれば良いようです。
以下引用
https://excel-ubara.com/vba_class/VBA_CLASS_01.html
(VBAのことをネットで調べるとエクセルの神髄さんへたどり着く率高いですね)
心配はいりません、少なくともVBAでは、継承も多態性も使えないのですから、 (継承としてはインターフェースだけ使えますが、急ぎ覚える必要はないでしょう。) 従って、カプセル化だけを意識すれば良いのです。
カプセル化とは
以下引用 https://qiita.com/katolisa/items/6cfd1a2a87058678d646
- フィールド=private、メソッド=public
- フィールドを操作するためにgetterメソッドとsetterメソッドを用意する
= カプセル化である。
https://qiita.com/iverson3kobe0824/items/f3d7b93f6049b18b170e
やっていることはVBAも同じですね。ちなみにVBAではこんなコードになります。
例)Personクラス
'[Person]クラスモジュール Private age_ As Long Public Property Get Age() As Long Age = age_ End Property Public Property Let Age(ByVal newAge As Long) If newAge > 0 Then age_ = newAge Else age_ = 0 End If End Property '[Module1]標準モジュール Sub personclass_test1() Dim myPerson As Person Set myPerson = New Person myPerson.Age = 60 End Sub
宣言フィールドでプライベート変数を宣言して、パブリックなPropertyメソッドを経由して値を返します。
2回目の講座で、オブジェクトモジュールで同じことをしていました。つまり、今まで講座で学んでいたことはカプセル化の仕方も含まれていました!
3回目の講座のクラスの講座も同様です。
カプセル化のメリットはこの辺りでしょうか。
- 変数の中身が書き変わって、変な値で処理しないように制限する
- 値に制限が書けられる
もうちょっと調べてみました。
カプセル化のメリット
以下引用 https://xtech.nikkei.com/atcl/nxt/column/18/00208/031300003/
カプセル化のメリットは大きく分けて2つあり、1つめは、オブジェクトを利用する人はその操作の仕様だけを知っていれば、オブジェクト内の操作の実装やデータの内容を知る必要がないことです。 2つめは、関連する操作がひとまとめになっているので、理解しやすく変更の影響も局所化できることです。
こう見ると、「保守性が高くなる」という印象ですね。自分でクラスを使って管理できるようになると、実感できそうです。
継承とポリモーフィズム(多態性)とは
ほかの言語ではこの2つも使えるらしいので、それぞれの意味を調べました。
- 継承
以下引用 https://blog.codecamp.jp/java-inheritance
クラスの中身である変数やメソッドを、他のクラスに受け継がせる(=継承する)ことができます。
個人的にこれは便利そうなので使ってみたいです。
同じ命令でも、受け取り手が変わると実行内容が変わる(結果は変わらない・・・?)。
参考サイト https://java2005.cis.k.hosei.ac.jp/materials/lecture18/polymorphism.html
これはイメージがいまいち湧かないですね・・・。難しいです。
おわりに
クラスについて調べると、Javaの記事が多い印象でした。
まずは、クラスを知るには実践あるのみですね。
講座については、1回目からずっと、クラスを学習するための準備をしてきた印象です。
講座の最終的な目標は「VBAの開発や保守を楽に&スマートにできる知識とスキルを身につける」です。個人的な目標としては、「自分以外の人が保守ができるマクロを作る」としているので、まずはクラスの使いどころを掴みたいところですね。
VBA中級講座も折り返し地点なので、引き続き頑張りたいと思います。
Excel VBAのクラスを学ぶ2
はじめに
前回はクラスについて、プロパティを定義する方法までを説明しました。
今回は続きの、クラスにはメソッドを定義する方法から始めます。
◆目次◆
クラスにメソッドを定義する方法
オブジェクトにメソッドを追加する方法と同じです。
- Subプロシージャ
- Functionプロシージャ
例:Personクラスに、挨拶をするメソッドと、お腹がすいたら「ご飯を食べる」というメッセージを返すメソッドを追加
'[Person] Public Sub Greet() MsgBox "Hello!" End Sub Public Function Eat() As String If MsgBox("お腹がすいた?", vbYesNo) = vbYes Then Eat = "ご飯を食べる" End If End Function '[Module1] Sub personclass_test1() Dim myPerson As Person Set myPerson = New Person myPerson.Greet MsgBox myPerson.Eat End Sub
実行結果は、以下のとおりです。 * 「Hello!」というメッセージボックスが表示される * 「お腹がすいた?」というメッセージボックスが表示され、「はい」を選択すると、「ご飯を食べる」というメッセージボックスが表示される
Cloneメソッド
自身のコピーをオブジェクトとして生成し返すメソッドです。
例:同じ名前と血液型を持つクローンメソッドを作る
'[Person]クラスモジュール '変数Nameはパブリック変数にして、ほかのモジュールでも参照可能にする Public Name As String '変数bloodType_はプライベート変数にして、Propertyプロシージャでセッターとゲッターを作る Private bloodType_ As String Public Property Let BloodType(ByVal newBloodType As String) bloodType_ = newBloodType End Property Public Property Get BloodType() As String BloodType = bloodType_ End Property 'クローンメソッドを使って '同じ名前と血液型を持つインスタンスを戻り値を使って生成する Public Function Clone() As Person Dim objNew As Person Set objNew = New Person objNew.Name = Me.Name objNew.BloodType = Me.BloodType Set Clone = objNew End Function '[Module1]標準モジュール Sub cloneTest1() 'myPersonというPersonクラスのインスタンスを生成する Dim myPerson As Person Set myPerson = New Person myPerson.Name = "Bob" myPerson.BloodType = "A" 'clonePersonというmyPersonと同じ名前と血液型をクローンのインスタンスを生成する Dim clonePerson As Person Set clonePerson = myPerson.Clone Debug.Print clonePerson.Name '出力結果:Bob Debug.Print clonePerson.BloodType '出力結果:A 'clonePersonの名前を変更する clonePerson.Name = "Mike" 'myPersonとclonePersonの名前と血液型を確認する Debug.Print myPerson.Name '出力結果:Bob Debug.Print myPerson.BloodType '出力結果:A Debug.Print clonePerson.Name '出力結果:Mike Debug.Print clonePerson.BloodType '出力結果:A End Sub
clonePersonはmyPersonとは異なるインスタンスなので、clonePersonの名前(Name)を変更しても、myPersonに影響はありません。
コンストラクタとデストラクタ
どちらもイベントプロシージャで設定します。
- コンストラクタ
インスタンスが生成されたときに実行される。
Initializeイベントで呼び出されるプロシージャ
Private Sub Class_Initialize() 'インスタンスが生成されたときに行う処理 End Sub
- デストラクタ
インスタンスへの参照がすべて失われたときに実行される。
Terminateイベントで呼び出されるプロシージャ
Private Sub Class_Terminate() 'インスタンスへの参照がすべて失われたときに行う処理 End Sub
さいごに
まだクラスの使いどころがわかっていないので、これから活用して覚えていきたいと思います。
Excel VBAのクラスを学ぶ1
はじめに
以前「Excel VBAのクラスを学ぶ前の準備 - モジュールとプロパティ -」という記事を書きました。 (現在受講中のVBAの講座内容です)
先日とうとうクラスの講座を受講しました!
まだまだ理解不足のところもありますが、そこで学んだことをまとめたいと思います。
◆目次◆
クラスがあると何ができるのか?
- クラス:設計書(オブジェクトとそのメンバーを定義)
- インスタンス化:クラスから実体のオブジェクトを生成
つまり、設計書をもとにオブジェクトが生成できます。 利点は、複数のオブジェクトが作れるので、コードの管理がしやすくなります。
クラスモジュールの作り方
- 挿入→クラスモジュール
- プロパティウィンドウのオブジェクト名を編集してクラスの名前をつける
オブジェクトの作り方
- クラスモジュールを作成する
- 標準モジュールでクラス型の変数を宣言し、インスタンス化する
例:人間の設計図として、Personクラスを作成し、オブジェクトを生成
'[Person]クラスモジュールを作成後に '[Module1]で以下を宣言すると、オブジェクトが生成される Dim myPerson As Person Set myPerson = New Person
まだこの段階ではPersonクラスモジュールに何も書いていないので、設計図の中身がない状態です。
では、クラス(設計図)にプロパティを加えます。
クラスにプロパティを定義する方法
オブジェクトにプロパティを追加する方法と同じです。
- モジュールレベル変数
- Property Let/Setプロシージャ
- Property Getプロシージャ
例:Personクラスに名前と年齢の設定を追加
'[Person]クラスモジュールにプロパティを追加する Public Name As String Private age_ As Long Public Property Get Age() As Long Age = age_ End Property Public Property Let Age(ByVal newAge As Long) If newAge > 0 Then age_ = newAge Else age_ = 0 End If End Property '[Module1]で以下のように宣言すると 'Personクラスの設計図をもとにmyPersonオブジェクトが生成され 'myPersonオブジェクトに名前と年齢が設定できる Sub personclass_test1() Dim myPerson As Person Set myPerson = New Person myPerson.Name = Bob myPerson.Age = 15 End Sub
クラスにはメソッドも定義できますが長くなるので、続きは次回にします。
VBAでOutlookの受信メールフォルダから特定のメールの情報を抽出しExcelへ転記する ※この記事は個人的な備忘録です
はじめに
- この記事は個人的な備忘録です。
- 業務で使用するためのサンプルツール用のコードなどを記述します。書きかけのコードもあります。
- あくまでサンプルのため、コードの実践は自己責任でお願いします。
概要
まずはOutlookの参照設定をする
そして、マクロ記述してテストする
まずはどれくらい時間がかかるのかをチェックする
Option Base 1 Enum Header_ 件名 = 1 担当者 添付ファイル名 End Enum Sub メール内容抽出転記() Dim appOL As Outlook.Application Set appOL = New Outlook.Application '受信トレイのメールをすべて抽出 Dim objItems As Object Set objItems = appOL.GetNamespace("MAPI").GetDefaultFolder(6).Items Dim objMailItem As Object Dim arr() As String Dim txt As String Dim i As Long i = 1 For Each objMailItem In objItems With objMailItem If .Subject Like "*抽出のトリガー(件名)*" Then txt = .Body ReDim Preserve arr(2, i) arr(Header_.件名, i) = .Subject arr(Header_.担当者, i) = Mid(txt, InStr(txt, "抽出のトリガー(本文)") + 8, 2) i = i + 1 End If End With Next objMailItem Dim arr2 As Variant arr2 = WorksheetFunction.Transpose(arr) With Sheet4 .Range(.Cells(1, 1), .Cells(UBound(arr2, 1), UBound(arr2, 2))) = arr2 End With End Sub
Option Base 1 Enum Header_ 受信日時 = 1 件名 担当者 End Enum Sub メール内容抽出転記2() Dim appOL As Outlook.Application Set appOL = New Outlook.Application ' '受信トレイのメールをすべて抽出 Dim objItems As Object Set objItems = appOL.GetNamespace("MAPI").GetDefaultFolder(6).Items Dim objMailItem As Object Dim arr() As String Dim txt As String Dim i As Long i = 1 For Each objMailItem In objItems With objMailItem ' If .Subject Like "*1234*" Then If .Subject Like "*1234*" And .ReceivedTime Like "*2021/09*" Then txt = .Body ReDim Preserve arr(3, i) arr(Header_.受信日時, i) = .ReceivedTime arr(Header_.件名, i) = .Subject arr(Header_.担当者, i) = Mid(txt, InStr(txt, "12345678") + 8, 2) i = i + 1 End If End With Next objMailItem Dim arr2 As Variant arr2 = WorksheetFunction.Transpose(arr) With Sheet1 .Range(.Cells(1, 1), .Cells(UBound(arr2, 1), UBound(arr2, 2))) = arr2 End With End Sub