いろいろ備忘録日記

主に .NET とか Java とか Python絡みのメモを公開しています。最近Go言語勉強中。

Pythonメモ-107 (__main__.py について)(-m オプション, メインファイル)

概要

目から鱗の情報だったので、忘れないうちにメモメモ。そういえばそうだわwってなりました。知らなかったのが恥ずかしいですが。そういえば、Github とかでも よく __main__.py ファイル見かけていました。

bit.ly

__main__.py を使いましょうって記事です。

__main__ を復習

python やってる人だとほぼ全員以下を書いている or 書いたことがあるはずです。

if __name__ == "__main__":
    # 処理

python インタープリターは、ファイルを指定されて起動した場合 特殊変数 __name____main__ という値を設定してくれるので、それにより、この スクリプトファイル が他のスクリプトから import されて呼ばれたのか、python インタープリターで直接指定された起動したのかを見分けることが出来るってやつですね。どの本にも書いてある内容です。

んじゃ、__main__.py があるとどうなる?

上の参照記事でも詳しく書いてくださってますが、__main__.py をおいておくと、python インタープリターにて

-m オプションでパッケージ指定して起動した際に メインファイル として読み込まれる

ということになります。

どういうときに便利?

簡単な使い捨てスクリプトだと1ファイルなので

$ python hoge.py

とかやって、起動させてしまうのですが、ちょっとしたツール作るときとかはパッケージ構成にすることも多いですね。

例えば

$ tree -L 2
.
├── pkg1
│   ├── __init__.py
│   ├── __main__.py
│   └── __pycache__
├── pkg2
│   ├── __init__.py
│   ├── __pycache__
│   └── main.py
└── venv
    ├── bin
    ├── include
    ├── lib
    └── pyvenv.cfg

こんな風に pkg1 と pkg2 って構成にしていたとして(ちょっと変ですが説明用ってことで)、pkg1の方には __main__.py をおいてあります。 pkg2の方は 普通に main.py って名前でファイルおいてあります。中身はこんな感じ。

  • __main__.py (pkg1)
print("hello pkg1")
  • main.py (pkg2)
if __name__ == "__main__":
    print("hello pkg2")

pkg1 の場合、-m オプションつけて呼ぶ場合は、以下のように出来ます。

$ python -m pkg1

pkg2 の場合、以下のようにしないと動いてくれません。

$ python -m pkg2.main

pkg1 と同じように起動してみると

$ python -m pkg2
python: No module named pkg2.__main__; 'pkg2' is a package and cannot be directly executed

って怒られます。

pkg1の場合だと、パッケージ名を指定しているだけなので、個人的にすごい楽。

あと、__main__.py の場合だと、-m つけてパッケージ指定された際に呼ばれるのが確定してるので、そもそも

if __name__ == "__main__":
    # 処理

って書かなくてもいいのも楽。

配布とかしない自前ツールをパッケージ起動する場合とかに便利。

補足

上の例はあくまで -m オプションつけて呼ぶ場合の話です。setup.py を作って console_scripts で定義して

$ pip install -e .

とかして使う場合、setup.py には

entry_points={
    'console_scripts': [
        'pkg1=pkg1.__main__:main'
    ]
}

みたいに関数指定して書く必要があるので __main__.py には

def main():
    pass

if __name__ == "__main__":
    main()

って書いたりします。

python-packaging.readthedocs.io


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

セッション跨いで名前付き Mutex を使ったときにうまく動かなかった件 (System.Threading.Mutex, Global Mutex, 所有権, prefix "Global\")

概要

知らなかったので忘れないうちにメモメモ。

プロセスまたいで排他制御したいときに、よく Mutex とか Semaphore とか使いますが

このとき Mutex には名前をつけてやります。

var mutex = new Mutex(false, "hogehoge");

こんな風に。よくアプリの二重起動防止とかで使いますね。

dobon.net

セッション跨いでいる場合は?

んで、知らなかったのが、この使い方だと、同一セッション内では排他制御できるけど

セッション跨いでいる場合は駄目ってことでした。

(まあ、ほぼこんな状況ないと思いますが、、、、あったりするんです現実。。。)

つまり、片方をユーザセッションで起動、もう片方がタスクスケジューラで起動していて別セッションとかって場合です。

この場合、Mutex に付ける名前に

先頭に Global\ ってつけないと駄目

です。知らなかったです。

stackoverflow.com

docs.microsoft.com

ちゃんと書いてあった・・・。読めよ俺・・。

On a server that is running Terminal Services, a named system mutex can have two levels of visibility. If its name begins with the prefix "Global\", the mutex is visible in all terminal server sessions. If its name begins with the prefix "Local\", the mutex is visible only in the terminal server session where it was created. In that case, a separate mutex with the same name can exist in each of the other terminal server sessions on the server. If you do not specify a prefix when you create a named mutex, it takes the prefix "Local\". Within a terminal server session, two mutexes whose names differ only by their prefixes are separate mutexes, and both are visible to all processes in the terminal server session. That is, the prefix names "Global\" and "Local\" describe the scope of the mutex name relative to terminal server sessions, not relative to processes.

サンプル

一応サンプルです。

まず、2個 console プロジェクト作成

(1) ユーザセッション側で起動するやつ

$ mkdir MutexExampleUser
$ cd MutexExampleUser
$ dotnet new console

ソースはこんな感じ。

using System;
using System.Threading;

namespace MutexExampleUser
{
    class Program
    {
        const string MUTEX_NAME = "MyMutex";

        static void Main(string[] args)
        {
            var handle = false;
            using (var mutex = new Mutex(initiallyOwned: false, name: MUTEX_NAME))
            {
                try
                {
                    // 所有権を取る
                    handle = mutex.WaitOne(timeout: TimeSpan.Zero);
                    if (!handle)
                    {
                        Console.Error.WriteLine("所有権が取れなかった");
                        return;
                    }

                    Console.Error.WriteLine("所有権獲得: 1分待機...");
                    Thread.Sleep(TimeSpan.FromMinutes(1));
                }
                catch (Exception mutexEx)
                {
                    Console.Error.WriteLine(mutexEx);
                }
                finally
                {
                    if (handle)
                    {
                        // 所有権を開放
                        mutex.ReleaseMutex();
                        Console.Error.WriteLine("所有権開放");
                    }

                    mutex.Close();
                }
            }
        }
    }
}

(2) タスクスケジューラで起動するやつ

$ mkdir MutexExampleAnotherSession
$ cd MutexExampleAnotherSession
$ dotnet new console

ソースはこんな感じ。

using System;
using System.IO;
using System.Threading;

namespace MutexExampleAnotherSession
{
    class Program
    {
        const string MUTEX_NAME = "MyMutex";

        static void Main(string[] args)
        {
            using (var logStream = new StreamWriter(path: $"C:/Temp/mymutex.log", append: false))
            {
                logStream.AutoFlush = true;
                Console.SetError(logStream);

                var handle = false;
                using (var mutex = new Mutex(initiallyOwned: false, name: MUTEX_NAME))
                {
                    try
                    {
                        // 所有権を取る
                        handle = mutex.WaitOne(timeout: TimeSpan.Zero);
                        if (!handle)
                        {
                            Console.Error.WriteLine("[別セッション] 所有権が取れなかった");
                            return;
                        }

                        Console.Error.WriteLine("[別セッション] 所有権獲得: 1分待機...");
                        Thread.Sleep(TimeSpan.FromMinutes(1));
                    }
                    catch (Exception mutexEx)
                    {
                        Console.Error.WriteLine(mutexEx);
                    }
                    finally
                    {
                        if (handle)
                        {
                            // 所有権を開放
                            mutex.ReleaseMutex();
                            Console.Error.WriteLine("[別セッション] 所有権開放");
                        }

                        mutex.Close();
                    }
                }
            }
        }
    }
}

タスクマネージャーだと、当然コンソール出せないのでログを出力するようにしておく。

(2) の方をタスクスケジューラに登録、その際に

ユーザがログオンしているかどうかにかかわらず実行する

をチェックしておきます。

んで、先に (1) を起動。以下のようにでます。

$ dotnet run
所有権獲得: 1分待機...

んで、すぐにタスクスケジューラから (2) を起動。 (1) 側でMutexの所有権を持っているので取れないはずが

$ tail -f mymutex.log
[別セッション] 所有権獲得: 1分待機...

取れてる・・・・・ってなります。

名前に Global\ を付ける

うまく取れなかったのを確認したので、上のソースの

const string MUTEX_NAME = "MyMutex";

const string MUTEX_NAME = @"Global\MyMutex";

に変更。

で、もう一回試してみます。

$ dotnet run
所有権獲得: 1分待機...

タスクスケジューラから起動

$ tail -f mymutex.log
[別セッション] 所有権が取れなかった

ちゃんと所有権が取れなかったってログが出ました。


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

Pythonメモ-106 (PyPIが2段階認証に対応)(two-factor authentication, 2FA)

概要

やっとPyPIが2段階認証に対応したとのこと。忘れないうちにメモメモ。

セキュリティの観点からも、アカウント持っている人は早めに設定しておいた方がいいですね。

pythoninsider.blogspot.com

Account Settings の下の方に 2FA の項目があります。下は有効にしたあとの状態。

f:id:gsf_zero1:20190602012854p:plain
PyPI 2FA


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com