Pythonでディレクトリ配下の.pyファイルを検索し、特定の文字列を探す方法

# **user**:

ディレクトリを指定した時に、その配下にあるサブディレクトリを全て走査して（隠しファイルや隠しディレクトリを除く）、 .py ファイルを全て調べ上げ、指定した文字列がどのファイルの何行目にあるかを全てリストアップするPythonプログラムを作ってください。なお、そのプログラムはコマンドラインで動かせるようにしてください。

# **assistant**:

以下のPythonスクリプトを作成しました。このスクリプトは、指定したディレクトリ配下の `.py` ファイルを再帰的に検索し、指定した文字列が含まれるファイルと行番号を出力します。隠しファイルや隠しディレクトリは無視します。

```python
import os
import argparse

def search_string_in_py_files(directory, search_string):
    """ 指定したディレクトリ配下の .py ファイルを検索し、
    指定した文字列を含む行とそのファイルのパスを出力する """
    for root, dirs, files in os.walk(directory):
        # 隠しディレクトリを除外
        dirs[:] = [d for d in dirs if not d.startswith('.')]
        
        for file in files:
            if file.endswith(".py") and not file.startswith('.'):
                file_path = os.path.join(root, file)
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        for line_num, line in enumerate(f, start=1):
                            if search_string in line:
                                print(f"{file_path}:{line_num}: {line.strip()}")
                except (UnicodeDecodeError, PermissionError) as e:
                    print(f"[Error] {file_path}: {e}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Search for a string in all .py files within a directory.")
    parser.add_argument("directory", help="Target directory to search in")
    parser.add_argument("search_string", help="String to search for in .py files")
    
    args = parser.parse_args()
    search_string_in_py_files(args.directory, args.search_string)
```

このスクリプトの使い方:
```sh
python search_py_files.py /path/to/directory "検索文字列"
```
例えば、`/home/user/project` ディレクトリ内の `.py` ファイルから `"import numpy"` を検索する場合:
```sh
python search_py_files.py /home/user/project "import numpy"
```

出力例:
```
/home/user/project/script.py:10: import numpy as np
/home/user/project/utils/math.py:5: from numpy import array
```

隠しファイルや隠しディレクトリはスキップし、エンコーディングエラーや権限エラーも処理しています。

# **user**:

ありがとうございます。このコードは大文字小文字の区別をしませんよね？するかしないか選択できるように改変できますか？

# **assistant**:

```python
import os
import argparse

def search_string_in_py_files(directory, search_string, case_sensitive):
    """ 指定したディレクトリ配下の .py ファイルを検索し、
    指定した文字列を含む行とそのファイルのパスを出力する """
    if not case_sensitive:
        search_string = search_string.lower()
    
    for root, dirs, files in os.walk(directory):
        # 隠しディレクトリを除外
        dirs[:] = [d for d in dirs if not d.startswith('.')]
        
        for file in files:
            if file.endswith(".py") and not file.startswith('.'):
                file_path = os.path.join(root, file)
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        for line_num, line in enumerate(f, start=1):
                            line_to_check = line if case_sensitive else line.lower()
                            if search_string in line_to_check:
                                print(f"{file_path}:{line_num}: {line.strip()}")
                except (UnicodeDecodeError, PermissionError) as e:
                    print(f"[Error] {file_path}: {e}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Search for a string in all .py files within a directory.")
    parser.add_argument("directory", help="Target directory to search in")
    parser.add_argument("search_string", help="String to search for in .py files")
    parser.add_argument("--case-sensitive", action="store_true", help="Enable case-sensitive search")
    
    args = parser.parse_args()
    search_string_in_py_files(args.directory, args.search_string, args.case_sensitive)
```

大文字小文字の区別を選択できるように修正しました。`--case-sensitive` オプションを追加し、このオプションを指定すると大文字小文字を区別する検索が可能になります。

使用例:
```sh
python search_py_files.py /path/to/directory "SearchString" --case-sensitive
```
この場合、「SearchString」と完全一致するもののみがヒットします。

オプションを指定しなければ、大文字小文字を無視した検索が行われます。

# **user**:

ありがとうございます。自作のPythonライブラリが、指定されたパス配下にある時に、from ... import ... を使ってインポートするにはどうすればいいですか？

# **assistant**:

自作のPythonライブラリ（モジュールやパッケージ）が指定されたパス配下にある場合、`sys.path` にそのディレクトリを追加すれば `from ... import ...` でインポートできます。

### 方法 1: `sys.path` に追加する
```python
import sys
import os

# 自作ライブラリがあるディレクトリを指定
custom_lib_path = "/path/to/custom_library"

# sys.path に追加
if custom_lib_path not in sys.path:
    sys.path.append(custom_lib_path)

# 自作ライブラリのインポート
from my_module import my_function  # my_module.py の my_function をインポート
```
この方法では、`custom_lib_path` に `my_module.py` や `my_package/` がある場合、それらを通常のPythonモジュールと同様にインポートできます。

---

### 方法 2: `PYTHONPATH` を設定する
環境変数 `PYTHONPATH` にパスを追加すると、`sys.path` に自動的に含まれるため、`sys.path.append()` を書かずに済みます。

#### 一時的に設定する（ターミナルで実行）
```sh
export PYTHONPATH="/path/to/custom_library:$PYTHONPATH"
python script.py
```
#### 永続的に設定する（`~/.bashrc` や `~/.zshrc` に追加）
```sh
export PYTHONPATH="/path/to/custom_library:$PYTHONPATH"
```

---

### 方法 3: `site.addsitedir()` を使う
`sys.path.append()` の代わりに、`site.addsitedir()` を使うと、ディレクトリ内の `*.pth` ファイル（パスリスト）も反映されます。
```python
import site
site.addsitedir("/path/to/custom_library")

from my_package import my_module
```

---

どの方法も `from my_module import my_function` や `from my_package import my_module` などの標準的なインポートが可能になります。

# **user**:

ありがとうございます！大変役に立ちました！

# **assistant**:

どういたしまして！お役に立てて嬉しいです😊  
また何かあれば気軽に聞いてくださいね！🚀