深度解密为什么实例在调用方法时会将自身传给 self 参数(一)

简介: 深度解密为什么实例在调用方法时会将自身传给 self 参数

楔子




我们都知道实例在调用方法时,会自动将自身传给 self 参数,那么你有没有想过这背后的原理是怎么样的呢?

本篇文章就来探讨一下这背后的原理。


属性引用




在调用方法之前肯定要先获取,而实例在获取属性(或方法)的时候,需要通过属性操作符 . 的方式,我们看一下这个过程是怎样的?

class Girl:
    
    def __init__(self):
        self.name = "satori"
        self.age = 16
        
    def get_info(self):
        return f"name: {self.name}, age: {self.age}"
    
g = Girl()
# 获取 name 属性
name = g.name
# 获取 get_info 方法并调用
g.get_info()

想要了解背后都发生了什么,最直接的途径就是查看字节码,这里只看模块对应的字节码。

# 偏移量为 0 ~ 12 的字节码是用来创建类的
 # 这部分以后再聊,总之这几条字节码执行完毕之后
 # Girl 这个类就已经创建好了
 0 LOAD_BUILD_CLASS
 2 LOAD_CONST               0 (<code object Girl at 0x00...>)
 4 LOAD_CONST               1 ('Girl')
 6 MAKE_FUNCTION            0
 8 LOAD_CONST               1 ('Girl')
10 CALL_FUNCTION            2
12 STORE_NAME               0 (Girl)
 
 # g = Girl() 对应的字节码
 # 将类型对象 Girl 压入运行时栈
14 LOAD_NAME                0 (Girl)
 # 将 Girl 从运行时栈弹出,调用生成实例对象
16 CALL_FUNCTION            0
 # 将实例对象用变量 g 保存起来
18 STORE_NAME               1 (g)
 
 # name = g.name 对应的字节码
 # 加载变量 g
20 LOAD_NAME                1 (g)
 # 获取 g.name,加载属性用的是 LOAD_ATTR
22 LOAD_ATTR                2 (name)
 # 将结果交给变量 name 保存
24 STORE_NAME               2 (name)
 
 # g.get_info() 对应的字节码
 # 加载变量 g
26 LOAD_NAME                1 (g)
 # 获取方法 g.get_info,加载方法用的是 LOAD_METHOD
28 LOAD_METHOD              3 (get_info)
 # 调用方法,注意:调用方法对应的指令是 CALL_METHOD
 # 而调用函数对应的指令是 CALL_FUNCTION
30 CALL_METHOD              0
 # 从栈顶弹出返回值
32 POP_TOP
 # return None
34 LOAD_CONST               2 (None)
36 RETURN_VALUE

除了 LOAD_METHOD 和 LOAD_ATTR,其它的指令基本都见过了,也很简单,因此下面重点分析这两条指令。

case TARGET(LOAD_METHOD): {
    //从符号表中获取符号,因为是 g.get_info
    //那么这个 name 就指向字符串对象 "get_info"
    PyObject *name = GETITEM(names, oparg);
    //从栈顶获取元素obj,显然这个 obj 就是代码中的实例对象 g
    PyObject *obj = TOP();
    //meth 是一个 PyObject * 指针
    //显然它要指向一个方法
    PyObject *meth = NULL;
    
    //这里是获取和 "get_info" 绑定的方法,然后让meth指向它
    //具体做法就是调用 _PyObject_GetMethod,传入二级指针&meth
    //然后让 meth 存储的地址变成指向具体方法的地址
    int meth_found = _PyObject_GetMethod(obj, name, &meth);
    
    //如果 meth == NULL,raise AttributeError
    if (meth == NULL) {
        /* Most likely attribute wasn't found. */
        goto error;
    }
    
    //注意:无论是 Girl.get_info、还是 g.get_info
    //对应的指令都是 LOAD_METHOD
    //类去调用的话,说明得到的是一个未绑定的方法,说白了就等价于函数
    //实例去调用的话,会得到一个绑定的方法,相当于对函数进行了封装
    //关于绑定和未绑定我们后面会详细介绍
    if (meth_found) {
        //如果 meth_found 为 1
        //说明 meth 是一个绑定的方法,obj 就是 self
        //将 meth 设置为栈顶元素,然后再将 obj 压入栈中
        SET_TOP(meth);
        PUSH(obj);  // self
    }
    else {
        //否则说明 meth 是一个未绑定的方法
        //那么将栈顶元素设置为 NULL,然后将 meth 压入栈中
        SET_TOP(NULL);
        Py_DECREF(obj);
        PUSH(meth);
    }
    DISPATCH();
}

获取方法是 LOAD_METHOD 指令 ,获取属性则是 LOAD_ATTR 指令,来看一下。

case TARGET(LOAD_ATTR): {
    //可以看到和 LOAD_METHOD 本质上是类似的,但更简单一些
    //name 依旧是符号,这里指向字符串对象 "name"
    PyObject *name = GETITEM(names, oparg);
    //从栈顶获取变量 g
    PyObject *owner = TOP();
    //res 显然就是获取属性返回的结果了,即 g.name
    //通过 PyObject_GetAttr 进行获取
    PyObject *res = PyObject_GetAttr(owner, name);
    Py_DECREF(owner);
    //设置到栈顶
    SET_TOP(res);
    if (res == NULL)
        goto error;
    DISPATCH();
}

所以这两个指令本身是很简单的,而核心在 PyObject_GetAttr 和  _PyObject_GetMethod 上面,前者用于获取属性、后者用于获取方法。

来看一下 PyObject_GetAttr 具体都做了什么事情。

//Objects/object.c
PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{   
    // v: 对象
    // name: 属性名
    
    // 获取实例对象 v 对应的类型对象
    PyTypeObject *tp = Py_TYPE(v);
    
    // name 必须是一个字符串
    if (!PyUnicode_Check(name)) {
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     name->ob_type->tp_name);
        return NULL;
    }
    // 通过类型对象的 tp_getattro 成员获取实例对应的属性
    if (tp->tp_getattro != NULL)
        return (*tp->tp_getattro)(v, name);
    
    //tp_getattro 和 tp_getattr 功能一样,但前者可以支持中文
    if (tp->tp_getattr != NULL) {
        const char *name_str = PyUnicode_AsUTF8(name);
        if (name_str == NULL)
            return NULL;
        return (*tp->tp_getattr)(v, (char *)name_str);
    }
    
    //属性不存在,抛出异常
    PyErr_Format(PyExc_AttributeError,
                 "'%.50s' object has no attribute '%U'",
                 tp->tp_name, name);
    return NULL;
}

PyTypeObject 里面定义了两个与属性访问相关的操作:tp_getattro 和 tp_getattr。其中前者是优先选择的属性访问动作,而后者已不推荐使用。

这两者的区别在 PyObject_GetAttr 中已经显示很清楚了,主要是在属性名的使用上。

  • tp_getattro 所使用的属性名是一个 PyUnicodeObject *;
  • 而 tp_getattr 所使用的属性名是一个 char *。
       

如果这两个成员同时被定义,那么优先使用 tp_getattro。

问题来了,自定义类对象的 tp_getattro 对应哪一个 C 函数呢?显然我们要去找 object。

object 在底层对应 PyBaseObject_Type,它的 tp_getattro 为 PyObject_GenericGetAttr,因此虚拟机在创建 Girl 这个类时,也会将此操作继承下来。

//Objects/object.c
PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
    return _PyObject_GenericGetAttrWithDict(
                 obj, name, NULL, 0);
}
PyObject *
_PyObject_GenericGetAttrWithDict(
                PyObject *obj, PyObject *name,
                PyObject *dict, int suppress)
{
    //拿到 obj 的类型对象
    //对于我们当前的例子来说,显然是 class Girl
    PyTypeObject *tp = Py_TYPE(obj);
    //描述符
    PyObject *descr = NULL;
    //返回值
    PyObject *res = NULL;
    //描述符的 __get__ 
    descrgetfunc f;
    Py_ssize_t dictoffset;
    PyObject **dictptr;
    
    //name 必须是字符串
    if (!PyUnicode_Check(name)){
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     name->ob_type->tp_name);
        return NULL;
    }
    Py_INCREF(name);
    
    //...
    
    //从 mro 列表中获取属性对应的值,并检测是否为描述符
    //如果属性不存在、或者存在但对应的值不是描述符,则返回 NULL
    descr = _PyType_Lookup(tp, name);
    f = NULL;
    if (descr != NULL) {
        Py_INCREF(descr);
        //如果 descr 不为 NULL,说明该属性被代理了
        //descr 是描述符,f 就是它的 __get__
        //f = descr.__class__.__get__ 
        f = descr->ob_type->tp_descr_get;
        //补充:类型对象在底层都是 PyTypeObject
        //它有两个成员:tp_descr_get 和 tp_descr_set
        //tp_descr_get 对应 Python 中的 __get__
        //tp_descr_set 对应 Python 中的 __set__ 
        //如果 f 不为 NULL,并且 descr 是数据描述符
        if (f != NULL && PyDescr_IsData(descr)) {
            //那么直接调用描述符的 __get__ 方法,返回结果
            res = f(descr, obj, (PyObject *)obj->ob_type);
            //...
        }
    }
    
    //走到这说明要获取的属性没有被代理,或者说代理它的是非数据描述符
    //那么实例优先从自身的属性字典中获取,当然还有一种情况
    //就是属性被数据描述符代理,但是该数据描述符没有 __get__
    //那么仍会优先从实例对象自身的 __dict__ 中寻找属性
    
    if (dict == NULL) {
        // ...
    }
    //dict 不为 NULL,从字典中获取
    if (dict != NULL) {
        // ...
    }
    
    //如果程序走到这里,说明什么呢?
    //显然意味着实例的属性字典里面没有要获取的属性
    //但如果下面的 f != NULL 成立,说明属性被代理了
    //并且代理属性的描述符是非数据描述符,它的优先级低于实例
    //所以实例会先到自身的属性字典中查找,找不到再去执行描述符的 __get__
    if (f != NULL) {
        //第一个参数是描述符本身,也就是 __get__ 里面的 self
        //第二个参数是实例对象,也就是 __get__ 里面的 instance
        //第三个参数是类对象,也就是 __get__ 里面的 owner
        res = f(descr, obj, (PyObject *)Py_TYPE(obj));
        // ...
        goto done;
    }
    
    //程序能走到这里,说明属性字典里面没有要找的属性
    //并且也没有执行描述符的 __get__
    //但如果 describe 还不为 NULL,这说明什么呢?
    //显然该属性仍被描述符代理了,只是这个描述符没有 __get__
    //如果是这种情况,那么会返回描述符本身
    if (descr != NULL) {
        res = descr;
        descr = NULL;
        goto done;
    }
    
    //找不到,就报错
    if (!suppress) {
        PyErr_Format(PyExc_AttributeError,
                     "'%.50s' object has no attribute '%U'",
                     tp->tp_name, name);
    }
  done:
    Py_XDECREF(descr);
    Py_DECREF(name);
    return res;
}

代码有点长,但是逻辑不难理解,用一张流程图总结一下。

fa671eade392f9ab09ee88f0925ea9d6.png

其实难点在于里面涉及了很多描述符相关的知识,这里为了方便理解,我们对描述符的内容做一个补充。

如果一个类定义了 __get__ 或 __set__,那么它的实例对象就被称为描述符。并且定义了 __set__ 被称为数据描述符,只有 __get__ 而没有 __set__ 则被称为非数据描述符。

1)如果实例的某个属性被数据描述符代理,那么实例在设置属性和访问属性时,会执行描述符的 __set__ 和 __get__,举个例子。

class Descr:
    def __get__(self, instance, owner):
        print("__get__")
        print(instance)
        print(owner)
    def __set__(self, instance, value):
        print("__set__")
        print(instance)
        print(value)
class Girl:
    # name 属性被数据描述符代理了
    name = Descr()
g = Girl()
# 设置属性时,会执行描述符的 __set__
# 参数 instance 就是实例本身,value 则是赋的值
g.name = "古明地觉"
"""
__set__
<__main__.Girl object at 0x7fcf180d9d60>
古明地觉
"""
# 获取属性时,会执行描述符的 __get__
# 参数 instance 也是实例本身,owner 则是实例对应的类
g.name
"""
__get__
<__main__.Girl object at 0x7f9ca8269d60>
<class '__main__.Girl'>
"""
# 在获取属性时,不管实例有没有 name 这个属性
# 只要被数据描述符代理,获取的时候都无条件地执行描述符的 __get__

2)如果实例的某个属性被非数据描述符代理,那么实例在设置属性时,会设置到自己的属性字典里面。但在获取属性时,如果自身存在该属性,那么直接获取,不存在则执行描述符的 __get__;

class Descr:
    def __get__(self, instance, owner):
        print("__get__")
        print(instance)
        print(owner)
class Girl:
    # name 属性被非数据描述符代理
    name = Descr()
g = Girl()
# 由于不存在 name 属性,所以依旧执行描述符的 __get__
g.name
"""
__get__
<__main__.Girl object at 0x7fe9d80e1d60>
<class '__main__.Girl'>
"""
# 此时会将 name 属性设置到属性字典中
# 因为代理它的是非数据描述符
g.name = "古明地觉"
# 由于是非数据描述符,那么当属性字典中存在时
# 就不会再走描述符的 __get__ 了
print(g.name)  # 古明地觉

3)如果代理属性的描述符只有 __set__,没有 __get__,那么设置属性时依旧执行描述符的 __set__。但获取属性时,如果实例自身存在该属性,那么就从实例自身获取,如果不存在,则返回描述符本身。

class Descr:
    def __set__(self, instance, value):
        print("__set__")
        print(instance)
        print(value)
class Girl:
    # name 被数据描述符代理
    name = Descr()
g = Girl()
# 设置 name 属性,依旧执行描述符的 __set__
g.name = "古明地恋"
"""
__set__
<__main__.Girl object at 0x7fd8181e9d60>
古明地恋
"""
# 注意:我们在 __set__ 中只是做了几行打印
# 没有修改实例本身,所以实例对象 g 中不存在属性 name
# 而代理 name 属性的是数据描述符,因此访问的时候本应执行 __get__
# 但描述符没有 __get__,而且实例自身也不存在 name 属性
# 因此,这种情况下会返回描述符本身,因为代理 name 的是一个描述符
print(g.name)
"""
<__main__.Descr object at 0x7fd808077940>
"""
# 但如果实例有 name 属性,那么会从实例自身查找
# 虽然数据描述符的优先级大于实例,访问属性时应该执行 __get__
# 但问题是描述符没有 __get__,此时只能从实例自身查找了
# 如果实例也没有,则直接返回描述符本身,就像上面那样
g.__dict__["name"] = "古明地恋"
# 需要通过属性字典的方式来设置,否则会执行 __set__
print(g.name)
"""
古明地恋
"""

以上是属性被描述符代理的情况,如果没有被代理,那么访问和设置属性就会直接作用在实例上面。设置属性等价于往实例的属性字典中添加一个键值对,获取属性则等价于从字典中拿到 key 对应的 value。

所以 PyObject_GenericGetAttr 函数的代码看似很长,但它的逻辑是很简单的,说白了它就是Python 里面访问属性操作所对应的 C 实现,因为 Python 解释器就是 C 写出来的。

获取方法则通过 _PyObject_GetMethod,过程与之类似,这里就不再看了。


函数变身




了解完属性引用之后,终于到我们的主题了,那就是 self 参数。

class Girl:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def get_info(self):
        return f"name = {self.name}, age = {self.age}"
g = Girl("satori", 16)
res = g.get_info()
print(res)  # name = satori, age = 16

我们在调用 g.get_info 的时候,并没有给 self 传递参数,那么 self 到底是不是一个真正有效的参数呢?还是说它仅仅只是一个语法意义上的占位符而已?

不用想,self 肯定是货真价实的参数,只不过自动帮你传递了。根据使用 Python 的经验,我们知道第一个参数就是实例本身。那么这是怎么实现的呢?想要弄清这一点,还是要从字节码入手。而调用方法的字节码是 CALL_METHOD,那么玄机就隐藏在这里面。

1863605990cbbed1063c38c9688e4e76.png

调用时的指令参数是 0,表示不需要传递参数。注意:这里说的不需要传递参数,指的是不需要我们手动传递。

case TARGET(CALL_METHOD): {
    PyObject **sp, *res, *meth;
    //栈指针,指向运行时栈的栈顶
    sp = stack_pointer;
    meth = PEEK(oparg + 2);
    //meth 为 NULL,说明是函数
    //我们传递的参数从 orarg 开始
    if (meth == NULL) {
        res = call_function(tstate, &sp, oparg, NULL);
        stack_pointer = sp;
        (void)POP(); /* POP the NULL. */
    }
    //否则是方法,我们传递的参数从 oparg + 1开始
    //而第一个参数显然要留给 self
    else {
        res = call_function(tstate, &sp, oparg + 1, NULL);
        stack_pointer = sp;
    }
    PUSH(res);
    if (res == NULL)
        goto error;
    DISPATCH();
}

为了对比,我们再把 CALL_FUNCTION 指令的源码贴出来。

case TARGET(CALL_FUNCTION): {
    PREDICTED(CALL_FUNCTION);
    PyObject **sp, *res;
    sp = stack_pointer;
    res = call_function(tstate, &sp, oparg, NULL);
    stack_pointer = sp;
    PUSH(res);
    if (res == NULL) {
        goto error;
    }
    DISPATCH();
}

通过对比发现了端倪,这两者都调用了call_function,但是传递的参数不一样。

如果是类调用,那么这两个指令是等价的;但如果是实例调用,CALL_METHOD 的第三个参数是 oparg + 1,CALL_FUNCTION 则是 oparg。

但是这还不足以支持我们找出问题所在,如果你仔细看一下函数的类型对象 PyFunction_Type,会发现里面隐藏着一个秘密。

PyTypeObject PyFunction_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "function",
    sizeof(PyFunctionObject),
    //...
    //...
    
    //注意注意注意,看下面这行
    func_descr_get,                          /* tp_descr_get */
    0,                                       /* tp_descr_set */
    offsetof(PyFunctionObject, func_dict),   /* tp_dictoffset */
    0,                                       /* tp_init */
    0,                                       /* tp_alloc */
    func_new,                                /* tp_new */
};

我们说 tp_descr_get 对应 __get__,而它被设置成了 func_descr_get,这意味着函数是一个描述符,因为它的类型对象实现了 __get__。

def func():
    pass
print(func.__get__)
"""
<method-wrapper '__get__' of function object at 0x...>
"""

同理,实例对象 g 在调用 get_info 之前,肯定要先获取 get_info。而在获取的时候,显然会执行 get_info 的 __get__。也就是说,g.get_info 会得到什么,取决于 get_info 的 __get__ 会返回什么。

那么函数的 __get__ 会返回什么呢?显然这要去 func_descr_get 函数中一探究竟。

// funcobject.c
static PyObject *
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{  
    //如果是类获取函数:那么obj为NULL,type为类对象本身
    //如果是实例获取函数:那么obj为实例,type仍是类对象本身
    
    //如果obj为空,说明是类获取
    //那么直接返回func本身, 也就是原来的函数
    if (obj == Py_None || obj == NULL) {
        Py_INCREF(func);
        return func;
    }
    //如果是实例对象,那么调用 PyMethod_New 
    //将函数和实例绑定在一起,得到一个 PyMethodObject 对象 
    return PyMethod_New(func, obj);
}

函数对应的结构体是 PyFunctionObject,那么 PyMethodObject 是啥应该不需要我说了,显然就是方法对应的结构体。所以类里面的定义的就是单纯的函数,通过类去调用的话,和调用一个普通函数并无区别。

但是实例调用就不一样了,实例在拿到类的成员函数时,会先调用 PyMethod_New 将函数包装成方法,然后再对方法进行调用。

class Girl:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def get_info(self):
        return f"name = {self.name}, age = {self.age}"
g = Girl("satori", 16)
print(Girl.get_info.__class__)
print(g.get_info.__class__)
"""
<class 'function'>
<class 'method'>
"""

在获取 get_info 时,会发现它被描述符代理了,而描述符就是成员函数本身。因为类型对象 PyFunction_Type 实现了 tp_descr_get,__get__,所以它的实例对象(函数)本质上就是个描述符。

因此无论是类还是实例,在调用时都会执行 func_descr_get。如果是类调用,那么实例 obj 为空,于是会将成员函数直接返回,因此类调用的就是函数本身。

如果是实例调用,则执行 PyMethod_New,将 PyFunctionObject 包装成 PyMethodObject,然后调用。因此,实例调用的是方法。

那么问题来了,方法在底层长什么样呢?可以肯定的是,方法也是一个对象,一个 PyObject。

//classobject.h
typedef struct {
    PyObject_HEAD
    //可调用的PyFunctionObject对象
    PyObject *im_func;  
    //self参数,instance对象
    PyObject *im_self;   
    //弱引用列表,不做深入讨论
    PyObject *im_weakreflist;
    //速度更快的矢量调用
    //因为方法和函数一样,肯定是要被调用的
    //所以它们都自己实现了一套调用方式:vectorcallfunc
    //而没有走类型对象的 tp_call
    vectorcallfunc vectorcall;
} PyMethodObject;

所以方法就是对函数的一个封装,我们用 Python 举例说明:

class Girl:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def get_info(self):
        return f"name = {self.name}, age = {self.age}"
g = Girl("satori", 16)
# 方法是对函数的封装
# 只不过里面不仅仅有函数,还有实例
method = g.get_info
# 拿到的是实例本身
print(method.__self__ is g)  # True
# 拿到是成员函数,也就是 Girl.get_info
print(method.__func__ is Girl.get_info)  # True
print(
    method()
    == 
    Girl.get_info(g)
    ==
    method.__func__(method.__self__)
)  # True

而方法是在 PyMethod_New 中创建的,再来看看这个函数。

//classobjet.c
PyObject *
PyMethod_New(PyObject *func, PyObject *self)
{   
    PyMethodObject *im; 
    if (self == NULL) {
        PyErr_BadInternalCall();
        return NULL;
    }
    im = free_list;
    //缓存池
    if (im != NULL) {
        free_list = (PyMethodObject *)(im->im_self);
        (void)PyObject_INIT(im, &PyMethod_Type);
        numfree--;
    }
    //缓冲池如果空了,直接创建PyMethodObject对象
    else {
        //可以看到方法的类型在底层是 &PyMethod_Type
        im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);
        if (im == NULL)
            return NULL;
    }
    im->im_weakreflist = NULL;
    Py_INCREF(func);
    //im_func指向PyFunctionObject对象
    im->im_func = func; 
    Py_XINCREF(self);
    //im_self指向实例对象
    im->im_self = self;
    //会通过method_vectorcall来对方法进行调用
    im->vectorcall = method_vectorcall;
    //被 GC 跟踪
    _PyObject_GC_TRACK(im);
    return (PyObject *)im;
}

在PyMethod_New中,分别将im_func,im_self 设置为函数、实例。因此通过 PyMethod_New 将函数、实例结合在一起,得到的 PyMethodObject 就是我们说的方法。并且我们还看到了 free_list,说明方法也使用了缓存池。

所以不管是类还是实例,获取成员函数时都会走描述符的 func_descr_get,然后在里面会判断是类获取还是实例获取。如果是类获取,会直接返回函数本身;如果是实例获取,则通过 PyMethod_New 将函数和实例绑定起来得到方法,这个过程称为成员函数的绑定

ab7e6b10009774cc9d85f83dd20fb297.png

当然啦,调用方法本质上还是调用方法里面的 im_func,也就是函数。只不过会处理自动传参的逻辑,将内部的 im_self(实例)和我们传递的参数组合起来(如果没有传参,那么只有一个 im_self),然后整体传递给 im_func。

所以为什么实例调用方法的时候会自动传递第一个参数,此刻算是真相大白了。当然啦,以上只能说从概念上理解了,但是源码还没有看,下面就来看看具体的实现细节。


接下篇深度解密为什么实例在调用方法时会将自身传给 self 参数(二):https://developer.aliyun.com/article/new?spm=a2c6h.13046898.J_eBhO-wcawiLJRkGqHmozR.90.110f6ffaddNUt4#/


相关文章
|
21天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
17天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2563 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
15天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
13天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
17天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1556 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
19天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
826 14
|
14天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
621 7
|
7天前
|
Docker 容器
Docker操作 (五)
Docker操作 (五)
170 69
|
7天前
|
Docker 容器
Docker操作 (三)
Docker操作 (三)
167 69
|
19天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
629 52
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界