Microsoft MVP성태의 닷넷 이야기
닷넷: 2245. C# - IronPython을 이용한 파이썬 소스코드 연동 [링크 복사], [링크+제목 복사],
조회: 4612
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
12085정성태12/20/201914106오류 유형: 586. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생 - 두 번째 이야기 [1]
12084정성태12/19/201914776디버깅 기술: 143. windbg/sos - Hashtable의 buckets 배열 내용을 모두 덤프하는 방법 (do_hashtable.py) [1]
12083정성태12/17/201917173Linux: 27. linux - lldb를 이용한 .NET Core 응용 프로그램의 메모리 덤프 분석 방법 [2]
12082정성태12/17/201915933오류 유형: 585. lsof: WARNING: can't stat() fuse.gvfsd-fuse file system
12081정성태12/16/201917851개발 환경 구성: 465. 로컬 PC에서 개발 중인 ASP.NET Core 웹 응용 프로그램을 다른 PC에서도 접근하는 방법 [5]
12080정성태12/16/201915633.NET Framework: 870. C# - 프로세스의 모든 핸들을 열람
12079정성태12/13/201916925오류 유형: 584. 원격 데스크톱(rdp) 환경에서 다중 또는 고용량 파일 복사 시 "Unspecified error" 오류 발생
12078정성태12/13/201916757Linux: 26. .NET Core 응용 프로그램을 위한 메모리 덤프 방법 [3]
12077정성태12/13/201916427Linux: 25. 자주 실행할 명령어 또는 초기 환경을 "~/.bashrc" 파일에 등록
12076정성태12/12/201914353디버깅 기술: 142. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅 - 배포 방법에 따른 차이
12075정성태12/11/201915091디버깅 기술: 141. Linux - lldb 환경에서 sos 확장 명령어를 이용한 닷넷 프로세스 디버깅
12074정성태12/10/201914743디버깅 기술: 140. windbg/Visual Studio - 값이 변경된 경우를 위한 정지점(BP) 설정(Data Breakpoint)
12073정성태12/10/201916713Linux: 24. Linux/C# - 실행 파일이 아닌 스크립트 형식의 명령어를 Process.Start로 실행하는 방법
12072정성태12/9/201913956오류 유형: 583. iisreset 수행 시 "No such interface supported" 오류
12071정성태12/9/201916567오류 유형: 582. 리눅스 디스크 공간 부족 및 safemode 부팅 방법
12070정성태12/9/201918835오류 유형: 581. resize2fs: Bad magic number in super-block while trying to open /dev/.../root
12069정성태12/2/201914862디버깅 기술: 139. windbg - x64 덤프 분석 시 메서드의 인자 또는 로컬 변수의 값을 확인하는 방법
12068정성태11/28/201923074디버깅 기술: 138. windbg와 Win32 API로 알아보는 Windows Heap 정보 분석 [3]파일 다운로드2
12067정성태11/27/201915074디버깅 기술: 137. 실제 사례를 통해 Debug Diagnostics 도구가 생성한 닷넷 웹 응용 프로그램의 성능 장애 보고서 설명 [1]파일 다운로드1
12066정성태11/27/201914831디버깅 기술: 136. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석 - OracleCommand.ExecuteReader에서 OpsSql.Prepare2 PInvoke 호출 분석
12065정성태11/25/201913631디버깅 기술: 135. windbg - C# PInvoke 호출 시 마샬링을 담당하는 함수 분석파일 다운로드1
12064정성태11/25/201916076오류 유형: 580. HTTP Error 500.0/500.33 - ANCM In-Process Handler Load Failure
12063정성태11/21/201915001디버깅 기술: 134. windbg - RtlReportCriticalFailure로부터 parameters 정보 찾는 방법
12062정성태11/21/201915029디버깅 기술: 133. windbg - CoTaskMemFree/FreeCoTaskMem에서 발생한 덤프 분석 사례 - 두 번째 이야기
12061정성태11/20/201915502Windows: 167. CoTaskMemAlloc/CoTaskMemFree과 윈도우 Heap의 관계
12060정성태11/20/201915841디버깅 기술: 132. windbg/Visual Studio - HeapFree x64의 동작 분석
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...