<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Python &#8211; 開発記録</title>
	<atom:link href="https://www.kthksgy.com/category/python/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.kthksgy.com</link>
	<description>開発メモです。現在レイアウトが一部崩れている箇所があります。</description>
	<lastBuildDate>Wed, 11 Nov 2020 10:57:58 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2.2</generator>
	<item>
		<title>PythonでGoogle Driveを触る</title>
		<link>https://www.kthksgy.com/python/access-google-drive/</link>
					<comments>https://www.kthksgy.com/python/access-google-drive/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Wed, 11 Nov 2020 04:28:13 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=32</guid>

					<description><![CDATA[PythonとGoogleのAPIを使って、Google Driveにアクセスします。 環境 Windows10 Python 3.8.6 環境構築 Python標準のvenvを使って仮想環境を構築し、そこに必要なパッケ&#8230;]]></description>
										<content:encoded><![CDATA[<p>PythonとGoogleのAPIを使って、Google Driveにアクセスします。<br />
<span id="more-32"></span></p>
<h2>環境</h2>
<ul>
<li>Windows10</li>
<li>Python <code>3.8.6</code></li>
</ul>
<h2>環境構築</h2>
<p>Python標準のvenvを使って仮想環境を構築し、そこに必要なパッケージをインストールしました。</p>
<pre><code class="language-bash line-numbers">$ cd "~/Google Drive Viewer"
$ python -m venv .venv
$ source .venv/Scripts/activate
(.venv) $ python -m pip install --upgrade pip
(.venv) $ pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
</code></pre>
<h2>GCPプロジェクトの作成と認証情報のダウンロード</h2>
<p><a class="wp-editor-md-post-content-link" href="https://console.developers.google.com/">Google Developer Console</a>でプロジェクトを作成して、認証情報を<code>client_id.json</code>としてダウンロードしてください。</p>
<h2>Pythonスクリプト</h2>
<p>公式チュートリアルを参考にPythonスクリプトを書きます。</p>
<pre><code class="language-python line-numbers">from __future__ import print_function
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# アプリに必要な権限を設定
SCOPES = [
    # Google Driveの全ファイル読み込み専用
    'https://www.googleapis.com/auth/drive.readonly'
]

credentials = None
# 認証トークンがあれば読み込む
if os.path.exists('token.pickle'):
    with open('token.pickle', 'rb') as token:
        credentials = pickle.load(token)

# 認証情報が有効でなければGoogleのログイン画面を開いて新たに生成
if not credentials or not credentials.valid:
    if credentials and credentials.expired and credentials.refresh_token:
        credentials.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
            'client_id.json', SCOPES)
        credentials = flow.run_local_server(port=0)
    # Save the credentials for the next run
    with open('token.pickle', 'wb') as token:
        pickle.dump(credentials, token)

# Google Drive API v3のオブジェクトを生成
service = build('drive', 'v3', credentials=credentials)

# APIを使う(ファイルの一覧を10件取得)
results = service.files().list(
    pageSize=10, fields="nextPageToken, files(id, name)").execute()
items = results.get('files', [])

# 結果を表示
if not items:
    print('No files found.')
else:
    print('Files:')
    for item in items:
        print(u'{0} ({1})'.format(item['name'], item['id']))
</code></pre>
<h2>実行</h2>
<p>実行時に有効なユーザーの認証情報が<code>token.pickle</code>に保存されていない場合は、Googleのアプリ認証ページがブラウザで開かれます。</p>
<p>もしくは、以下のような形でアプリ認証ページのURLがターミナルに表示されます。</p>
<pre><code class="language-bash line-numbers"># 実行
(.venv) $ python main.py
# 表示されたURLで自分のGoogleアカウントにログインする
Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&amp;client_id=...
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3dNUHvUrJU84wX5c14ZNuocezVtGrhk4Y5FAQZNjlOFftVLJBgb1XJR7vp9NHIsR50ZV8Dq-Ws2_7kXiWxSnSw6IPqnk1nTrXgInrjEDgB7yHiFeWJ1uHvrOqGtWQs2Gqe-e7AgPnh6xzF1YXCZ2MNA=s0" alt="" /></p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3fsrcPy19rFu5BaeBikyBozchBqVvDdXGwSmUIjGA-1Qh5NFxaLrRG-cJuwPnUku8kyjbC4TV93P0PGveEPRgkE0g0xVjh-564I2CrsvpJg72zo8zK5RGVa9KU4WeVvBTKwXJ0FBphU_lIjfj57iSIl=s0" alt="" /></p>
<p>Googleによって確認されていないアプリの場合は警告が出ます。<br />
<em>詳細</em>からアプリのページに移動します。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3dRv9K2E8EV0_EjD32CZGWoYVneEhM8_j7nr4kuKQfrpwYe-We5xD-KHJeeeSCdpPN-SDRIOtpQs0s4tiqvOJR2WT_YgMyUMUO1V6d9LntgBcRwkNJi1dZkbIWXBZxmIE1QuPE2CykPCd_DZNHsHDdB=s0" alt="" /></p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3cNNLSDmsjHGhFGeHKKvvT_Y-2951VW_Ap5vKZtikfErW60mLn5LSxKr59CEwvWBFb_UYqm7GNIT-vTXj0VB2o0223nBP46YiRW5lf0WqfjnZGmN8UfFWeyPE_QmkeWYc0oZxY8y5xATPzmtp7LhRn6=s0" alt="" /></p>
<p>必要な権限(プログラムでは<code>SCOPES</code>で指定)が表示されるので、許可を選びます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3cjdfsm7Qo1kX2npsHkQbwlRAn7py7Bp4vyI9MHFCE6bd73_alAIPv-GBn1APcZ9NbE9lKi-eE68cVihhckVe0sQGVRgVQBa5JwZmd1e1ssfqirIuGyWEsD5FNHOjD2cm7RoHzhU0Zy6VvEsaG2AR2c=s0" alt="" /></p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3dveDqjkl3tR8knhMyjjeL7go12r_-ufX0VPodFrKY2nrirHbvO4OVpJ4D8e0uzGoRkRoYG5u7OxRDoivtVxdNqLTCe626UiEnVPIIusqkaWody2XGiSnyvqh5hALtF1YnVc0JWIfELW_xgbYFv8BfJ=s0" alt="" /></p>
<p>画面が自動的に閉じて、ユーザーの認証情報が<code>token.pickle</code>に保存されます。</p>
<p>例のプログラムではGoogle Driveのファイルの一覧を10件取得して表示するので、<br />
この後ターミナルにファイルの一覧が表示されます。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://developers.google.com/drive/api/v3/quickstart/python">Python Quickstart | Google Drive API | Google Developers</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/python/access-google-drive/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>〈Windows10〉venvでPythonの仮想環境を作る</title>
		<link>https://www.kthksgy.com/python/windows-venv/</link>
					<comments>https://www.kthksgy.com/python/windows-venv/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Mon, 09 Nov 2020 12:54:24 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=29</guid>

					<description><![CDATA[Pythonに標準搭載されているvenvを使って、Pythonの仮想環境を作ります。 この手順で作成可能な環境のバージョンは、インストールされているPythonのバージョンのみです。 プロジェクトごとに仮想環境を作成する&#8230;]]></description>
										<content:encoded><![CDATA[<p>Pythonに標準搭載されている<em>venv</em>を使って、Pythonの仮想環境を作ります。<br />
<span id="more-29"></span></p>
<p>この手順で作成可能な環境のバージョンは、インストールされているPythonのバージョンのみです。</p>
<p>プロジェクトごとに仮想環境を作成すると、それぞれのプロジェクトが干渉するなどのトラブルを避ける事ができます。</p>
<p>Windows標準の<em>PowerShell</em>とGit for Windowsをインストールすると利用可能になる<em>Bash</em>で手順を説明します。</p>
<h2>PowerShellでの手順</h2>
<p>例として、プロジェクトのディレクトリ<code>python_project/</code>をデスクトップに作ります。</p>
<pre><code class="language-powershell line-numbers"># プロジェクトのディレクトリを作成
PS &gt; mkdir $HOME\Desktop\python_project\
</code></pre>
<p>作成したプロジェクトのディレクトリ内に、<em>venv</em>で仮想環境<code>.venv</code>を作ります。</p>
<pre><code class="language-powershell line-numbers"># プロジェクトのディレクトリに移動
PS &gt; cd $HOME\Desktop\python_project\
# 仮想環境を作成
PS &gt; python -m venv .venv
</code></pre>
<p>仮想環境を有効化します。<code>gcm</code>コマンドで参照されるPythonの位置を調べられるので、有効にする前と後でPythonの参照先が変わる事を確認します。</p>
<pre><code class="language-powershell line-numbers"># 参照先の確認
PS &gt; gcm python

# 有効化する
PS &gt; .\.venv\Scripts\activate
# 参照先の確認
(.venv) PS &gt; gcm python

# 無効化する
(.venv) PS &gt; deactivate
# 参照先の確認
PS &gt; gcm python
</code></pre>
<p>この操作はPowerShellを新しく起動する度に必要になります。<br />
Python実行前に<code>(.venv)</code>がコマンド入力行の先頭にあるかを確認しましょう。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3efqogW_kV0OSuZB5kKq0RIqYDOmLXYWTPBAeRC8ikB7iBgUGkKVkygePPgLVCyxkaQJ2tO18vhRpfPxs_l8FbHVRzw_AA_-I1xx7G7caEWhDtBZ1GOhOUV5NYgCQD7cdsCrETQXfJkUdarAOoSqYY1=s0" alt="" /></p>
<h2>Bashでの手順</h2>
<p>Bashでは基本的な流れは同じですが、有効化には<code>source</code>コマンドを使います。</p>
<pre><code class="language-bash line-numbers"># プロジェクトのディレクトリに移動
<span class="katex math inline">cd</span>HOME/Desktop/python_project/

# 参照先の確認
<span class="katex math inline">which python

# 有効化</span> source .venv/Scripts/activate
# 参照先の確認
<span class="katex math inline">which python

# 無効化
(.venv)</span> deactivate
# 参照先の確認
$ which python
</code></pre>
<p>こちらもPowerShell同様Bash起動時に毎回必要になります。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pw/ACtC-3cXDaFkdND4yAN6hsSogX_cnPmPjqLNLm5FQwpxZgyVUka1HO802peaPGDTf5QcA0IQHkfqcBGWV0JAvyep52WcgxP7tXVCVPRh-R94LucTp3EqimXZbIzEVGOK2X6AzzUUx95x55ypeMhvntMAQjy7=s0" alt="" /></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/python/windows-venv/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>numpy.ndarray.astype(numpy.uint8)の落とし穴【Python/Numpy】</title>
		<link>https://www.kthksgy.com/python/numpy-clip-astype/</link>
					<comments>https://www.kthksgy.com/python/numpy-clip-astype/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Sun, 03 May 2020 21:43:45 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[NumPy]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=10</guid>

					<description><![CDATA[float64やint32のndarrayをuint8に型変換した時に自動的に近い境界値に丸めてくれると思っていたのですがそうではないようです。 OpenCVで画像を読み込んでNumPyで弄った後に符号無し8ビット整数に&#8230;]]></description>
										<content:encoded><![CDATA[<p><code>float64</code>や<code>int32</code>の<code>ndarray</code>を<code>uint8</code>に型変換した時に自動的に近い境界値に丸めてくれると思っていたのですがそうではないようです。<br />
<span id="more-10"></span></p>
<p>OpenCVで画像を読み込んでNumPyで弄った後に符号無し8ビット整数に変換して書き出そうとした時に躓いたのでメモ。</p>
<h2>概要</h2>
<p>元の型より精度が低い整数型への型変換<code>numpy.ndarray.astype(dtype)</code>は変換前に<code>numpy.ndarray.clip(min, max)</code>をすべきです。</p>
<p>例えば、<code>a.astype(np.uint8)</code>する時は以下のように<code>a.clip(0, 255)</code>で値を先に制限します。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; import numpy as np
&gt;&gt;&gt; a = np.array([-1, 0, 255, 256])
# こうではなくて
&gt;&gt;&gt; a.astype(np.uint8) 
array([255,   0, 255,   0], dtype=uint8)
# こうする
&gt;&gt;&gt; a.clip(0, 255).astype(np.uint8) 
array([  0,   0, 255, 255], dtype=uint8)
</code></pre>
<p>以下は詳細です。</p>
<h2>環境</h2>
<p>対話環境で確認しました。</p>
<pre><code class="language-python line-numbers">$ python
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt; import numpy as np
&gt;&gt;&gt; np.__version__
'1.18.3'
</code></pre>
<h2>numpy.ndarray.astype(numpy.uint8)とは</h2>
<p>読んで字の如く、型変換のメソッドです。</p>
<p><code>numpy.ndarray</code>に対して型名を引数にして呼ぶと、配列が指定した型に変換されて返ってきます。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; a = np.zeros((32, 32, 3), dtype=np.float16)
&gt;&gt;&gt; a.dtype
dtype('float16')
&gt;&gt;&gt; a.astype(np.uint8).dtype
dtype('uint8')
</code></pre>
<h2>落とし穴</h2>
<p><code>uint8</code>では<code>0~255</code>の数値を表せますが、その範囲外の数値を変換した時に想像とは違う数値変換がされます。</p>
<h3>範囲内の場合</h3>
<p><code>0~255</code>の範囲内の場合は想像した通りの変換、つまり何も変換されません。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; a = np.array([0, 1, 10, 64, 150, 255])
&gt;&gt;&gt; a.astype(np.uint8)
array([  0,   1,  10,  64, 150, 255], dtype=uint8)
</code></pre>
<h3>範囲外の場合</h3>
<p>一方、範囲外では少なくとも想像とは違う結果になりました。変換は以下の通りです。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; b = np.array([256, 511, 512, 32767, 32768, -1, -128, -127, -32768, -32767])
&gt;&gt;&gt; b.astype(np.uint8)
array([  0, 255,   0, 255,   0, 255, 128, 129,   0,   1], dtype=uint8)
</code></pre>
<p>浮動小数点数からの変換は以下の通り。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; c = np.array([256.1, 511.3, 512.2, 32767.8, 32768.1, -1.2, -128.0, -127.9, -32768.5, -32767.4])
&gt;&gt;&gt; c.astype(np.uint8)
array([  0, 255,   0, 255,   0, 255, 128, 129,   0,   1], dtype=uint8)
</code></pre>
<p>想像では、<code>256 -&gt; 255</code>や<code>-1 -&gt; 0</code>のように<strong>近い方の整数に丸めて欲しかった</strong>のですが、どうやら実際は<strong>少数部分を切り捨てた後に十分なビット数で符号付き整数に変換した後に下位8ビットを取る</strong>変換をしているようです。</p>
<p>なので、<code>256 -&gt; 0000 0001 0000 0000 -&gt; 0000 0000 -&gt; 0</code>、<code>-1 -&gt; 1111 1111 -&gt; 255</code>、<code>-128 -&gt; 1000 0000 -&gt; 128</code>となっています。</p>
<h2>解決法</h2>
<p>意図しない変換がされる前に先に近い方の整数に丸める変換してしまいましょう。</p>
<p>近い方の整数に丸めるには、<code>numpy.clip(a, a_min, a_max)</code>か<code>numpy.ndarray.clip(min, max)</code>を使います。前者と後者の違いは呼び出し方だけです。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; b = np.array([256, 511, 512, 32767, 32768, -1, -128, -127, -32768, -32767])
# numpyから呼び出して引数に指定する
&gt;&gt;&gt; np.clip(b, 0, 255).astype(np.uint8)
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
# numpy.ndarrayから呼び出す
&gt;&gt;&gt; b.clip(0, 255).astype(np.uint8)
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
</code></pre>
<p>浮動小数点数に対しても想像通りの変換がされるようになりました。</p>
<pre><code class="language-python line-numbers">&gt;&gt;&gt; c = np.array([256.1, 511.3, 512.2, 32767.8, 32768.1, -1.2, -128.0, -127.9, -32768.5, -32767.4])
&gt;&gt;&gt; np.clip(c, 0, 255).astype(np.uint8) 
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
&gt;&gt;&gt; c.clip(0, 255).astype(np.uint8) 
array([255, 255, 255, 255, 255,   0,   0,   0,   0,   0], dtype=uint8)
</code></pre>
<p>警告も無しに気軽に変換してくれるので、諸々の事は勝手にやってくれると思っていたのですが、実は結構気に掛けねばならない事もあるようです。</p>
<p>ともかくこれで、OpenCVで出力した画像が白飛びしたりしなくなりました。めでたしめでたし。</p>
<h2>参考文献</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://numpy.org/doc/stable/reference/generated/numpy.ndarray.clip.html">numpy.ndarray.clip</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://numpy.org/doc/stable/reference/generated/numpy.clip.html">numpy.clip</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://numpy.org/doc/stable/reference/generated/numpy.ndarray.astype.html">numpy.ndarray.astype</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/python/numpy-clip-astype/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>循環インポートとcannot import nameの謎</title>
		<link>https://www.kthksgy.com/python/python-circular-dependency/</link>
					<comments>https://www.kthksgy.com/python/python-circular-dependency/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Sat, 01 Feb 2020 09:48:06 +0000</pubDate>
				<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=19</guid>

					<description><![CDATA[Flaskのバックエンドを書いている途中にimportのエラーで不思議に思った点があるので検証を行いました。 問題のコード 以下のようなディレクトリ構造で、それぞれのファイルには次のような内容が書かれています。 意図した&#8230;]]></description>
										<content:encoded><![CDATA[<p>Flaskのバックエンドを書いている途中に<code>import</code>のエラーで不思議に思った点があるので検証を行いました。</p>
<h2>問題のコード</h2>
<p>以下のようなディレクトリ構造で、それぞれのファイルには次のような内容が書かれています。</p>
<p>意図した動作としては、<code>main.py</code>を実行すると<code>main.py</code>から<code>pkg1</code>パッケージ内の<code>mod1</code>モジュールの関数<code>func2</code>をロードして<code>main.py</code>内で実行します。</p>
<p><code>mod1</code>モジュールの関数<code>func2</code>は<code>main.py</code>にある関数<code>func1</code>の返り値を<code>print</code>する関数です。</p>
<pre><code class="language-clean line-numbers">mytest
|-- pkg1/ (パッケージ1)
   |-- __init__.py
   |-- mod1.py (モジュール1)
|-- main.py (実行ファイル)
</code></pre>
<pre><code class="language-python line-numbers"># main.py
from pkg1.mod1 import func2

var1 = 'foo'


def func1():
    return var1

if __name__ == '__main__':
    func2()
</code></pre>
<pre><code class="language-python line-numbers"># pkg1/__init__.py
</code></pre>
<pre><code class="language-python line-numbers"># pkg1/mod1.py
from main import func1

def func2():
    print(func1())
</code></pre>
<h2>インポート時にエラーが起きる</h2>
<p><code>main.py</code>と<code>pkg1/mod1.py</code>で相互にインポートしているので、実行時に次のようなエラーが発生します。</p>
<pre><code class="language-bash line-numbers"># (bash)
$ python main.py
Traceback (most recent call last):
  File "main.py", line 2, in &lt;module&gt;
    from pkg1.mod1 import func2
  File "...\mytest\pkg1\mod1.py", line 2, in &lt;module&gt;
    from main import func1
  File "...\mytest\main.py", line 2, in &lt;module&gt;
    from pkg1.mod1 import func2
ImportError: cannot import name 'func2' from 'pkg1.mod1' (...\mytest\pkg1\mod1.py)
</code></pre>
<h2>解決策</h2>
<p><code>main.py</code>のみを編集する解決策としては次の2通りが有ります。</p>
<h3>解決策1 : ローカルスコープインポート</h3>
<p>1つ目の解決策は、循環インポートを応急的に解消する方法です。</p>
<pre><code class="language-python line-numbers"># main.py
var1 = 'foo'


def func1():
    return var1

if __name__ == '__main__':
    from pkg1.mod1 import func2
    func2()
</code></pre>
<p><code>main.py</code>でのモジュールの参照を、<code>main.py</code>がトップレベルとして実行された時に限定します。</p>
<p>これで、<code>mod1.py</code>で<code>main.py</code>を参照した時には<code>mod1.py</code>が参照されないため、循環インポートが解消されて正常に実行出来るようになります。</p>
<h3>解決策2 : ワイルドカードインポート</h3>
<p>2つ目の解決策は、<code>func2</code>を名指しでインポートしていた所をワイルドカードインポートに変更します。</p>
<pre><code class="language-python line-numbers"># main.py
from pkg1.mod1 import *

var1 = 'foo'


def func1():
    return var1

if __name__ == '__main__':
    func2()
</code></pre>
<p>こうすると、プログラムが正常に実行出来るようになります。</p>
<h2>ちょっと待って</h2>
<p>1つ目の解決策は循環インポートが解消されたので実行出来るようになるのは理解出来ます。</p>
<p>しかし、2つ目の解決策は何も解決しているように見えません。</p>
<h2>検証</h2>
<p>どういう経路で実行されているのか知るために、<code>main.py</code>と<code>pkg1/mod1.py</code>のファイルの先頭とインポートの前後に<code>print()</code>を置きました。</p>
<pre><code class="language-python line-numbers"># ファイルの先頭
print('Loading ファイル名 =', __name__)
</code></pre>
<pre><code class="language-python line-numbers"># インポートの前後
print('Before import, pkg1/mod1.py =', __name__)
# from ... import ...
print('After import, pkg1/mod1.py =', __name__)
</code></pre>
<p>また、<code>var1</code>が定義された時間を調べるために<code>time</code>モジュールを使います。</p>
<pre><code class="language-python line-numbers"># var1 = 'foo'
var1 = time.time()
print(__name__, var1)
</code></pre>
<h3>解決策1の経路</h3>
<p>解決策1の場合の経路です。</p>
<pre><code class="language-bash line-numbers"># (bash)
$ python main.py
Loading main.py = __main__              # 実行コマンドによるロード
__main__ 1580243970.7842584             # __main__でvar1が定義される
Before import, main.py = __main__       # main =&gt; pkg1/mod1 開始
Loading pkg1/mod1.py = pkg1.mod1        # mainからのimportによるロード
Before import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 =&gt; main 開始
Loading main.py = main                  # pkg1/mod1からののimportによるロード
main 1580243970.7892146                 # mainでvar1が定義される
After import, pkg1/mod1.py = pkg1.mod1  # pkg1/mod1 =&gt; main 終了
After import, main.py = __main__        # main =&gt; pkg1/mod1 終了
1580243970.7892146                      # mainでのvar1の結果が呼ばれている
</code></pre>
<p>ローカルスコープインポートのため、<code>main.py</code>から<code>pkg1/mod1.py</code>へのインポートは1回しか発生していません。</p>
<p>また、実行コマンドで一度<code>main.py</code>は実行されていますが、<code>pkg1/mod1.py</code>からのインポート時にもう一度実行してその結果をインポートしているのが、最後の結果から分かります。</p>
<h3>解決策2の経路</h3>
<p>解決策2の場合の経路です。</p>
<pre><code class="language-bash line-numbers"># (bash)
$ python main.py
Loading main.py = __main__              # 実行コマンドによるロード
Before import, main.py = __main__       # main =&gt; pkg1/mod1 開始
Loading pkg1/mod1.py = pkg1.mod1        # mainからのimportによるロード
Before import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 =&gt; main 開始
Loading main.py = main                  # pkg1/mod1からのimportによるロード
Before import, main.py = main           # main =&gt; pkg1/mod1 開始
# pkg1/mod1.pyが読み込まれたらLoading ...が表示されるはず
After import, main.py = main            # main =&gt; pkg1/mod1 終了
main 1580244277.4291725                 # mainでvar1が定義される
After import, pkg1/mod1.py = pkg1.mod1  # pkg1/mod1 =&gt; main 終了
After import, main.py = __main__        # main =&gt; pkg1/mod1 終了
__main__ 1580244277.4301698             # __main__でvar1が定義される
1580244277.4291725                      # mainでのvar1の結果が呼ばれている
</code></pre>
<p>ローカルスコープインポートと違い、コードが実行される度にインポート自体は行われているようです。</p>
<p>但し、解決策2ではインポートガードが機能しているので、<code>pkg1/mod1.py</code>の2回目の実行は阻止されています。</p>
<h3>エラーの場合の経路</h3>
<p>最初に示したコードの場合の経路です。</p>
<pre><code class="language-bash line-numbers"># (bash)
$ python main.py
Loading main.py = __main__              # 実行コマンドによるロード
Before import, main.py = __main__       # main =&gt; pkg1/mod1 開始
Loading pkg1/mod1.py = pkg1.mod1        # mainからのimportによるロード
Before import, pkg1/mod1.py = pkg1.mod1 # pkg1/mod1 =&gt; main 開始
Loading main.py = main                  # pkg1/mod1からのimportによるロード
Before import, main.py = main           # main =&gt; pkg1/mod1 開始
Traceback (most recent call last):      # インポートエラー発生
  ...
ImportError: cannot import name 'func2' from 'pkg1.mod1' (...\mytest\pkg1\mod1.py)
</code></pre>
<p><code>pkg1/mod1.py</code>の2回目の実行の前までは解決策2と同じ経路のように見えます。</p>
<h2>考察</h2>
<p>解決策1と解決策2では、インポートの位置による実行経路の違いが見られ、どちらも<code>pkg1/mod1.py</code>のインポート時に実行された結果を利用するようでした。</p>
<p>しかし、解決策2の場合ではインポートガードによって<code>pkg1/mod1.py</code>の2回目の実行が回避されているのに対し、エラーの場合ではそのタイミングでエラーが発生しています。</p>
<p>つまり、名指しでインポートを行うと、インポート名を明示的に指定している関係でインポートガードが行えないのではないかと考えられます。</p>
<h2>まとめ</h2>
<p>循環インポートはコードが無駄に実行されてしまったり今回のようなちょっとしたエラーにもなるのでそもそも循環インポートになるような構造は避けるべきですが、仮に循環インポートになってしまったとしてもPythonの賢いインポートガードによって上手にループが回避されているのが分かりました。</p>
<p>ワイルドカードインポートはPEP 8で非推奨になっているし名前空間が分かりづらくなるので、どうしても循環構造が生まれてしまう場合は解決策1の方が望ましいかも知れません。勿論、ファイルの途中でインポートするのもPEP 8では推奨されていないので、<code>from pkg1 import mod1</code>でモジュールレベルインポートした後、関数オブジェクトのように<code>func2 = mod1.func2</code>で別名を付けるのが最適解だと思います。</p>
<blockquote><p>
  ワイルドカードを使った import (from <module> import *) は避けるべきです。(<a class="wp-editor-md-post-content-link" href="https://pep8-ja.readthedocs.io/ja/latest/#import">pep8-ja/import</a>)
</p></blockquote>
<p>ちなみに、このエラーはFlask-SQLAlchemyでビューの作成を<code>blueprint</code>でしようとしていてこのような構造になったので発生しました。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/python/python-circular-dependency/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
