Python 元类这样用会产生问题吗?

2019-06-29 16:43:21 +08:00
 caneman

我想通过元类做到以下几点:

1.获取基于它产生的类的,具有特定命名规则的函数列表

2.列表里面的函数可以直接被调用,而无需做多余额外的操作

关键是第二点该如何操作? 例如列表 lst=[func1, func2],我想实现的是可以直接通过func1()func2()这种方式来运行函数

我目前能做到的解决方案是

class Metaclass(type):

    func_list = []

    def __new__(cls, name, base, attrs):
        obj = type.__new__(cls, name, base, attrs)()
        for k,v in attrs.items():
            if k.startswith('at'):
                func = obj.__getattribute__(k)
                Metaclass.func_list.append(func)
        return type.__new__(cls, name, base, attrs)


class A(object, metaclass=Metaclass):

    def at_func1(self):
        print('A_func1')

    def at_func2(self):
        print('A_func2')

请问这是最优的吗?或者一般的解决方法是什么?

有没有办法在元类里面不创建 obj 的情况下,实现我上面的需求? 即obj = type.__new__(cls, name, base, attrs)()没有这行代码 因为 obj 并不会随着元类创建类的完毕而销毁,会有一个元类创建的实例对象( obj )常驻内存,而这个实例有太多额外的东西是不需要的,我仅仅需要的是里面的两个特定函数at_func1at_func2

如果我把下面的at_func1(self)里面的 self 删掉,确实可以实现,不创建实例的情况下完成上面的需求

但是这样导致的另一个结果就是,我如果自己在其他地方新建一个 a=A()的实例,那么执行a.at_func1()就会报错,有没有办法能权衡这两点?

满足需求的情况下,

1.在元类里面无需创建 obj

2.在元类外面A().at_func1()能正常运行

2463 次点击
所在节点    Python
9 条回复
NullPoint
2019-06-29 18:32:40 +08:00
把两个方法变成静态方法就可以了吧,都不需要元类
BingoXuan
2019-06-29 18:56:34 +08:00
你是想把一个多个符合特定格式的实例方法加到一个类里面吗?

那直接构建一个类,其中一个类方法是通过 getattr 获取实例的方法,并加入类属性里面。如果实例方法的 self 要指定,那么用 function tool 的 partial 指定一个对象作为 self 传进去就好了。
Dilras
2019-06-29 20:15:34 +08:00
不需要创建 obj,atts 字典里面本身就存着函数对象,所以直接 append 就好了
```python
class Metaclass(type):

func_list = []

def __new__(mcs, name, base, attrs):
for k,v in attrs.items():
if k.startswith('at'):
mcs.func_list.append(v)
return type.__new__(mcs, name, base, attrs)
```
caneman
2019-06-29 21:07:14 +08:00
@NullPoint 方法和类后续都会扩展,直接用静态方法的话,没有办法维持一个满足给定条件的全部函数集合。

@BingoXuan 我想实现的大概是这样一种场景,我创建一个元类,和若干子类,子类可能随时会扩展,但是我需要的仅仅是子类中满足特定格式命名的方法(实例方法、类方法、静态方法等都可以),我想在元类里面创建一个列表,里面是所有满足条件的方法的集合,如:func_list = [fun1, fun2, fun3],我想使这个列表中的元素(即每一个函数)可以直接执行,如:fun1()、func2(),而不需要其余多余的操作。

@Dilras attrs 中 v 确实存的是函数的引用,但是是无法通过直接在其后面加括号运行的( func1())这种方法运行的,会提示缺少(self)参数,如果改成将 func1 改成类方法就会提示缺少(cls),如果什么都不填( def func1():return 'xx'),那么我在其他地方实例化这个类,A().func1()就无法运行了...,( A.func1()也不行)

我的问题实质可能是,能否把一个类通过某些手段转换成一个变量+函数的集合,不具有其他多余的东西(类似于闭包的感觉),同时在其他情况下能够保留类本身的特性。
lrxiao
2019-06-29 21:24:25 +08:00
没搞懂 那不同的 self 怎么区分? 除非你这个类是单例
BingoXuan
2019-06-30 18:13:52 +08:00
@caneman
“”“我的问题实质可能是,能否把一个类通过某些手段转换成一个变量+函数的集合,不具有其他多余的东西(类似于闭包的感觉),同时在其他情况下能够保留类本身的特性。”“”

这个设计有点不明所以,连你自己都不确定自己要什么,能实现什么功能,方便解决什么问题。总感觉你是实现类似 go 的 duck typing 风格的类。但我觉得如果一个类解耦成函数和变量的集合。应该通过类的多继承实现 mixin。通过多继承数据类和方法类,最终自由的组合任意数据类型和函数,实现高度解耦。甚至代理模式也可以。通过 override 代理类的__getattribute__方法,代理特定数据对象和方法对象的所有属性和方法。你甚至可以在__getattribute__这个方法里面通过访问 globals 获取当前所有定义的类的方法或者函数,再把数据扔进去执行。但这种 trick 我觉得都是苦劳。

普通的继承也可以把所有子类的对象捆绑到最终的父类对象上。在子类实例化时候,把子类的所有符合条件方法绑定到父类属性里面(下面代码我默认全部公开的实例方法绑定),你可能需要修改一下判断条件。对静态函数,方法,类函数等,可能需要用 functiontool 这个库进行修改参数,比如去掉 self,指定默认 self 等等。

class A:
subclass_list = []
def __init__(self):
for attr in dir(self):
if not attr.startswith('__') and isinstance(getattr(self,attr),MethodType):
self.__class__.subclass_list.append(getattr(self,attr))
frostming
2019-07-01 11:26:35 +08:00
你为什么可以用一个 dummy 的实例去调用方法呢?那我要指定实例怎么办?
如果调用方法跟 self 是什么没关系的话,你用 staticmethod 不就不用这个 obj 了吗?

你的想法和你的解决方法互相矛盾,叫人如何回答
caneman
2019-07-01 13:23:37 +08:00
@BingoXuan 谢谢。

我重新整理了下我的需求,
1. 有很多功能模块,每个功能模块里面有若干方法,在主线程里面,我只需要用到这些模块的某些特定方法,不需要实例化这些功能模块,仅仅调用方法。(这种情况下,每个模块类似于变量+方法的集合)。

2.在某些子线程里面,可能会实例化某一个功能模块(这种情况下,模块就是一般的类)。

3.另一个需要满足的是,我需要有一个功能模块中心,它能够自动感知功能模块的增删,以及去所有功能模块里面收集并维持一个满足条件的方法列表。

这样做的目的在于,我的主线程中的业务逻辑只需要关注功能模块中心即可,增删功能模块,不需要对其它地方做额外的变动。

因为绝大多数的情况下,都是 1 在发挥作用,极少数情况下才会用到 2,所以我不希望在 1 的时候需要实例化每个功能模块,因为实例化带来的附加属性我是完全用不到,虽然也占不到几个内存。当时之所以采用元类的方式来解决,也是出于这一点,不需要实例化子类的情况下,便能完成子类中属性方法的获取。


@frostming

采用静态方法的化,我如何才能获得这个静态方法的引用呢?即 func1 这个变量如何才能指向 A.func1(),以便我 func1()便能 A.func1(),__getattr__不是实例方法吗,元类里面 attrs 里面存的函数引用,对于静态方法也是不能直接执行。
for k,v in attrs.items(): v()这样是不行的


现在的情况,我基本放弃 1 的需求了,实例就实例把,也占不了太多东西,这样的化解决方案就很多了,

@BingoXuan @frostming 的方法都可以实现我的需求了。
frostming
2019-07-01 14:33:27 +08:00
@caneman 改成 staticmethod 以后你肯定不需要那个无用的 obj 了啊,用类本身就可以

func = getattr(cls, name)

1. 这类东西放在__init__里面而不是__new__里面
2. 用 getattr(cls, name),不要直接用 cls.__getattribute__

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/578551

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX