gpt4 book ai didi

Python 元类和导入 *

转载 作者:太空宇宙 更新时间:2023-11-04 10:18:05 25 4
gpt4 key购买 nike

主要目标:在运行时使用该字符串动态创建的工厂中自动注册类(通过字符串),类可以在它们自己的文件中,而不是分组在一个文件中。

我有几个类都继承自同一个基类,它们定义了一个字符串作为它们的类型。

用户想要获取其中一个类的实例,但只知道运行时的类型。

因此我有一个工厂来创建给定类型的实例。我不想硬编码“if then 语句”,所以我有一个元类来注册基类的所有子类:

class MetaRegister(type):
# we use __init__ rather than __new__ here because we want
# to modify attributes of the class *after* they have been
# created
def __init__(cls, name, bases, dct):
if not hasattr(cls, 'registry'):
# this is the base class. Create an empty registry
cls.registry = {}
else:
# this is a derived class. Add cls to the registry
interface_id = cls().get_model_type()
cls.registry[interface_id] = cls

super(MetaRegister, cls).__init__(name, bases, dct)

问题是要使它工作,工厂必须导入所有子类(因此元类运行)。要解决此问题,您可以使用 from X import *但要使其正常工作,您需要在包的 __init__.py 文件中定义一个 __all__ var 以包含所有子类。

我不想对子类进行硬编码,因为它违背了使用元类的目的。

我可以使用以下方法查看包中的文件:

import glob

from os.path import dirname, basename, isfile

modules = glob.glob(dirname(__file__) + "/*.py")
__all__ = [basename(f)[:-3] for f in modules if isfile(f)]

效果很好,但是项目需要编译成单个 .so 文件,这会导致文件系统的使用无效。

那么我怎样才能在不硬编码类型的情况下实现在运行时创建实例的主要目标呢?

有没有办法在运行时填充 __all__ var 而无需触及文件系统?

在 Java 中,我可能会用注解装饰类,然后在运行时获取所有带有该注解的类,在 python 上有类似的东西吗?

我知道 python 中有装饰器,但我不确定我能否以这种方式使用它们。

编辑 1:每个子类都必须在一个文件中:

- Models
-- __init__.py
-- ModelFactory.py
-- Regression
--- __init__.py
--- Base.py
--- Subclass1.py
--- Subclass2ExtendsSubclass1.py

编辑 2:说明问题的一些代码:

+ main.py
|__ Models
|__ __init__.py
|__ ModelFactory.py
|__ Regression
|__ init__.py
|__ Base.py
|__ SubClass.py
|__ ModelRegister.py

main.py

from models.ModelFactory import ModelFactory

if __name__ == '__main__':
ModelFactory()


ModelFactory.py

from models.regression.Base import registry
import models.regression

class ModelFactory(object):
def get(self, some_type):
return registry[some_type]


ModelRegister.py
class ModelRegister(type):
# we use __init__ rather than __new__ here because we want
# to modify attributes of the class *after* they have been
# created
def __init__(cls, name, bases, dct):
print cls.__name__
if not hasattr(cls, 'registry'):
# this is the base class. Create an empty registry
cls.registry = {}
else:
# this is a derived class. Add cls to the registry
interface_id = cls().get_model_type()
cls.registry[interface_id] = cls

super(ModelRegister, cls).__init__(name, bases, dct)

Base.py

from models.regression.ModelRegister import ModelRegister

class Base(object):
__metaclass__ = ModelRegister

def get_type(self):
return "BASE"

SubClass.py

from models.regression.Base import Base


class SubClass(Base):
def get_type(self):
return "SUB_CLASS"

运行它你只能看到它打印的“Base”。使用装饰器会得到相同的结果。

最佳答案

将类注册为运行时的一种简单方法是使用装饰器:

registry = {}

def register(cls):
registry[cls.__name__] = cls
return cls

@register
class Foo(object):
pass

@register
class Bar(object):
pass

如果您的所有类都定义在同一个模块中,并且该模块是在运行时导入的,那么这将起作用。但是,您的情况使事情复杂化。首先,您想在不同的模块中定义您的类。这意味着我们必须能够在运行时动态确定包中存在哪些模块。使用 Python 的 pkgutil 模块会很简单,但是,您还声明您正在使用 Nuitka 将包编译成扩展模块。 pkgutil 不适用于此类扩展模块。

我找不到任何记录在案的方法来从 Python 中确定 Nuitka 扩展模块中包含的模块。如果确实存在,上面的装饰器方法将在动态导入每个子模块后起作用。

事实上,我认为最直接的解决方案是在编译之前编写一个脚本来生成一个__init__.py。假设我们有如下包结构:

.
├── __init__.py
├── plugins
│   ├── alpha.py
│   └── beta.py
└── register.py

“插件”包含在 plugins 目录中。文件内容为:

# register.py
# -----------

registry = {}
def register(cls):
registry[cls.__name__] = cls
return cls

# __init__.py
# -----------

from . import plugins
from . import register


# ./plugins/alpha.py
# ------------------

from ..register import register

@register
class Alpha(object):
pass


# ./plugins/beta.py
# ------------------

from ..register import register

@register
class Beta(object):
pass

就目前而言,导入上面的包不会导致任何类被注册。这是因为类定义永远不会运行,因为包含它们的模块永远不会被导入。解决办法是为plugins文件夹自动生成一个__init__.py。下面是一个执行此操作的脚本 -- 这个脚本可以成为您编译过程的一部分。

import pathlib


root = pathlib.Path('./mypkg/plugins')
exclude = {'__init__.py'}

def gen_modules(root):
for entry in root.iterdir():
if entry.suffix == '.py' and entry.name not in exclude:
yield entry.stem

with (root / '__init__.py').open('w') as fh:
for module in gen_modules(root):
fh.write('from . import %s\n' % module)

将此脚本放在包根目录之上的一个目录(假设您的包名为 mypkg)并运行它会产生:

from . import alpha
from . import beta

现在进行测试:我们编译包:

nuitka --module mypkg --recurse-to=mypkg

并尝试导入它,检查是否所有类都已正确注册:

>>> import mypkg
>>> mypkg.register.registry
{'Beta': <class 'mypkg.plugins.beta.Beta'>,
'Alpha': <class 'mypkg.plugins.alpha.Alpha'>}

请注意,同样的方法也适用于使用元类来注册插件类,我只是更喜欢在这里使用装饰器。

关于Python 元类和导入 *,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34160529/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com