Comme la fin de l'année approche cette année, j'utiliserai [Python C / API] en vain. Inversion du but et des moyens. Cette fois, je vise Awaitable, quelque chose comme Coroutine obtenu en exécutant le spam de la fonction coroutine suivante.
import asyncio
import sys
async def spam():
print('do something')
ret = await asyncio.sleep(1, 'RETURN VALUE')
return ret.lower()
async def main():
ret = await spam()
print(ret)
if __name__ == '__main__':
if sys.version_info < (3, 7):
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
else:
asyncio.run(main())
Imprimez quelque chose, attendez le sommeil pour obtenir le résultat et appelez sa méthode inférieure pour renvoyer le résultat. 3 opération. Les méthodes send, throw et close que Coroutine et Generator n'ont pas Awaitable ne seront pas reproduites cette fois.
Avant d'imiter le code écrit comme async def, attendez. Passons en revue ce que c'est en premier lieu. Le texte lorsque ces fonctionnalités de langage ont été proposées en plus à Python est PEP 492. Après avoir lu ceci, je me suis souvenu négligemment qu'attendre était en fait un rendement. Je pense que la raison pour laquelle cela a été préparé était la suivante. «La spécification Generator a été conçue à l'origine dans le but d'être utilisée comme Coroutine. La capacité de pause et de reprise était l'itérateur lui-même, et la capacité d'envoyer des valeurs PEP 342. Je l'ai ajouté. Et je me débrouillais bien avant Python 3.4. Cependant, des problèmes dus à l'indiscernabilité de Generator et Iterator et Coroutine ont commencé à être signalés, de sorte que la spécification du langage de Python 3.5 peut être distinguée. Créez une autre méthode appelée \ _ \ _ await \ _ \ _ au lieu de \ _ \ _iter \ _ \ _. Ne détournez pas l'instruction yield de la fonction Generator, mais au lieu de la fonction Coroutine Une autre instruction d'attente à utiliser à l'intérieur est nouvellement ajoutée pour la distinguer. " Il existe différents noms \ _ \ _ wait \ _ \ _ et \ _ \ _ iter \ _ \ _, mais le comportement n'est pas différent. Tp_iter et tp_as_sync. Am_await sont également fournis sur la structure PyTypeObject de CPython et sont traités comme des membres séparés.
Maintenant que j'ai une meilleure compréhension de Awaitable. Notez immédiatement le spam de la fonction coroutine avec une instruction de classe. La différence avec Iteratable est qu'il a \ _ \ _ wait \ _ \ _ au lieu de \ _ \ _ iter \ _ \ _. Cependant, la seule différence est qu'il renvoie Iterator à partir d'ici.
class Spam:
def __await__(self):
... # TODO:Quel Itarator peut être retourné pour imiter la coroutine de spam?
Continuez le démontage. L'itérateur se renvoie avec \ _ \ _ iter \ _ \ _, reprend le traitement avec \ _ \ _next \ _ \ _, crée la valeur suivante et renvoie la valeur lors de l'interruption du traitement.
class _Spam:
def __iter__(self):
return self
def __next__(self):
... # TODO:Comment imiter la coroutine de spam?
class Spam:
def __await__(self):
return _Spam()
Maintenant, de quoi avons-nous besoin pour implémenter "Awaitable similaire à Coroutine obtenu en exécutant la fonction coroutine spam"? C'est le maintien de l'état de la quantité des trois actions «imprimer quelque chose, attendre le sommeil pour obtenir le résultat, appeler la méthode inférieure de celui-ci et renvoyer le résultat». Donne à l'objet l'attribut \ _state. Cette fois, j'ai utilisé 0, 1, 2 int pour le moment, mais si vous voulez l'écrire proprement, il vaut mieux utiliser Enum. Ensuite, implémentez \ _ \ _next \ _ \ _ qui traite par état. Le problème ici est d'attendre ou de céder. Il délègue le traitement à un autre Iterator que celui de yield from. Se répète de manière itérative jusqu'à ce qu'un autre itérateur s'arrête et la valeur obtenue est renvoyée telle quelle. Vous devez garder un autre Iterator en cours d'exécution pour implémenter l'équivalent. Pour cette raison, nous avons ajouté l'attribut \ _it. Lorsque cet Iterator s'arrête, une exception StopIteration est envoyée, regardez donc l'attribut value. Cela correspond à la valeur de l'attente, du rendement de l'expression dans l'instruction return de la fonction coroutine ou de la fonction générateur. Faites attention à la position de la méthode inférieure. Inversement, lors du renvoi d'une valeur, attribuez une valeur à l'exception StopIteration. Iterator une fois arrêté a Restriction qu'il doit continuer à renvoyer l'exception StopIteration, alors prenez des mesures contre cela. .. \ _ \ _ Next \ _ \ _ se termine par augmenter StopIteration. L'attribut de conservation d'état doit commencer par \ _ pour indiquer que vous ne souhaitez pas qu'il soit réécrit en externe. Cette \ _initialisation de l'état est effectuée dans \ _ \ _ nouveau \ _ \ _ pour le rendre résistant à plusieurs appels à \ _ \ _ init \ _ \ _.
class _Spam:
def __new__(cls):
obj = super().__new__(cls)
obj._state = 0
obj._it = None
return obj
def __iter__(self):
return self
def __next__(self):
if self._state == 0:
print('do something')
self._it = asyncio.sleep(1, 'RETURN VALUE').__await__()
self._state = 1
if self._state == 1:
try:
v = next(self._it)
except StopIteration as e:
ret = e.value
self._it = None
self._state = 2
raise StopIteration(ret.lower())
else:
return v
raise StopIteration
class Spam:
def __await__(self):
return _Spam()
Parce que le démontage est terminé. Écrivez ceci dans Python C / API. Langage C d'ici. Pour créer une classe, définissez Ecrire directement la structure PyTypeObject ou PyType_Spec et appelez PyType_FromSpec. Cette fois, dans le sens de l'utilisation de PyType_FromSpec. Commencez par Spam, qui est plus simple que \ _Spam. Pour créer \ _ \ _await \ _ \ _, enregistrez la fonction dans tp_as_sync. Am_await. Étant donné que cette méthode doit renvoyer une instance de \ _Spam, nous nous assurerons que les attributs de la classe Spam ont la classe \ _Spam. Le traitement d'ajout d'attribut de classe est effectué après la création de classe avec PyType_FromSpec dans le traitement d'initialisation du module enregistré en tant que Py_mod_exec.
typedef struct {
PyObject_HEAD
} SpamObject;
static PyObject *
advent2019_Spam_await(SpamObject *self)
{
PyObject *_Spam_Type = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "_Spam");
if (_Spam_Type == NULL) { return NULL; }
PyObject *it = PyObject_CallFunction(_Spam_Type, "");
Py_DECREF(_Spam_Type);
return it;
}
static PyType_Slot advent2019_Spam_slots[] = {
{Py_am_await, (unaryfunc)advent2019_Spam_await},
{0, 0},
};
static PyType_Spec advent2019_Spam_spec = {
.name = "advent2019.Spam",
.basicsize = sizeof(SpamObject),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.slots = advent2019_Spam_slots,
};
static int advent2019_exec(PyObject *module) {
int ret = -1;
PyObject *_Spam_Type = NULL; // TODO
PyObject *Spam_Type = NULL;
if (!(Spam_Type = PyType_FromSpec(&advent2019_Spam_spec))) { goto cleanup; }
// Spam._Spam = _Spam
if (PyObject_SetAttrString(Spam_Type, "_Spam", _Spam_Type)) { goto cleanup; }
if (PyObject_SetAttrString(module, "Spam", Spam_Type)) { goto cleanup; }
if (PyObject_SetAttrString(module, "_Spam", _Spam_Type)) { goto cleanup; }
ret = 0;
cleanup:
Py_XDECREF(_Spam_Type);
Py_XDECREF(Spam_Type);
if (ret) { Py_XDECREF(module); }
return ret;
}
D'ailleurs, c'est l'implémentation de Iterator \ _Spam qui a été reportée. D'abord à partir de la structure \ _SpamObject et \ _ \ _ nouveau \ _ \ _. Préparez un état de valeur pour contenir l'état et pour qu'il contienne l'itérateur asyncio.sleep (). \ _ \ _ Await \ _ \ _. Comme il contiendra d'autres PyObject *, assurez-vous d'allouer de la mémoire avec PyObject_GC_New correspondant au mécanisme de garbage collection. De plus, après l'initialisation, appelez PyObject_GC_Track et enregistrez-vous dans le ramasse-miettes.
typedef struct {
PyObject_HEAD
unsigned char state;
PyObject *it;
} _SpamObject;
static PyObject *
advent2019__Spam_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwlist)) {
return NULL;
}
_SpamObject *obj = PyObject_GC_New(_SpamObject, type);
if (!obj) { return NULL; }
obj->state = 0;
obj->it = NULL;
PyObject_GC_Track(obj);
return (PyObject *)obj;
}
Vous aurez besoin d'une fonction pour quand le garbage collection fonctionne. Il existe trois types: traversée, qui vous permet de récupérer l'objet que vous tenez, clear, qui détruit la référence, et dealloc, qui se détruit. La Macro Py_VISIT est utile pour écrire des traversées. PyObject_GC_UnTrack, qui est associé à PyObject_GC_Track, doit être appelé au début de dealloc.
static int
advent2019__Spam_traverse(_SpamObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->it);
return 0;
}
static int
advent2019__Spam_clear(_SpamObject *self)
{
Py_CLEAR(self->it);
return 0;
}
static void
advent2019__Spam_dealloc(_SpamObject *self)
{
PyObject_GC_UnTrack(self);
advent2019__Spam_clear(self);
PyObject_GC_Del(self);
}
\ _ \ _ Iter \ _ \ _ est facile car il se renvoie simplement. N'oubliez pas d'opérer le comptage de référence.
static PyObject *
advent2019__Spam_iter(_SpamObject *self)
{
Py_INCREF(self);
return (PyObject *)self;
}
Enfin \ _ \ _ suivant \ _ \ _. Il est nommé iternext sur Python C / API. Les fonctions intégrées print et sleep du module asyncio doivent être incluses dans les attributs de classe avec les noms \ _print et \ _sleep lors de la création de la classe, de la même manière que Spam._Spam. Après cela, le code écrit en Python est PyObject_GetAttrString, [PyObject_CallFunction](https: //docs.python .org / ja / 3 / c-api / object.html # c.PyObject_CallFunction) et d'autres sont utilisés pour le portage régulier. Vérifiez si la valeur de retour est NULL à chaque appel. Le processus de réduction du nombre de références d'objets inutiles est fastidieux. PyErr_Fetch, PyErr_GivenExceptionMatches pour le portage des instructions try /ja/3/c-api/exceptions.html#c.PyErr_GivenExceptionMatches), PyErr_Restore Je vais.
static PyObject *
advent2019__Spam_iternext(_SpamObject *self)
{
if (self->state == 0) {
// print('do something')
PyObject *printfunc = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "_print");
if (!printfunc) { return NULL; }
PyObject *ret = PyObject_CallFunction(printfunc, "s", "do something");
Py_DECREF(printfunc);
if (!ret) { return NULL; }
Py_DECREF(ret);
// self._it = asyncio.sleep(1, 'RETURN VALUE').__await__()
PyObject *sleep_cofunc = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "_sleep");
if (!sleep_cofunc) { return NULL; }
PyObject *sleep_co = PyObject_CallFunction(sleep_cofunc, "is", 1, "RETURN VALUE");
Py_DECREF(sleep_cofunc);
if (!sleep_co) { return NULL; }
if (!(Py_TYPE(sleep_co)->tp_as_async)) { Py_DECREF(sleep_co); return NULL; }
if (!(Py_TYPE(sleep_co)->tp_as_async->am_await)) { Py_DECREF(sleep_co); return NULL; }
PyObject *temp = self->it;
self->it = Py_TYPE(sleep_co)->tp_as_async->am_await(sleep_co);
Py_DECREF(sleep_co);
Py_XDECREF(temp);
if (self->it == NULL) { return NULL; }
self->state = 1;
}
if (self->state == 1) {
// next(self.it)
if (Py_TYPE(self->it)->tp_iternext == NULL) { PyErr_SetString(PyExc_TypeError, "no iternext"); return NULL; }
PyObject *ret = Py_TYPE(self->it)->tp_iternext(self->it);
if (!ret) {
// except StopIteration as e
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(type, PyExc_StopIteration)) {
Py_XDECREF(type);
Py_XDECREF(traceback);
if (!value) { PyErr_SetString(PyExc_ValueError, "no StopIteration value"); return NULL; }
// ret = e.value.lower()
PyObject *value2 = PyObject_CallMethod(value, "lower", NULL);
Py_DECREF(value);
if (!value2) { return NULL; }
// raise StopIteration(ret)
PyErr_SetObject(PyExc_StopIteration, value2);
Py_DECREF(value2);
Py_CLEAR(self->it);
self->state = 2;
} else {
// except:
// raise
PyErr_Restore(type, value, traceback);
}
}
return ret;
}
// raise StopIteration(None)
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
Maintenant que nous avons les méthodes de la classe \ _Spam, définissez PyType_Spec. Assurez-vous de définir l'indicateur Py_TPFLAGS_HAVE_GC pour indiquer que la classe doit être gérée par le garbage collection.
static PyType_Slot advent2019__Spam_slots[] = {
{Py_tp_new, advent2019__Spam_new},
{Py_tp_iter, advent2019__Spam_iter},
{Py_tp_iternext, advent2019__Spam_iternext},
{Py_tp_traverse, advent2019__Spam_traverse},
{Py_tp_clear, advent2019__Spam_clear},
{Py_tp_dealloc, advent2019__Spam_dealloc},
{0, 0},
};
static PyType_Spec advent2019__Spam_spec = {
.name = "advent2019._Spam",
.basicsize = sizeof(_SpamObject),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.slots = advent2019__Spam_slots,
};
J'ai essayé d'implémenter Awaitable avec Python C / API. Nous avons également pu collecter des liens vers des documents officiels qui fournissent les informations nécessaires pour ce faire. au fait. Est-ce utile? Le code pour cela est beaucoup plus long, et cela pourrait vous aider à mieux comprendre CPython, tout comme vous avez réalisé la commodité de la syntaxe async def et attendre ...
setup.cfg
[metadata]
name = advent2019
version = 0.0.0
[options]
python_requires = >=3.5.0
setup.py
from setuptools import Extension, setup
extensions = [Extension('advent2019', sources=['advent2019.c'])]
setup(ext_modules=extensions)
advent2019.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyObject_HEAD
unsigned char state;
PyObject *it;
} _SpamObject;
static PyObject *
advent2019__Spam_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
static char *kwlist[] = {NULL};
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwlist)) {
return NULL;
}
_SpamObject *obj = PyObject_GC_New(_SpamObject, type);
if (!obj) { return NULL; }
obj->state = 0;
obj->it = NULL;
PyObject_GC_Track(obj);
return (PyObject *)obj;
}
static PyObject *
advent2019__Spam_iter(_SpamObject *self)
{
Py_INCREF(self);
return (PyObject *)self;
}
static PyObject *
advent2019__Spam_iternext(_SpamObject *self)
{
if (self->state == 0) {
// print('do something')
PyObject *printfunc = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "_print");
if (!printfunc) { return NULL; }
PyObject *ret = PyObject_CallFunction(printfunc, "s", "do something");
Py_DECREF(printfunc);
if (!ret) { return NULL; }
Py_DECREF(ret);
// self._it = asyncio.sleep(1, 'RETURN VALUE').__await__()
PyObject *sleep_cofunc = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "_sleep");
if (!sleep_cofunc) { return NULL; }
PyObject *sleep_co = PyObject_CallFunction(sleep_cofunc, "is", 1, "RETURN VALUE");
Py_DECREF(sleep_cofunc);
if (!sleep_co) { return NULL; }
if (!(Py_TYPE(sleep_co)->tp_as_async)) { Py_DECREF(sleep_co); return NULL; }
if (!(Py_TYPE(sleep_co)->tp_as_async->am_await)) { Py_DECREF(sleep_co); return NULL; }
PyObject *temp = self->it;
self->it = Py_TYPE(sleep_co)->tp_as_async->am_await(sleep_co);
Py_DECREF(sleep_co);
Py_XDECREF(temp);
if (self->it == NULL) { return NULL; }
self->state = 1;
}
if (self->state == 1) {
// next(self.it)
if (Py_TYPE(self->it)->tp_iternext == NULL) { PyErr_SetString(PyExc_TypeError, "no iternext"); return NULL; }
PyObject *ret = Py_TYPE(self->it)->tp_iternext(self->it);
if (!ret) {
// except StopIteration as e
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(type, PyExc_StopIteration)) {
Py_XDECREF(type);
Py_XDECREF(traceback);
if (!value) { PyErr_SetString(PyExc_ValueError, "no StopIteration value"); return NULL; }
// ret = e.value.lower()
PyObject *value2 = PyObject_CallMethod(value, "lower", NULL);
Py_DECREF(value);
if (!value2) { return NULL; }
// raise StopIteration(ret)
PyErr_SetObject(PyExc_StopIteration, value2);
Py_DECREF(value2);
Py_CLEAR(self->it);
self->state = 2;
} else {
// except:
// raise
PyErr_Restore(type, value, traceback);
}
}
return ret;
}
// raise StopIteration(None)
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
static int
advent2019__Spam_traverse(_SpamObject *self, visitproc visit, void *arg)
{
Py_VISIT(self->it);
return 0;
}
static int
advent2019__Spam_clear(_SpamObject *self)
{
Py_CLEAR(self->it);
return 0;
}
static void
advent2019__Spam_dealloc(_SpamObject *self)
{
PyObject_GC_UnTrack(self);
advent2019__Spam_clear(self);
PyObject_GC_Del(self);
}
static PyType_Slot advent2019__Spam_slots[] = {
{Py_tp_new, advent2019__Spam_new},
{Py_tp_iter, advent2019__Spam_iter},
{Py_tp_iternext, advent2019__Spam_iternext},
{Py_tp_traverse, advent2019__Spam_traverse},
{Py_tp_clear, advent2019__Spam_clear},
{Py_tp_dealloc, advent2019__Spam_dealloc},
{0, 0},
};
static PyType_Spec advent2019__Spam_spec = {
.name = "advent2019._Spam",
.basicsize = sizeof(_SpamObject),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
.slots = advent2019__Spam_slots,
};
typedef struct {
PyObject_HEAD
} SpamObject;
static PyObject *
advent2019_Spam_await(SpamObject *self)
{
PyObject *_Spam_Type = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "_Spam");
if (_Spam_Type == NULL) { return NULL; }
PyObject *it = PyObject_CallFunction(_Spam_Type, "");
Py_DECREF(_Spam_Type);
return it;
}
static PyType_Slot advent2019_Spam_slots[] = {
{Py_am_await, (unaryfunc)advent2019_Spam_await},
{0, 0},
};
static PyType_Spec advent2019_Spam_spec = {
.name = "advent2019.Spam",
.basicsize = sizeof(SpamObject),
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.slots = advent2019_Spam_slots,
};
static int advent2019_exec(PyObject *module) {
int ret = -1;
PyObject *builtins = NULL;
PyObject *printfunc = NULL;
PyObject *asyncio_module = NULL;
PyObject *sleep = NULL;
PyObject *_Spam_Type = NULL;
PyObject *Spam_Type = NULL;
if (!(builtins = PyEval_GetBuiltins())) { goto cleanup; } /* borrowed */
// fetch the builtin function print
if (!(printfunc = PyMapping_GetItemString(builtins, "print"))) { goto cleanup; }
// import asyncio
if (!(asyncio_module = PyImport_ImportModule("asyncio"))) { goto cleanup; }
if (!(sleep = PyObject_GetAttrString(asyncio_module, "sleep"))) { goto cleanup; };
if (!(_Spam_Type = PyType_FromSpec(&advent2019__Spam_spec))) { goto cleanup; }
// _Spam._print = print
if (PyObject_SetAttrString(_Spam_Type, "_print", printfunc)) { goto cleanup; }
// _Spam._sleep = asyncio.sleep
if (PyObject_SetAttrString(_Spam_Type, "_sleep", sleep)) { goto cleanup; }
if (!(Spam_Type = PyType_FromSpec(&advent2019_Spam_spec))) { goto cleanup; }
// Spam._Spam = _Spam
if (PyObject_SetAttrString(Spam_Type, "_Spam", _Spam_Type)) { goto cleanup; }
if (PyObject_SetAttrString(module, "Spam", Spam_Type)) { goto cleanup; }
if (PyObject_SetAttrString(module, "_Spam", _Spam_Type)) { goto cleanup; }
ret = 0;
cleanup:
Py_XDECREF(printfunc);
Py_XDECREF(asyncio_module);
Py_XDECREF(sleep);
Py_XDECREF(_Spam_Type);
Py_XDECREF(Spam_Type);
if (ret) { Py_XDECREF(module); }
return ret;
}
static PyModuleDef_Slot advent2019_slots[] = {
{Py_mod_exec, advent2019_exec},
{0, NULL}
};
static struct PyModuleDef advent2019_moduledef = {
PyModuleDef_HEAD_INIT,
.m_name = "advent2019",
.m_slots = advent2019_slots,
};
PyMODINIT_FUNC PyInit_advent2019(void) {
return PyModuleDef_Init(&advent2019_moduledef);
}
Exemple de code pour utiliser ceci
import sys
import asyncio
import advent2019
async def main():
v = await advent2019.Spam()
print(v)
if __name__ == '__main__':
if sys.version_info < (3, 7):
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
else:
asyncio.run(main())
Recommended Posts