Microsoft MVP성태의 닷넷 이야기
닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동 [링크 복사], [링크+제목 복사],
조회: 9758
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 4개 있습니다.)
스크립트: 11. 파이썬(Python) 윈도우 개발 환경
; https://www.sysnet.pe.kr/2/0/1343

닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동
; https://www.sysnet.pe.kr/2/0/13603

닷넷: 2246. C# - Python.NET을 이용한 파이썬 소스코드 연동
; https://www.sysnet.pe.kr/2/0/13605

닷넷: 2255. C# 배열을 Numpy ndarray 배열과 상호 변환
; https://www.sysnet.pe.kr/2/0/13615




C# - IronPython을 이용한 파이썬 소스코드 연동

"IronPython"을 처음 소개했던 때와는 다르게 이제 사용법도 많이 편리해졌습니다.

단지, 프로젝트에 IronPython 패키지만 추가하고,

Install-Package IronPython

예제로 간단한 "test.py"가 있다면,

def greetings(name):
    return 'Hello ' + name + '!'

이후, 다음과 같이 코드를 만들면 됩니다.

namespace ConsoleApp1;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"C# Process ID: {Environment.ProcessId}");
        string codeText = File.ReadAllText("test.py");

        var eng = IronPython.Hosting.Python.CreateEngine();
        var scope = eng.CreateScope();
        eng.Execute(codeText, scope);
        dynamic greetings = scope.GetVariable("greetings");
        System.Console.WriteLine(greetings("world"));
    }
}

실행 시 이런 출력이 나옵니다.

C# Process ID: 69284
Hello world!




기본적인 사용은 그렇고, 그렇다면 기본 패키지를 import하는 경우에는 어떻게 될까요? 테스트를 위해 test.py의 내용을 다음과 같이 바꾸고,

import os

def greetings(name):
    return str(os.getpid()) + ': Hello ' + name + '!'

실행하면 이제는 os 모듈을 찾을 수 없다는 예외가 발생합니다.

Unhandled exception. IronPython.Runtime.Exceptions.ImportException: No module named 'os'
   at Microsoft.Scripting.Runtime.LightExceptions.ThrowException(LightException lightEx)
   at Microsoft.Scripting.Runtime.LightExceptions.CheckAndThrow(Object value)
   at Microsoft.Scripting.Interpreter.FuncCallInstruction`2.Run(InterpretedFrame frame)
   at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
   at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
   at IronPython.Compiler.PythonScriptCode.RunWorker(CodeContext ctx)
   at IronPython.Compiler.PythonScriptCode.Run(Scope scope)
   at IronPython.Compiler.RuntimeScriptCode.InvokeTarget(Scope scope)
   at IronPython.Compiler.RuntimeScriptCode.Run(Scope scope)
   at Microsoft.Scripting.SourceUnit.Execute(Scope scope, ErrorSink errorSink)
   at Microsoft.Scripting.SourceUnit.Execute(Scope scope)
   at Microsoft.Scripting.Hosting.ScriptSource.Execute(ScriptScope scope)
   at Microsoft.Scripting.Hosting.ScriptEngine.Execute(String expression, ScriptScope scope)
   at ConsoleApp1.Program.Main(String[] args) 

왜냐하면 IronPython은 단순히 Python 문법의 Interpreter 해석기에 불과하기 때문에 외부 모듈에 대해서는 알지 못합니다. 즉, "os" 패키지가 있어야 하는 건데요, 다행히 IronPython은 기본적인 파이썬 패키지를 별도로 패키징해 제공하고 있습니다.

Install-Package IronPython.StdLib

IronPython.StdLib를 참조 추가하면 솔루션 탐색기의 프로젝트 노드에는 "lib"가 별도로 추가됩니다.

ironpython_interop_1.png

예상할 수 있듯이, 빌드하면 저 lib 디렉터리가 그대로 출력 디렉터리에 복사됩니다. 엄밀히 저 lib 디렉터리는 Python을 설치했을 때 생성되는 Lib 디렉터리에 해당합니다. (단지, idlelib, test, tkinter, cmd.py 등이 Python에 더 있고, 반면 clrtype.py, wpf.py 등이 IronPython.StdLib에 추가돼 있습니다.)

이렇게 기본 패키지를 확보했으니, 이제 정상적으로 다음과 같은 출력 결과를 볼 수 있습니다.

C# Process ID: 69840
69840: Hello world!

재미있는 것은, 위의 결과에도 나오지만 IronPython은 외부 python.exe 실행 모듈과 연동하는 것이 아닌, 자체 스크립트 해석 엔진으로 동작하기 때문에 In-process 실행이라는 점이 큰 장점입니다.




자, 그렇다면 외부 라이브러리는, 가령 "pip install ..."로 설치하는 패키지는 어떻게 사용할 수 있을까요?

예를 들어, beautifulsoup4를 사용하는 코드를 이렇게 작성했다면,

def get_page(url):
    import urllib.request
    import bs4

    web_page = urllib.request.urlopen()
    page_html = web_page.read(url) if web_page else ""
    return page_html[0:100]

var eng = IronPython.Hosting.Python.CreateEngine();

var scope = eng.CreateScope();
eng.Execute(codeText, scope);
dynamic get_page = scope.GetVariable("get_page");
System.Console.WriteLine(get_page("https://www.naver.com"));

당연히 "bs4"를 import 하는 코드에서 예외가 발생할 것입니다.

Unhandled exception. IronPython.Runtime.Exceptions.ImportException: No module named 'bs4'
   at Microsoft.Scripting.Runtime.LightExceptions.ThrowException(LightException lightEx)
   at Microsoft.Scripting.Runtime.LightExceptions.CheckAndThrow(Object value)
   at Microsoft.Scripting.Interpreter.FuncCallInstruction`2.Run(InterpretedFrame frame)
   at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
   at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
   at IronPython.Compiler.PythonCallTargets.OriginalCallTarget1(PythonFunction function, Object arg0)
   at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
   at ConsoleApp1.Program.Main(String[] args) 

현재(2024-04-20) IronPython의 최신 버전이 3.4.1인데요, 이것은 IronPython이 파이썬 3.4에 해당하는 구문 분석 기능을 가지고 있다는 것을 의미합니다. 따라서, 위와 같이 외부 모듈을 사용하고 싶다면 파이썬 3.4 인터프리터로 해석 가능한, 쉽게 말해 파이썬 3.4 환경에서 설치한 beautifulsoup4 패키지만을 사용할 수 있습니다.

자, 그럼 해당 모듈을 구하기 위해 우선 로컬에 파이썬 3.4를 설치한 다음,

Python 3.4.0
; https://www.python.org/downloads/release/python-340/

그 버전에 해당하는 pip도 아래의 스크립트를 실행해 설치합니다.

get-pip.py
; https://bootstrap.pypa.io/pip/3.4/get-pip.py

예를 들어 C:\Python34 디렉터리에 설치했다면 이후 pip를 이용해 beautifulsoup4 패키지를 설치합니다.

C:\Python34> python -m pip install beautifulsoup4
DEPRECATION: Python 3.4 support has been deprecated. pip 19.1 will be the last one supporting it. Please upgrade your Python as Python 3.4 won't be maintained after March 2019 (cf PEP 429).
Collecting beautifulsoup4
  Using cached https://files.pythonhosted.org/packages/69/bf/f0f194d3379d3f3347478bd267f754fc68c11cbf2fe302a6ab69447b1417/beautifulsoup4-4.10.0-py3-none-any.whl
Requirement already satisfied: soupsieve>1.2 in C:\python34\lib\site-packages (from beautifulsoup4) (2.5)
ERROR: soupsieve requires Python '>=3.8' but the running Python is 3.4.0
WARNING: You are using pip version 19.1, however version 19.1.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.

그런데, 문제군요. ^^; 위의 명령어처럼 아마도 많은 패키지들이 3.4 버전의 파이썬은 권장하지 않는다면서 아예 설치를 거부해버립니다. 따라서 3.4 환경을 지원하는 버전을 명시해야 하는데요, 파이썬 3.5가 2015년 9월 13일에 release 되었다고 하니 PyPI의 beautifulsoup4 이력으로 들어가,

beautifulsoup4 4.12.3 - Release history
; https://pypi.org/project/beautifulsoup4/#history

대략 그때 릴리스된 버전이 4.4.0으로 나오므로 그 버전을 명시하시면 됩니다.

C:\Python34> python -m pip install beautifulsoup4==4.4.0

그럼 추가 패키지가 "C:\Python34\Lib\site-packages" 디렉터리 하위에 설치될 것입니다. 예를 들어, beautifulsoup4 패키지는 "C:\Python34\Lib\site-packages\bs4" 디렉터리에 설치가 될 텐데요, 해당 디렉터리를 복사해 닷넷 프로그램의 출력 디렉터리에 복사합니다.

[IronPython.StdLib로 인해 추가된 site-packages]
C:\temp\ConsoleApp1\ConsoleApp1\bin\Debug\net8.0\lib\site-packages

위의 경로 하위에 bs4 디렉터리를 복사

그리고, 저 경로의 패키지를 찾으라고 IronPython에 알리는 코드를 추가하고,

string codeText = File.ReadAllText("test.py");

var eng = IronPython.Hosting.Python.CreateEngine();

string dirPath = Path.GetDirectoryName(typeof(Program).Assembly.Location) ?? Environment.CurrentDirectory;
string sitePackagesPath = Path.Combine(dirPath, "lib", "site-packages");

var oldPath = eng.GetSearchPaths();
oldPath.Add(sitePackagesPath);
eng.SetSearchPaths(oldPath);

var scope = eng.CreateScope();
eng.Execute(codeText, scope);
dynamic get_page = scope.GetVariable("get_page");
System.Console.WriteLine(get_page("https://www.naver.com"));

이후 다시 실행하면, 이렇게 잘 동작하는 것을 확인할 수 있습니다. ^^

C:\temp\ConsoleApp1\ConsoleApp1\bin\Debug\net8.0> ConsoleApp1.exe
b'   <!doctype html> <html lang="ko" class="fzoom"> <head> <meta charset="utf-8"> <meta name="Referrer'

일단, 개발은 잘 되었으니 이제 배포를 고민해야 합니다.

결국, 파이썬 통합으로 인해 추가되는 용량은 IronPython.StdLib와 기타 파이썬 패키지들이 될 텐데요, 위의 beautifulsoup4를 추가한 lib 전체 용량이라고 해봐야 12MB 정도밖에 안 됩니다. 가령 .NET 8 콘솔을 Self-Contained 배포 패키지로 만든 경우의 바이너리가 기본적으로 수십 MB에 달하기 때문에 거기에 12MB 더해도 별로 티도 나지 않습니다.

첨부 파일은 (파이썬 3.4 site-packages 제외) 이 글의 예제 코드를 포함>합니다.




위에서 제가, IronPython.StdLib와 Python 3.4가 기본적으로 가지고 있는 Lib 디렉터리가 크게 틀리지 않는다고 했는데요, 그런 점을 이용하면 IronPython.StdLib를 사용하지 않고 그것까지 모두 Python 3.4 디렉터리의 내용을 재사용하고 싶을 수도 있습니다.

List<string> newPath = new List<string>();
newPath.Add(sitePackagesPath);

newPath.Add(@"C:\Python34\Lib");
newPath.Add(@"C:\Python34\Lib\site-packages");

eng.SetSearchPaths(newPath);

그런데, beautifulsoup4를 대상으로 저렇게 실행해 보면 urllib.request 코드 실행 시 "Unhandled exception. System.Exception: unknown url type: https" 예외가 발생합니다.

그러니까 적어도 Lib 만큼은 (clrtype.py, wpf.py 등도 있으니) 그냥 IronPython.StdLib를 쓰는 것이 더 좋습니다.

// Install-Package IronPython.StdLib

var oldPath = eng.GetSearchPaths();

oldPath.Add(@"C:\Python34\Lib\site-packages");
eng.SetSearchPaths(oldPath);

위와 같이 Python 3.4의 site-packages를 재사용하는 것은 그래도 의미가 좀 있습니다. 가령, 복잡한 의존성을 갖는 패키지의 경우 pip로 설치할 때 site-packages에 함께 구성이 되는데 그 모든 것들을 일일이 찾아 복사해 오는 것도 만만치 않을 수 있습니다.

혹은, 그냥 "C:\Python34\Lib\site-packages" 패키지의 내용을 통째로 C# 프로젝트에 복사해 재사용해도 무방합니다. 약간 용량이 더 커지는 문제가 있을 뿐, 오히려 이거저거 생각할 필요가 없어 더 편할 수 있습니다.

그런 경우, csproj에 일일이 파일 하나씩 추가하는 것이 극단적으로 번거로운 작업이기 때문에 다음과 같이 "**" 기호를 이용한 추가를 하면 간단하게 처리할 수 있습니다.

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="IronPython" Version="3.4.1" />
        <PackageReference Include="IronPython.StdLib" Version="3.4.1" />
    </ItemGroup>

    <ItemGroup>
        <Content Include="..\pylib\**">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </Content>
    </ItemGroup>

    <ItemGroup>
        <None Update="test.py">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>

</Project>

위의 경우는 (csproj가 아닌) 상위의 솔루션 디렉터리를 기준으로 다음과 같이 site-packages를 복사해 온 것을 가정합니다.

// 파이썬 3.4의 site-packages 디렉터리 내용을 "C:\temp\ConsoleApp1\pylib\lib\site-packages"에 복사한 것으로 가정

C:\temp\ConsoleApp1\pylib\lib\site-packages

그럼 csproj 빌드 시에 CopyToOutputDirectory 명령에 따라 ./bin/Debug/net8.0 디렉터리에 함께 출력되므로 관리가 더 수월해집니다.




참고로, Python 외부 모듈을 사용하는 경우 반드시 Python 3.4 버전에 맞춰 pip install을 해야 합니다. 가령, 이 글의 예제에서 (3.4가 아닌) Python 3.7 환경으로 설치한 beautifulsoup4 패키지를 사용하게 되면 "Unhandled exception. Microsoft.Scripting.SyntaxErrorException: future feature is not defined:annotations" 오류가 발생하게 됩니다.

왜냐하면, beautifulsoup4 4.12.3 버전의 경우 Python 3.7 문법에서만 지원하는 future feature 구문을 사용하고 있기 때문에, 아직 그 문법을 지원하지 않는 IronPython에서는,

future feature is not defined #1781
; https://github.com/IronLanguages/ironpython3/issues/1781

오류가 발생하게 되는 것입니다.

무엇보다 IronPyhton의 이런 면이 참 아쉬운 부분인데요, 현재 github repo를 보면 3.6 브랜치가 열려 있지만 아쉽게도 마지막 commit 날짜가 4개월 정도 지난 상태입니다. 다시 말해 그다지 빠른 속도로 구현을 하고 있지는 않습니다. (어쩌면 조만간 archive 상태로 바뀔 지도... ^^;)

설령 3.6 지원이 되었다고 해도 근래 파이썬의 최신 버전이 3.12로 빠르게 업데이트되고 있는 상황에서 앞으로도 얼마나 뒤처지는 시간이 늘어날지 알 수 없습니다.

더욱 문제는, 3rd-party 패키지들도 빠르게 파이썬의 최신 문법을 지원하면서 지난 패키지는 deprecated 시킨다는 점입니다. 일례로, tensorflow는 파이썬 3.4 버전은 아예 지원조차 안 하는 상태입니다.

따라서, 단순히 파이썬 기본 라이브러리 내에서 동작하는 경우라면 IronPyhton이 좋은 선택일 수 있지만 조금이라도 3rd-party 패키지를 사용해야 한다면 철저하게 지원 여부를 따져봐야 합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/23/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [106]  107  108  109  110  111  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11273정성태8/22/201721624오류 유형: 415. 윈도우 업데이트 에러 Error 0x80070643
11272정성태8/21/201724745VS.NET IDE: 120. 비주얼 스튜디오 2017 버전 15.3.1 - C# 7.1 공개 [2]
11271정성태8/19/201719166VS.NET IDE: 119. Visual Studio 2017에서 .NET Core 2.0 프로젝트 환경 구성하는 방법
11270정성태8/17/201730608.NET Framework: 673. C#에서 enum을 boxing 없이 int로 변환하기 [2]
11269정성태8/17/201721414디버깅 기술: 93. windbg - 풀 덤프에서 .NET 스레드의 상태를 알아내는 방법
11268정성태8/14/201720992디버깅 기술: 92. windbg - C# Monitor Lock을 획득하고 있는 스레드 찾는 방법
11267정성태8/10/201725074.NET Framework: 672. 모노 개발 환경
11266정성태8/10/201724862.NET Framework: 671. C# 6.0 이상의 소스 코드를 Visual Studio 설치 없이 명령행에서 컴파일하는 방법
11265정성태8/10/201753124기타: 66. 도서: 시작하세요! C# 7.1 프로그래밍: 기본 문법부터 실전 예제까지 [11]
11264정성태8/9/201724002오류 유형: 414. UWP app을 signtool.exe로 서명 시 0x8007000b 오류 발생
11263정성태8/9/201719471오류 유형: 413. The C# project "..." is targeting ".NETFramework, Version=v4.0", which is not installed on this machine. [3]
11262정성태8/5/201718203오류 유형: 412. windbg - SOS does not support the current target architecture. [3]
11261정성태8/4/201720775디버깅 기술: 91. windbg - 풀 덤프 파일로부터 강력한 이름의 어셈블리 추출 후 사용하는 방법
11260정성태8/3/201718868.NET Framework: 670. C# - 실행 파일로부터 공개키를 추출하는 방법
11259정성태8/2/201718128.NET Framework: 669. 지연 서명된 어셈블리를 sn.exe -Vr 등록 없이 사용하는 방법
11258정성태8/1/201718892.NET Framework: 668. 지연 서명된 DLL과 서명된 DLL의 차이점파일 다운로드1
11257정성태7/31/201719128.NET Framework: 667. bypassTrustedAppStrongNames 옵션 설명파일 다운로드1
11256정성태7/25/201720580디버깅 기술: 90. windbg의 lm 명령으로 보이지 않는 .NET 4.0 ClassLibrary를 명시적으로 로드하는 방법 [1]
11255정성태7/18/201723161디버깅 기술: 89. Win32 Debug CRT Heap Internals의 0xBAADF00D 표시 재현 [1]파일 다운로드3
11254정성태7/17/201719475개발 환경 구성: 322. "Visual Studio Emulator for Android" 에뮬레이터를 "Android Studio"와 함께 쓰는 방법
11253정성태7/17/201719741Math: 21. "Coding the Matrix" 문제 2.5.1 풀이 [1]파일 다운로드1
11252정성태7/13/201718413오류 유형: 411. RTVS 또는 PTVS 실행 시 Could not load type 'Microsoft.VisualStudio.InteractiveWindow.Shell.IVsInteractiveWindowFactory2'
11251정성태7/13/201717062디버깅 기술: 88. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 (2)
11250정성태7/13/201720662디버깅 기술: 87. windbg 분석 - webengine4.dll의 MgdExplicitFlush에서 발생한 System.AccessViolationException의 crash 문제 [1]
11249정성태7/12/201718453오류 유형: 410. LoadLibrary("[...].dll") failed - The specified procedure could not be found.
11248정성태7/12/201724909오류 유형: 409. pip install pefile - 'cp949' codec can't decode byte 0xe2 in position 208687: illegal multibyte sequence
... [106]  107  108  109  110  111  112  113  114  115  116  117  118  119  120  ...