0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

pythonで、何としてでも内包メソッドを呼ばせない方法は?

Last updated at Posted at 2025-06-10

privateメソッドの実装は可能?

python の記述ルールでは、クラスメソッドの、メソッド名の先頭に二重のアンダースコアを記述すれば容易には呼べない仕組みですが、アンダースコアによる命名のルール('_クラス名' + 'メソッド名')を知っていれば呼び出すことができてしまいます。

そこで、需要はないと思われますが、何が何でもローカルからしか、呼ばれないようにするコードを書いてみました。

ex_private.py
#!/usr/bin/python
# coding: utf-8

"""
private 呼び出しをチェックする方法
"""

import traceback as tb
import re

# インデントレベル
##(この辺は直接関係ない)
indent = 0

# メソッドの呼び出しをトレースするデコレータ
#(この辺は直接関係ない)
def printmethod(func):
    def wrapper(*args, **kwargs):
        global indent
        _wsp = ' ' * indent * 2
        print(f'{_wsp}{func.__name__}: begin')
        indent += 1
        try:
            func(*args, **kwargs)
        except BaseException as ex:
            _wsp2 = ' ' * indent * 2
            print(f'{_wsp2}{func.__name__}: Exception: {ex}')
            raise
        finally:
            indent -= 1
            print(f'{_wsp}{func.__name__}: end')
    return wrapper

# インデントに合わせてプリント
##(この辺は直接関係ない)
def xprint(msg):
    _wsp = ' ' * indent * 2
    print(f'{_wsp}{msg}')


# プライベートCALLかどうかをチェックする
# ------------------------------------
def private_check(skip=1):
    # 引数チェック
    if not isinstance(skip, int):
        raise TypeError('Argument skip must be type int.')
    elif skip < 0:
        raise ValueError('Argument skip must be greater then -1.')
    # 内包クラス情報
    class classinfo:
        __slots__ = ['name', 'lineno', 'cname']
        def __init__(self):
            self.name = ''
            self.lineno = 0
            self.cname = ''
        # クラス名.メンバ名は呼び出し可能?
        @property
        def ok(self):
            _pass = False
            try:
                callable(eval(f'{self.cname}.{self.name}'))
                _pass = True
            except:
                pass
            return _pass
        # オブジェクトの文字列表記
        def __str__(self):
            _sout = f'{self.cname}'
            _sout += f'{"." if self.cname else ""}'
            _sout += f'{self.name}'
            return _sout
    # コールスタックを取得
    _stk = tb.extract_stack()
    # ソース記述から所属クラスを前方スキャン
    def _back_class_scan(lines, start):
        for i in range(start, -1, -1):
            _m = re.match(r'(class|def)\s+([^(:]+)[(:]', lines[i])
            if _m:
                return _m.group(2)
        return None
    # コールスタックのスキャン
    _stack = []
    for i in range(len(_stk) - (2 + skip), -1, -1):
        _map = classinfo()
        _map.name = _stk[i].name
        _map.lineno = _stk[i].lineno
        # コールスタックにはクラス名がないのでソースから探す
        _lines = []
        with open(_stk[i].filename, 'rt') as fp:
            _lines = fp.readlines()
        if _map.lineno > 0 and _map.lineno <= len(_lines):
            _class = _back_class_scan(_lines, _map.lineno - 1)
            _map.cname = _class if _class is not None else ''
            # モジュールのときはクラス名なし
            if _map.name == '<module>':
                _map.cname = ''
        # デコレータ @printmethod, xprint, private_check 自身は除く
        if _map.cname not in ['printmethod', 'xprint', 'private_check']:
            _stack.append(_map)
    # 内容をダンプしてみる
    for i in range(len(_stack)):
        print(f'callstack > "{_stack[i]}"')
    # 呼び出し元の1つ上(skip=1)が自己クラス以外のとき
    if len(_stack) > 0 and not _stack[0].ok:
        # 汝、あなたは、プライベートメソッドを直接呼んではならない!
        raise RuntimeError('You cannot call a private method directly.')
    return _stack

# テストコード

class TestClass:
 
    def __init__(self):
        pass

    @printmethod
    def _private_A(self, a, b, c):
        private_check()      # private呼び出しチェック
        xprint(f'I am _private_A.')

    @printmethod
    def _private_B(self, a, b):
        private_check()      # private呼び出しチェック
        xprint(f'I am _private_B.')

    @printmethod
    def public_A(self):
        xprint(f'I am public_A.')
        self._private_A(1,2,3)

    @printmethod
    def public_B(self):
        xprint(f'I am public_B.')
        self._private_B(1,2)

if __name__ == '__main__':

    x = TestClass()

    x.public_A()
    print('')

    x.public_B()
    print('')

    try:
        x._private_B(5,5)
    except:
        print("!!!!ERROR!!!!")
    print('')

    try:
        x._private_A(5,5,5)
    except:
        print("!!!!ERROR!!!!")
    print('')

実行結果

$ ex_private.py 
public_A: begin
  I am public_A.
  _private_A: begin
    call-stack> "TestClass.public_A"
    call-stack> "<module>"
    I am _private_A.
  _private_A: end
public_A: end

public_B: begin
  I am public_B.
  _private_B: begin
    call-stack> "TestClass.public_B"
    call-stack> "<module>"
    I am _private_B.
  _private_B: end
public_B: end

_private_B: begin
  call-stack> "<module>"
  _private_B: Exception: You cannot call a private method directly.
_private_B: end
!!!!ERROR!!!!

_private_A: begin
  call-stack> "<module>"
  _private_A: Exception: You cannot call a private method directly.
_private_A: end
!!!!ERROR!!!!

$ 

終わりに

クラス名を探すのに、ソースァイル以外から探せればオーバーヘッドも少なく良いかもしれない。更に、デコレータ化すればもっと実用的かもしれない。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?