Python에서 최소 플러그인 아키텍처 구축
저는 꽤 기술적인 독자(과학자)가 사용하는 Python으로 작성된 어플리케이션을 가지고 있습니다.
스크립트/플러그인 아키텍처 등 사용자가 애플리케이션을 확장할 수 있는 좋은 방법을 찾고 있습니다.
매우 가벼운 것을 찾고 있습니다.대부분의 스크립트(플러그인)는 서드파티에 의해 개발 및 배포되어 설치되는 것이 아니라 사용자가 몇 분 안에 작업을 자동화하고 파일 포맷을 지원하는 작업을 추가할 수 있습니다.따라서 플러그인은 최소한의 보일러 플레이트 코드를 가지고 있어야 하며 폴더에 복사하는 것 이외에는 '설치'가 필요하지 않습니다(따라서 setuptools 엔트리 포인트나 Zope 플러그인 아키텍처 등).
이와 같은 시스템이나 유사한 계획을 구현하는 프로젝트가 이미 존재하고 있습니까?아이디어/영향을 찾아야 합니다.
내 디렉토리는 기본적으로 "plugins"라고 불리는 디렉토리입니다.이 디렉토리는 메인 앱이 imp.load_module을 사용하여 파일을 픽업하고 모듈레벨의 설정 파라미터로 잘 알려진 진입점을 찾아 거기에서 이동할 수 있습니다.플러그인이 액티브하게 되어 있는 다이나믹한 기능에는, 파일 감시 기능을 사용하고 있습니다만, 이 기능은 매우 편리합니다.
물론 "[크고 복잡한 것은 필요 없습니다] X가 아니라 가벼운 것을 원합니다"라고 말하는 모든 요구사항은 X가 발견된 요구사항을 한 번에 다시 구현할 위험을 수반합니다.그렇다고 해서 그것을 즐길 수 없는 것은 아닙니다.
module_example.py
:
def plugin_main(*args, **kwargs):
print args, kwargs
loader.py
:
def load_plugin(name):
mod = __import__("module_%s" % name)
return mod
def call_plugin(name, *args, **kwargs):
plugin = load_plugin(name)
plugin.plugin_main(*args, **kwargs)
call_plugin("example", 1234)
이것은 확실히 「최소한」이며, 에러 체크도 없고, 아마 수많은 시큐러티 문제도 없고, 유연성도 높지 않습니다만, Python의 플러그인 시스템이 얼마나 심플할 수 있는지를 알 수 있을 것입니다.
imp 모듈도 조사해 보는 것이 좋을 것 같습니다만,__import__
,os.listdir
현악기 조작도 좀 하고
기존 플러그인 프레임워크/라이브러리에 대한 이 개요를 참조하십시오.이것이 좋은 출발점입니다.저는 옙시를 꽤 좋아하지만, 그것은 당신의 사용 사례에 따라 다릅니다.
그 질문은 매우 흥미롭지만, 자세한 내용이 없으면 대답하기 어렵다고 생각합니다.이게 무슨 어플이에요?GUI가 있나요?명령줄 도구입니까?대본 세트?독특한 진입점이 있는 프로그램 등...
제가 가지고 있는 정보가 적기 때문에, 매우 일반적인 방법으로 대답하겠습니다.
플러그인을 추가해야 하는 것은 어떤 의미입니까?
- 로드하는 경로/디렉토리가 리스트 되어 있는 설정 파일을 추가할 필요가 있습니다.
- 또 다른 방법은 "플러그인/디렉토리 내의 모든 파일이 로드됩니다"라고 말하는 것이지만, 사용자가 파일을 이동하도록 요구하는 불편함이 있습니다.
- 마지막 중간 옵션은 모든 플러그인이 동일한 플러그인/폴더에 있어야 하며 구성 파일 내의 상대 경로를 사용하여 활성화/비활성화해야 합니다.
순수한 코드/설계 관행에서는 사용자가 확장하기를 원하는 동작/특정 작업을 명확하게 결정해야 합니다.항상 오버라이드되는 공통 진입점/기능 세트를 식별하고 이러한 작업 내에서 그룹을 결정합니다.이 작업이 완료되면 응용 프로그램을 쉽게 확장할 수 있습니다.
MediaWiki에서 영감을 얻어 후크를 사용하는 예(PHP, 하지만 언어가 정말 중요합니까?)
import hooks
# In your core code, on key points, you allow user to run actions:
def compute(...):
try:
hooks.runHook(hooks.registered.beforeCompute)
except hooks.hookException:
print('Error while executing plugin')
# [compute main code] ...
try:
hooks.runHook(hooks.registered.afterCompute)
except hooks.hookException:
print('Error while executing plugin')
# The idea is to insert possibilities for users to extend the behavior
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)
# --------------------
# And in the plugin code:
# [...] plugin magic
def doStuff():
# ....
# and register the functionalities in hooks
# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)
또 다른 예는 수은에서 영감을 얻은 것이다.여기서 확장은 hg 명령줄 실행 파일에 명령만 추가하여 동작을 확장합니다.
def doStuff(ui, repo, *args, **kwargs):
# when called, a extension function always receives:
# * an ui object (user interface, prints, warnings, etc)
# * a repository object (main object from which most operations are doable)
# * command-line arguments that were not used by the core program
doMoreMagicStuff()
obj = maybeCreateSomeObjects()
# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }
두 가지 방법 모두 확장을 위해 공통 초기화 및 완료가 필요할 수 있습니다.모든 확장이 구현되어야 하는 공통 인터페이스를 사용하거나(두 번째 접근법에 더 적합하며, mercurial은 모든 확장에 대해 호출되는 reposetup(ui, repo)을 사용), 후크와 같은 접근 방식을 사용할 수 있습니다.셋업 훅
하지만 다시 한 번 말씀드리지만, 더 유용한 답변을 원하신다면 질문을 좁혀야 합니다.
Marty Allchin의 심플한 플러그인 프레임워크는 제 자신의 요구를 위해 사용하는 베이스입니다.꼭 한번 보시길 권합니다.심플하고 쉽게 해킹할 수 있는 것을 원하신다면 정말 좋은 시작이라고 생각합니다.장고 스니펫으로도 볼 수 있어요.
저는 은퇴한 생물학자로 디지털 마이크로그레이프(micrograqphs)를 다루면서 SGi 기계에서 실행하기 위해 이미지 처리 및 분석 패키지(기술적으로는 라이브러리가 아님)를 작성해야 했습니다.C로 코드를 작성하고 스크립트 언어로 Tcl을 사용했습니다.GUI는 그대로 Tk를 사용하여 실행되었습니다.TCL에 나타난 명령어는 "extensionName commandName arg0 arg1 ... param0 param1 ..." 형식, 즉 단순한 공백으로 구분된 단어와 숫자입니다.Tcl이 "extension Name" 서브스트링을 확인했을 때 제어는 C 패키지로 넘어갔습니다.다음으로 명령어를 렉서/파서(lex/yacc에서 실행)를 통해 실행한 후 필요에 따라 C 루틴을 호출했습니다.
패키지를 조작하는 명령어는 GUI의 창을 통해 하나씩 실행할 수 있지만 배치 작업은 유효한 TCL 스크립트인 텍스트파일을 편집함으로써 이루어집니다.원하는 파일 수준의 조작을 실행하는 템플릿을 선택한 후 실제 디렉토리 및 파일 이름과 패키지 명령어를 포함하도록 복사본을 편집할 수 있습니다.그것은 마법처럼 작동했다.그때까지...
1) PC로 세상이 바뀌었고 2) TCL의 iffy한 조직력이 실제로 불편해지기 시작하면서 스크립트가 약 500줄 이상 길어졌습니다.시간이 흘렀습니다...
저는 은퇴했고, 파이톤이 발명되었습니다. 그리고 그것은 Tcl의 완벽한 후계자처럼 보였습니다.PC에서 C 프로그램을 컴파일(매우 큰)하거나, C 패키지로 Python을 확장하거나, Python/Gt?/Tk?/에서 GUI를 실행하는 등의 과제에 직면한 적이 없기 때문에 저는 포트를 해본 적이 없습니다.그러나 편집 가능한 템플릿스크립트를 갖는다는 오래된 생각은 여전히 유효할 것 같습니다.또한 패키지 명령어를 네이티브 Python 형식으로 입력하는 것은 큰 부담이 되지 않습니다.예를 들어 다음과 같습니다.
packageName.command(arg0, arg1, ..., param0, param1, ...)
점 몇 개, 패런, 쉼표 몇 개가 더 있지만 쇼스토퍼는 아니에요
누군가가 Python에서 lex와 yacc 버전을 실행한 것을 기억합니다(http://www.dabeaz.com/ply/),을 시도해 보세요.그래서 그것들이 여전히 필요하다면, 그들은 근처에 있습니다).
이 횡설수설하는 요점은 Python 자체가 과학자들이 사용할 수 있는 "경량" 프런트 엔드로 생각된다는 것입니다.왜 그렇지 않다고 생각하는지 궁금해요. 정말이에요.
나중에 추가:어플리케이션 gedit에서는 플러그인이 추가될 것으로 예상되며, 이 사이트에는 간단한 플러그인 절차에 대한 설명이 몇 분 안에 나와 있습니다.시험:
https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld
저는 여전히 당신의 질문을 더 잘 이해하고 싶습니다.1) 과학자들이 당신의 (Python) 어플리케이션을 아주 간단하게 사용할 수 있기를 원하는지, 아니면 2) 과학자들이 당신의 어플리케이션에 새로운 기능을 추가하기를 원하는지 잘 모르겠습니다.선택지 #1은 이미지에 직면한 상황으로, 현재 필요에 따라 수정한 범용 스크립트를 사용하게 되었습니다.플러그인에 대한 아이디어를 이끌어내는 것은 Choice #2입니까?아니면 어플리케이션에 대한 명령어를 실행할 수 없게 만드는 것은 어플리케이션의 일부 측면입니까?
Python Decorators를 검색했을 때, 심플하지만 편리한 코드 스니펫을 발견했습니다.당신의 욕구에는 맞지 않을 수도 있지만 매우 고무적입니다.
Scipy Advanced Python#Plugin 등록 시스템
class TextProcessor(object):
PLUGINS = []
def process(self, text, plugins=()):
if plugins is ():
for plugin in self.PLUGINS:
text = plugin().process(text)
else:
for plugin in plugins:
text = plugin().process(text)
return text
@classmethod
def plugin(cls, plugin):
cls.PLUGINS.append(plugin)
return plugin
@TextProcessor.plugin
class CleanMarkdownBolds(object):
def process(self, text):
return text.replace('**', '')
사용방법:
processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")
Pycon 2009에서 Andre Roberge 박사의 다양한 플러그인 아키텍처에 대한 훌륭한 토론이 즐거웠습니다.그는 매우 간단한 것부터 시작하여 다양한 플러그인 구현 방법에 대해 개략적으로 설명합니다.
팟캐스트(원숭이 패칭 설명 후 2부)로 제공되며, 6개의 블로그 엔트리가 포함되어 있습니다.
결정을 내리기 전에 빨리 들어보는 것을 추천합니다.
최소한의 플러그인 아키텍처를 찾아 이곳에 도착했는데, 너무 많은 것을 발견했어요.Super Simple Python Plugins를 구현했습니다.이 기능을 사용하려면 하나 이상의 디렉토리를 만들고 특수 디렉토리를 삭제하십시오.__init__.py
을 사용하다를 Import Python 모듈로서 그「Import」라고 하는 「Python」이라고 하는 「Import」라고 하는 「Python」에됩니다.__all__
리스트. 그러면 이들 모듈을 검증/초기화/등록하는 것은 사용자에게 달려 있습니다.README の read read read read read 。
실제로 setuptools는 프로젝트 매뉴얼에서 인용한 다음 예시와 같이 "플러그인 디렉토리"에서 작동합니다.http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins
사용 예:
plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions) # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)
장기적으로 볼 때 setuptools는 충돌이나 요구 사항 누락 없이 플러그인을 로드할 수 있기 때문에 훨씬 안전한 선택입니다.
또 다른 장점은 원래 애플리케이션에 신경 쓸 필요 없이 동일한 메커니즘을 사용하여 플러그인 자체를 확장할 수 있다는 것입니다.
@edomaurs의 답변에 대해 자세히 설명하자면, Marty Alchin의 작업에서 영감을 얻은 단순한 플러그인 프레임워크인 simple_plugins(쉐임리스 플러그)에 대해 살펴보겠습니다.
프로젝트의 README에 근거한 간단한 사용 예:
# All plugin info
>>> BaseHttpResponse.plugins.keys()
['valid_ids', 'instances_sorted_by_id', 'id_to_class', 'instances',
'classes', 'class_to_id', 'id_to_instance']
# Plugin info can be accessed using either dict...
>>> BaseHttpResponse.plugins['valid_ids']
set([304, 400, 404, 200, 301])
# ... or object notation
>>> BaseHttpResponse.plugins.valid_ids
set([304, 400, 404, 200, 301])
>>> BaseHttpResponse.plugins.classes
set([<class '__main__.NotFound'>, <class '__main__.OK'>,
<class '__main__.NotModified'>, <class '__main__.BadRequest'>,
<class '__main__.MovedPermanently'>])
>>> BaseHttpResponse.plugins.id_to_class[200]
<class '__main__.OK'>
>>> BaseHttpResponse.plugins.id_to_instance[200]
<OK: 200>
>>> BaseHttpResponse.plugins.instances_sorted_by_id
[<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>]
# Coerce the passed value into the right instance
>>> BaseHttpResponse.coerce(200)
<OK: 200>
플러그인 시스템에 대한 다른 접근 방법으로 Extend Me 프로젝트를 선택할 수 있습니다.
예를 들어 단순 클래스와 그 확장을 정의합니다.
# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
my_attr_1 = 25
def my_method1(self, arg1):
print('Hello, %s' % arg1)
# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
def my_method1(self, arg1):
super(MyCoolClassExtension1, self).my_method1(arg1.upper())
def my_method2(self, arg1):
print("Good by, %s" % arg1)
그것을 사용해 보세요.
>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World
그리고 무대 뒤에 숨겨진 것을 보여주십시오.
>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]
extend_me 라이브러리는 메타클래스를 통해 클래스 생성 프로세스를 조작합니다.상기의 예에서는, 새로운 instance를 작성할 때,MyCoolClass
다 를 받았습니다.MyCoolClassExtension
★★★★★★★★★★★★★★★★★」MyCoolClass
Python의 다중 상속 덕분에 두 기능 모두 갖추게 되었습니다.
클래스 작성을 보다 효과적으로 제어하기 위해 이 lib에는 몇 가지 메타클래스가 정의되어 있습니다.
ExtensibleType
을 통해 심플한 합니다.- 「 subclassing - 」 。ExtensibleByHashType
- Extensible과 유사타입이지만 특화된 버전의 클래스를 빌드할 수 있어 기본 클래스의 글로벌 확장과 특화된 버전의 클래스 확장이 가능합니다.
이 lib는 OpenERP Proxy Project에서 사용되고 있으며, 충분히 동작하고 있는 것 같습니다.
실제 사용 예는 OpenERP Proxy 'field_datetime' 확장자를 참조하십시오.
from ..orm.record import Record
import datetime
class RecordDateTime(Record):
""" Provides auto conversion of datetime fields from
string got from server to comparable datetime objects
"""
def _get_field(self, ftype, name):
res = super(RecordDateTime, self)._get_field(ftype, name)
if res and ftype == 'date':
return datetime.datetime.strptime(res, '%Y-%m-%d').date()
elif res and ftype == 'datetime':
return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
return res
Record
이치노 RecordDateTime
는 확장입니다.
확장 하는 모듈을 Import합니다.Record
이후에 생성된 개체는 기본 클래스에 확장 클래스를 가지므로 모든 기능을 갖습니다.
이 라이브러리의 주요 장점은 확장 가능한 오브젝트를 동작시키는 코드로 확장에 대해 알 필요가 없으며 확장이 확장 가능한 오브젝트의 모든 것을 변경할 수 있다는 것입니다.
setuptools에는 다음 EntryPoint가 있습니다.
진입점은 배포가 다른 배포에서 사용할 수 있도록 Python 개체(예: 함수 또는 클래스)를 "애드버타이즈"하는 간단한 방법입니다.확장 가능한 응용 프로그램 및 프레임워크는 특정 배포 또는 sys.path 상의 모든 활성 배포에서 특정 이름 또는 그룹을 가진 진입점을 검색한 후 애드버타이즈된 개체를 검사하거나 로드할 수 있습니다.
AFIK 이 패키지는 pip 또는 virtualenv를 사용하는 경우 항상 사용할 수 있습니다.
pluginlib를 사용할 수 있습니다.
플러그인은 쉽게 만들 수 있으며 다른 패키지, 파일 경로 또는 진입점에서 로드할 수 있습니다.
필요한 메서드를 정의하여 플러그인 부모 클래스를 만듭니다.
import pluginlib
@pluginlib.Parent('parser')
class Parser(object):
@pluginlib.abstractmethod
def parse(self, string):
pass
부모 클래스를 상속하여 플러그인을 만듭니다.
import json
class JSON(Parser):
_alias_ = 'json'
def parse(self, string):
return json.loads(string)
플러그인을 로드합니다.
loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))
가끔 Python에서 플러그인 프레임워크를 검색하면서 이 스레드를 읽었습니다.몇 가지 써봤는데 단점이 있었어요.다음은 2017년의 정밀 조사를 위해 생각해낸 것입니다.인터페이스 프리, 느슨하게 결합된 플러그인 관리 시스템입니다.나 나중에 실어줘.다음은 사용 방법에 대한 튜토리얼입니다.
Python을 위한 작은 플러그인 시스템을 찾기 위해 많은 시간을 소비했습니다.하지만 저는 자연스럽고 유연한 유산이 이미 있다면 왜 그것을 사용하지 않을까 생각했습니다.
플러그인에 상속을 사용할 때의 유일한 문제는 가장 구체적인(상속 트리에서 가장 낮은) 플러그인 클래스가 무엇인지 모른다는 것입니다.
그러나 이 문제는 기본 클래스의 상속을 추적하는 메타클래스와 대부분의 특정 플러그인에서 상속하는 클래스를 빌드할 수 있습니다(아래 그림의 루트 확장).
그래서 저는 메타클래스를 코드화하여 해결책을 생각해냈습니다.
class PluginBaseMeta(type):
def __new__(mcls, name, bases, namespace):
cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
if not hasattr(cls, '__pluginextensions__'): # parent class
cls.__pluginextensions__ = {cls} # set reflects lowest plugins
cls.__pluginroot__ = cls
cls.__pluginiscachevalid__ = False
else: # subclass
assert not set(namespace) & {'__pluginextensions__',
'__pluginroot__'} # only in parent
exts = cls.__pluginextensions__
exts.difference_update(set(bases)) # remove parents
exts.add(cls) # and add current
cls.__pluginroot__.__pluginiscachevalid__ = False
return cls
@property
def PluginExtended(cls):
# After PluginExtended creation we'll have only 1 item in set
# so this is used for caching, mainly not to create same PluginExtended
if cls.__pluginroot__.__pluginiscachevalid__:
return next(iter(cls.__pluginextensions__)) # only 1 item in set
else:
name = cls.__pluginroot__.__name__ + 'PluginExtended'
extended = type(name, tuple(cls.__pluginextensions__), {})
cls.__pluginroot__.__pluginiscachevalid__ = True
return extended
따라서 메타클래스로 만든 루트 베이스가 있고 루트 베이스에서 상속되는 플러그인 트리가 있는 경우 하위 분류만으로 가장 구체적인 플러그인에서 상속되는 클래스를 자동으로 얻을 수 있습니다.
class RootExtended(RootBase.PluginExtended):
... your code here ...
코드 베이스는 매우 작고(순수한 코드의 30줄까지), 상속이 허용하는 한 유연합니다.
관심이 있으시면 https://github.com/thodnev/pluginlib에 문의해 주십시오.
그라운드 워크도 보실 수 있습니다.
이 아이디어는 패턴 및 플러그인이라고 하는 재사용 가능한 구성 요소를 중심으로 애플리케이션을 구축하는 것입니다.은 러음음음음음음음음음음음 that that에서 파생된 입니다.GwBasePattern
기본적인 예를 다음에 제시하겠습니다.
from groundwork import App
from groundwork.patterns import GwBasePattern
class MyPlugin(GwBasePattern):
def __init__(self, app, **kwargs):
self.name = "My Plugin"
super().__init__(app, **kwargs)
def activate(self):
pass
def deactivate(self):
pass
my_app = App(plugins=[MyPlugin]) # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it
또한 명령줄 인터페이스, 시그널링 또는 공유 객체 등을 처리할 수 있는 고급 패턴도 있습니다.
Groundwork를 통해 자동으로 .setuptools
는 특별한 포인트인 groundwork.plugin
.
여기 문서들이 있습니다.
면책사항:저는 그라운드 워크의 저자 중 한 명입니다.
현재의 헬스케어 제품에는 인터페이스 클래스와 함께 구현된 플러그인 아키텍처가 있습니다.당사의 기술 스택은 API용 Python 위에 Django가 있고 프런트 엔드용 nodejs 위에 Nuxtj가 있습니다.
당사 제품에는 기본적으로 Django와 Nuxtjs에 준거한 pip 및 npm 패키지로 작성된 플러그인 매니저 앱이 있습니다.
새로운 플러그인 개발(pip 및 npm)을 위해 플러그인 매니저를 의존관계로 만들었습니다.
Pip 패키지:셋업의 도움을 받아.py 플러그인의 엔트리 포인트를 추가하여 플러그인 매니저(initiations, initiations 등)를 조작할 수 있습니다.https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation
npm 패키지:pip와 마찬가지로 npm 스크립트에는 설치를 처리하기 위한 후크가 있습니다.https://docs.npmjs.com/misc/scripts
사용 사례:
플러그인 개발팀은 현재 핵심 개발팀과 분리되어 있습니다.플러그인 개발 범위는 제품 카테고리 중 하나에 정의된 서드파티 앱과 통합하기 위한 것입니다.플러그인 인터페이스는 다음과 같이 분류됩니다.- 팩스, 전화, 이메일 등 플러그인 매니저를 새로운 카테고리로 확장할 수 있습니다.
고객님의 경우:하나의 플러그인이 작성되어 동일한 플러그인이 작업에 재사용될 수 있습니다.
플러그인 개발자가 코어 개체를 재사용해야 하는 경우 플러그인 관리자 내에서 추상화 수준을 수행하여 개체를 사용할 수 있으므로 모든 플러그인이 이러한 메서드를 상속할 수 있습니다.
제품에 실장되어 있는 방법을 공유해 주시는 것만으로, 조금이나마 알 수 있으면 좋겠다고 생각하고 있습니다.
언급URL : https://stackoverflow.com/questions/932069/building-a-minimal-plugin-architecture-in-python
'programing' 카테고리의 다른 글
GSON과의 JSON 해석 중 Enum 사용 (0) | 2022.09.06 |
---|---|
mysql에서 문자열을 플로트로 변환하려면 어떻게 해야 하나요? (0) | 2022.09.06 |
스프링에서의 오브젝트매퍼 설정 (0) | 2022.09.06 |
팬더 로크 vs 일록 vs 일트? (0) | 2022.09.06 |
사용 중인 NumPy 버전을 확인하려면 어떻게 해야 합니까? (0) | 2022.09.06 |