Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/349.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 对默认属性init使用元类_Python_Init_Metaclass - Fatal编程技术网

Python 对默认属性init使用元类

Python 对默认属性init使用元类,python,init,metaclass,Python,Init,Metaclass,假设我有一个资源类,看起来是这样的: 类资源(元类=ResourceInit): 定义初始化(self,a,b): self.a=a self.b=b 我的目标是创建一个元类,即ResourceInit,该元类处理自动将属性分配给实例化的资源 我拥有的以下代码不起作用: config={“resources”:{“some_resource”:{“a”:“testA”,“b”:“testB”}} 类ResourceInit(类型): 定义调用(self,*args,**kwargs): obj

假设我有一个资源类,看起来是这样的:

类资源(元类=ResourceInit): 定义初始化(self,a,b): self.a=a self.b=b 我的目标是创建一个元类,即
ResourceInit
,该元类处理自动将属性分配给实例化的
资源

我拥有的以下代码不起作用:

config={“resources”:{“some_resource”:{“a”:“testA”,“b”:“testB”}}
类ResourceInit(类型):
定义调用(self,*args,**kwargs):
obj=super(ResourceInit,self)
argspec=inspect.getargspec(对象自初始化)
默认值={}
如果x!=“self”],[x代表argspec.args中的x:
对于配置[“资源”]中的设置。值()
对于键,设置中的val.items()
如果key==参数:
defaults.update({argument:val})
如果os.environ.get(参数):
defaults.update({argument:os.environ[argument]})
默认值。更新(kwargs)
对于键,默认值中的val.items()
setattr(对象、键、值)
返回obj
其思想是,在实例化时使用这个元类

res = Resource()
将自动填充
a
b
,如果它存在于
config
中或作为环境变量

显然,这是一个模拟示例,其中
a
b
将实质上更加具体,即
xx\u资源\u名称

我的问题是:

  • 您能否将参数传递给子类中的元类,即
    resource=“some_resource”
  • 如果未设置
    config
    os.environ
    ,即
    x=Test()
    导致
    TypeError:\uuu init\uuuuuo()缺少2个必需的位置参数:“a”和“b”

  • 备选方案

    你让事情变得比需要的更复杂了。简单的解决方案如下所示:

    def get_configured(name, value, config):
        if value is None:
            try:
                value = next(c for c in config['resources'].values() if name in c)[name]
            except StopIteration:
                value = os.environ.get(name, None)
        return value
    
    class Resource:
        def __init__(self, a=None, b=None):
            self.a = get_configured('a', a, config)
            self.b = get_configured('a', b, config)
    
    该函数是可重用的,您可以轻松地对其进行修改,以最大限度地减少每个类的样板文件数量

    完整答案

    然而,如果你坚持走元类之路,你也可以简化它。您可以在类定义中添加任意数量的纯关键字参数(问题1

    config
    ,以及
    元类
    之外的任何其他参数将直接传递给元类的
    \uuuuuu调用
    方法。从那里,它们被传递到元类中的
    \uuuuu new\uuuuu
    \uuuuuu init\uuuuu
    。因此,您必须实现
    \uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
    。您可能想实现
    \uuuuu init\uuuu
    而不是
    对象。从
    类型调用的\uuuu init\u子类\uuuuu
    。如果传入关键字参数,则会引发错误:

    class ResourceInit(type):
        def __new__(meta, name, bases, namespace, *, config, **kwargs):
            cls = super().__new__(meta, name, bases, namespace, **kwargs)
    
    请注意最后一个参数,
    config
    kwargs
    。位置参数作为
    基传递
    kwargs
    在传递给
    type.\uuuuu new\uuuu
    之前不得包含意外参数,但应传递类上
    \uuuuu init\u subclass\uuuu
    所需的任何参数

    当您可以直接访问
    名称空间
    时,无需使用
    \uuuuu self\uuu
    。请记住,只有在实际定义了
    \uuuu init\uuu
    方法时,才会更新默认值。您可能不想弄乱父级
    \uuuuu init\uuuuu
    。为了安全起见,如果
    \uuuuu init\uuuu
    不存在,我们将引发一个错误:

            if '__init__' not in namespace or not callable(getattr(cls, '__init__')):
                raise ValueError(f'Class {name} must specify its own __init__ function')
            init = getattr(cls, '__init__')
    
    现在我们可以使用一个类似于我上面展示的函数来建立默认值。您必须小心避免以错误的顺序设置默认值。因此,尽管所有纯关键字参数都可以有可选的默认值,但只有列表末尾的位置参数才能获得它们。这意味着位置默认值上的循环应从末尾开始,并应在找到没有默认值的名称后立即停止:

    def lookup(name, configuration):
        try:
            return next(c for c in configuration['resources'].values() if name in c)[name]
        except StopIteration:
            return os.environ.get(name)
    
    ...
    
            spec = inspect.getfullargspec(init)
    
            defaults = []
            for name in spec.args[:0:-1]:
                value = lookup(name, config)
                if value is None:
                    break
                defaults.append(value)
    
            kwdefaults = {}
            for name in spec.kwonlyargs:
                value = lookup(name, config)
                if value is not None:
                    kwdefaults[name] = value
    
    表达式
    spec.args[:0:-1]
    向后迭代所有位置参数,第一个参数除外。请记住,
    self
    是一个常规名称,而不是强制名称。因此,按索引删除它比按名称删除它要健壮得多

    使
    默认值
    kwdefaults
    值具有任何意义的关键是将它们分配给实际
    \uuuuuuuu默认值
    函数对象(问题2)上的
    \uuuuuuuu默认值

    \uuuu默认值\uuuu
    必须反转并转换为元组。前者是获得正确论点顺序所必需的。后者是
    \u默认值\u
    描述符所必需的

    快速测试

    >>> configR = {"resources": {"some_resource": {"a": "testA", "b": "testB"}}}
    >>> class Resource(metaclass=ResourceInit, config=configR):
    ...     def __init__(self, a, b):
    ...         self.a = a
    ...         self.b = b
    ... 
    >>> r = Resource()
    >>> r.a
    'testA'
    >>> r.b
    'testB'
    
    TL;DR

    def lookup(name, configuration):
        try:
            return next(c for c in configuration['resources'].values() if name in c)[name]
        except StopIteration:
            return os.environ.get(name)
    
    class ResourceInit(type):
        def __new__(meta, name, bases, namespace, **kwargs):
            config = kwargs.pop('config')
            cls = super().__new__(meta, name, bases, namespace, **kwargs)
    
            if '__init__' not in namespace or not callable(getattr(cls, '__init__')):
                raise ValueError(f'Class {name} must specify its own __init__ function')
            init = getattr(cls, '__init__')
            spec = inspect.getfullargspec(init)
    
            defaults = []
            for name in spec.args[:0:-1]:
                value = lookup(name, config)
                if value is None:
                    break
                defaults.append(value)
    
            kwdefaults = {}
            for name in spec.kwonlyargs:
                value = lookup(name, config)
                if value is not None:
                    kwdefaults[name] = value
    
            init.__defaults__ = tuple(defaults[::-1])
            init.__kwdefaults__ = kwdefaults
    
            return cls
    

    类方法要比元类简单得多。将从
    config
    或环境变量派生的值直接分配给
    A
    b
    有什么不对?@blhsing。我也在想同样的事情。这一切都是不必要的复杂。在最后一部分中,
    Test
    是什么?
    x=Test()@我的意思是
    x=Resource()
    这太棒了。。。关于如何扩展这样的内容,我的一个问题是:假设我们有一个
    类资源
    ,它接受
    a,b,c
    参数。。。元类将能够通过配置找到
    a、b
    ,但不能找到
    c
    。当前使用此选项,如果存在额外的
    c
    参数,则错误为:
    TypeError
    
    >>> configR = {"resources": {"some_resource": {"a": "testA", "b": "testB"}}}
    >>> class Resource(metaclass=ResourceInit, config=configR):
    ...     def __init__(self, a, b):
    ...         self.a = a
    ...         self.b = b
    ... 
    >>> r = Resource()
    >>> r.a
    'testA'
    >>> r.b
    'testB'
    
    def lookup(name, configuration):
        try:
            return next(c for c in configuration['resources'].values() if name in c)[name]
        except StopIteration:
            return os.environ.get(name)
    
    class ResourceInit(type):
        def __new__(meta, name, bases, namespace, **kwargs):
            config = kwargs.pop('config')
            cls = super().__new__(meta, name, bases, namespace, **kwargs)
    
            if '__init__' not in namespace or not callable(getattr(cls, '__init__')):
                raise ValueError(f'Class {name} must specify its own __init__ function')
            init = getattr(cls, '__init__')
            spec = inspect.getfullargspec(init)
    
            defaults = []
            for name in spec.args[:0:-1]:
                value = lookup(name, config)
                if value is None:
                    break
                defaults.append(value)
    
            kwdefaults = {}
            for name in spec.kwonlyargs:
                value = lookup(name, config)
                if value is not None:
                    kwdefaults[name] = value
    
            init.__defaults__ = tuple(defaults[::-1])
            init.__kwdefaults__ = kwdefaults
    
            return cls