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

비밀번호

댓글 작성자
 




... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1100정성태8/17/201128901.NET Framework: 236. SqlDbType - DateTime, DateTime2, DateTimeOffset의 차이점파일 다운로드1
1099정성태8/15/201128337오류 유형: 132. 어느 순간 갑자기 접속이 안 되는 TFS 서버
1098정성태8/15/201150357웹: 24. 네이버는 어떻게 로그인 처리를 할까요? [2]
1097정성태8/15/201121655.NET Framework: 235. 메서드의 메타 데이터 토큰 값으로 클래스를 찾아내는 방법
1096정성태8/15/201125794디버깅 기술: 42. Watson Bucket 정보를 이용한 CLR 응용 프로그램 예외 분석 - (2)
1095정성태8/14/201126217디버깅 기술: 41. Windbg - 비정상 종료된 닷넷 프로그램의 StackTrace에서 보이는 offset 값 의미
1094정성태8/14/201130612오류 유형: 131. Fiddler가 강제 종료된 경우, 웹 사이트 방문이 안되는 현상
1093정성태7/27/201124230오류 유형: 130. Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor ... Access is denied.
1092정성태7/22/201126636Team Foundation Server: 46. 코드 이외의 파일에 대해 소스 제어에서 제외시키는 방법
1091정성태7/21/201125677개발 환경 구성: 128. WP7 Emulator 실행 시 audiodg.exe의 CPU 소모율 증가 [2]
1089정성태7/18/201131255.NET Framework: 234. 왜? Button 컨트롤에는 MouseDown/MouseUp 이벤트가 발생하지 않을까요?파일 다운로드1
1088정성태7/16/201124299.NET Framework: 233. Entity Framework 4.1 - 윈도우 폰 7에서의 CodeFirst 순환 참조 문제파일 다운로드1
1087정성태7/15/201127021.NET Framework: 232. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 - 두 번째 이야기파일 다운로드1
1086정성태7/14/201128440.NET Framework: 231. Entity Framework 4.1 - CodeFirst 개체의 직렬화 시 순환 참조 해결하는 방법 [1]파일 다운로드1
1085정성태7/14/201128891.NET Framework: 230. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류 - 두 번째 이야기파일 다운로드1
1084정성태7/11/201134175.NET Framework: 229. SQL 서버 - DB 테이블의 데이터 변경에 대한 알림 처리 [4]파일 다운로드1
1083정성태7/11/201128220.NET Framework: 228. Entity Framework 4.1 - Code First + WCF 서비스 시 EndpointNotFoundException 오류
1082정성태7/10/201127787.NET Framework: 227. basicHttpBinding + 사용자 정의 인증 구현 [2]파일 다운로드1
1081정성태7/9/201127109VC++: 53. Windows 7에서 gcc.exe 실행 시 Access denied 오류 [2]
1080정성태7/8/201125607웹: 23. Sysnet 웹 사이트의 HTML5 변환 기록 - 두 번째 이야기파일 다운로드1
1079정성태7/6/201130025오류 유형: 129. Hyper-V + Realtek 랜카드가 설치된 시스템의 BSOD 현상 [2]
1078정성태7/5/201137532VC++: 52. Chromium 컴파일하는 방법 [2]
1077정성태6/24/201135168.NET Framework: 226. HttpWebRequest 타입의 HaveResponse 속성 이야기파일 다운로드1
1076정성태6/23/201129320오류 유형: 128. SQL Express - User Instance 옵션을 사용한 경우 발생하는 오류 메시지 유형 2가지
1075정성태6/21/201124927VS.NET IDE: 69. 윈폰 프로젝트에서 WCF 서비스 참조할 때 Reference.cs 파일이 비어있는 경우
1074정성태6/20/201125027.NET Framework: 225. 닷넷 네트워크 라이브러리의 트레이스 기능파일 다운로드1
... 151  152  153  154  155  156  157  [158]  159  160  161  162  163  164  165  ...