Da sich das Jahresende diesem Jahr nähert, werde ich [Python C / API] vergeblich verwenden. Umkehrung von Zweck und Mitteln. Dieses Mal strebe ich Awaitable an, so etwas wie Coroutine, das durch Ausführen des folgenden Coroutine-Funktions-Spam erhalten wird.
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())
Drucken Sie etwas aus, warten Sie auf den Ruhezustand, um das Ergebnis zu erhalten, und rufen Sie die niedrigere Methode auf, um das Ergebnis zurückzugeben. 3 Betrieb. Die Sende-, Wurf- und Schließmethoden, die Coroutine und Generator nicht haben, werden dieses Mal nicht reproduziert.
Warten Sie, bevor Sie den als async def geschriebenen Code nachahmen. Lassen Sie uns überprüfen, was dies überhaupt ist. Der Text, in dem diese Sprachfunktionen Python zusätzlich vorgeschlagen wurden, lautet PEP 492. Nachdem ich dies gelesen hatte, erinnerte ich mich schlampig daran, dass das Warten tatsächlich ein Ertrag von war. Ich denke, der Grund, warum dies vorbereitet wurde, war folgender. "Die Generatorspezifikation wurde ursprünglich im Hinblick auf die Verwendung als Coroutine entwickelt. Die Fähigkeit zum Anhalten und Fortsetzen war der Iterator selbst und die Fähigkeit, Werte zu senden PEP 342. Ich habe es hinzugefügt. Und ich habe es gut vor Python 3.4 gemacht. Es wurden jedoch Probleme aufgrund der Ununterscheidbarkeit von Generator und Iterator und Coroutine gemeldet, sodass die Sprachspezifikation von Python 3.5 unterschieden werden kann. Erstellen Sie eine andere Methode mit dem Namen \ _ \ _ await \ _ \ _ anstelle von \ _ \ _iter \ _ \ _. Lenken Sie den Ertrag nicht von der Anweisung für die Generatorfunktion ab, sondern anstelle der Coroutine-Funktion. Eine weitere Anweisung, die auf die Verwendung zur Verwendung im Inneren wartet, wurde neu hinzugefügt, um sie zu unterscheiden. " Es gibt verschiedene Namen \ _ \ _ warten \ _ \ _ und \ _ \ _ iter \ _ \ _, aber das Verhalten ist nicht unterschiedlich. Tp_iter und tp_as_sync. Am_await werden auch in der PyTypeObject-Struktur von CPython bereitgestellt und als separate Mitglieder behandelt.
Jetzt, wo ich Awaitable besser verstehe. Notieren Sie den Spam der Coroutine-Funktion sofort mit einer Klassenanweisung. Der Unterschied zu Iteratable besteht darin, dass \ _ \ _ \ _ \ _ anstelle von \ _ \ _ iter \ _ \ _ warten muss. Der einzige Unterschied besteht jedoch darin, dass Iterator von hier zurückgegeben wird.
class Spam:
def __await__(self):
... # TODO:Welcher Itarator kann zurückgegeben werden, um die Spam-Coroutine nachzuahmen?
Weiter zerlegen. Iterator gibt sich mit \ _ \ _ iter \ _ \ _ zurück, setzt die Verarbeitung mit \ _ \ _next \ _ \ _ fort, erstellt den nächsten Wert und gibt den Wert zurück, während die Verarbeitung unterbrochen wird.
class _Spam:
def __iter__(self):
return self
def __next__(self):
... # TODO:Wie kann ich die Spam-Coroutine imitieren?
class Spam:
def __await__(self):
return _Spam()
Was brauchen wir nun, um "Erwartet ähnlich wie Coroutine, das durch Ausführen von Coroutine-Funktions-Spam erhalten wird" zu implementieren? Es behält den Status bei, wie viele der drei Aktionen "Drucken Sie etwas, warten Sie auf den Ruhezustand, um das Ergebnis zu erhalten, rufen Sie die niedrigere Methode auf und geben Sie das Ergebnis zurück". Das Objekt hat das Attribut \ _state. Dieses Mal habe ich vorerst 0, 1, 2 int verwendet, aber wenn Sie es ordentlich schreiben möchten, ist es besser, Enum zu verwenden. Implementieren Sie dann \ _ \ _next \ _ \ _, das nach Status verarbeitet wird. Das Problem hier ist abzuwarten oder nachzugeben. Es delegiert die Verarbeitung an einen anderen Iterator als den Ertrag von. Wiederholt sich iterativ, bis ein anderer Iterator stoppt und der erhaltene Wert so zurückgegeben wird, wie er ist. Sie müssen einen anderen Iterator laufen lassen, um das Äquivalent zu implementieren. Aus diesem Grund haben wir das Attribut \ _it hinzugefügt. Wenn dieser Iterator stoppt, wird eine StopIteration-Ausnahme gesendet. Sehen Sie sich also das value-Attribut an. Dies entspricht dem Wert der Wartezeit, Ausbeute aus Ausdruck in der return-Anweisung der Coroutine-Funktion oder Generatorfunktion. Achten Sie auf die Position der unteren Methode. Wenn Sie dagegen einen Wert zurückgeben, geben Sie der StopIteration-Ausnahme einen Wert. Nach dem Stoppen des Iterators gilt Einschränkung, dass die StopIteration-Ausnahme weiterhin zurückgegeben werden muss. Ergreifen Sie daher Maßnahmen dagegen. .. \ _ \ _ Weiter \ _ \ _ endet mit Raise StopIteration. Das State-Holding-Attribut sollte mit \ _ beginnen, um anzuzeigen, dass es nicht extern neu geschrieben werden soll. Diese \ _Statusinitialisierung wird in \ _ \ _ new \ _ \ _ durchgeführt, um sie gegen mehrere Aufrufe von \ _ \ _ init \ _ \ _ resistent zu machen.
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()
Weil die Demontage vorbei ist. Schreiben Sie dies mit Python C / API. C Sprache von hier. Um eine Klasse zu erstellen, definieren Sie PyTypeObject-Struktur direkt schreiben oder PyType_Spec und rufen Sie PyType_FromSpec auf. Diesmal in Richtung der Verwendung von PyType_FromSpec. Beginnen Sie mit Spam, der einfacher ist als \ _Spam. Um \ _ \ _await \ _ \ _ zu erstellen, registrieren Sie die Funktion in tp_as_sync. Am_await. Da diese Methode eine Instanz von \ _Spam zurückgeben muss, stellen wir sicher, dass die Attribute der Spam-Klasse die \ _Spam-Klasse haben. Der Prozess zum Hinzufügen von Klassenattributen wird ausgeführt, nachdem die Klasse mit PyType_FromSpec im Initialisierungsprozess des als Py_mod_exec registrierten Moduls erstellt wurde.
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;
}
Übrigens wurde die Implementierung von Iterator \ _Spam verschoben. Zuerst aus der \ _SpamObject-Struktur und \ _ \ _ new \ _ \ _. Bereiten Sie einen Wertstatus vor, der den Status und den asyncio.sleep () enthält. \ _ \ _ Await \ _ \ _ iterator. Da es andere PyObject * enthält, stellen Sie sicher, dass Sie Speicher mit PyObject_GC_New zuweisen, der dem Garbage Collection-Mechanismus entspricht. Rufen Sie nach der Initialisierung außerdem PyObject_GC_Track auf und registrieren Sie sich im Garbage Collector.
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;
}
Sie benötigen eine Funktion, wenn die Speicherbereinigung funktioniert. Es gibt drei Arten: Traverse, mit dem Sie das Objekt, das Sie halten, aufnehmen können, Clear, das die Referenz zerstört, und Dealloc, das sich selbst zerstört. Py_VISIT-Makro ist nützlich zum Schreiben von Traversen. PyObject_GC_UnTrack, das mit PyObject_GC_Track gepaart ist, sollte zu Beginn des Dealloc aufgerufen werden.
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 \ _ \ _ ist einfach, weil es sich einfach selbst zurückgibt. Vergessen Sie nicht, den Referenzzähler zu bedienen.
static PyObject *
advent2019__Spam_iter(_SpamObject *self)
{
Py_INCREF(self);
return (PyObject *)self;
}
Endlich \ _ \ _ next \ _ \ _. In der Python C / API heißt es iternext. Die integrierten Funktionen print und sleep des Asyncio-Moduls sollten beim Erstellen der Klasse in den Klassenattributen mit den Namen \ _print und \ _sleep enthalten sein, genau wie Spam._Spam. Danach lautet der in Python geschriebene Code PyObject_GetAttrString, [PyObject_CallFunction](https: //docs.python) .org / ja / 3 / c-api / object.html # c.PyObject_CallFunction) und andere werden zum stetigen Portieren verwendet. Überprüfen Sie bei jedem Aufruf, ob der Rückgabewert NULL ist. Das Reduzieren der Referenzanzahl unnötiger Objekte ist langwierig. PyErr_Fetch, PyErr_GivenExceptionMatches zum Portieren von try-Anweisungen /ja/3/c-api/exceptions.html#c.PyErr_GivenExceptionMatches), PyErr_Restore Ich werde.
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;
}
Nachdem wir die Methoden der \ _Spam-Klasse haben, definieren Sie PyType_Spec. Stellen Sie sicher, dass das Flag Py_TPFLAGS_HAVE_GC gesetzt ist, um anzugeben, dass die Klasse von der Garbage Collection verwaltet werden soll.
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,
};
Ich habe versucht, Awaitable mit Python C / API zu implementieren. Wir konnten auch Links zu offiziellen Dokumenten sammeln, die die dafür erforderlichen Informationen enthalten. Apropos. Ist das nützlich? Der Code dafür ist viel länger und kann Ihnen helfen, CPython besser zu verstehen, genau wie Sie die Bequemlichkeit der asynchronen Def erkannt haben und auf die Syntax warten ...
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);
}
Codebeispiel, um dies zu verwenden
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