You're right to want to avoid using Pyramid events here. Pyramid's default template language is Chameleon, which you appear to be using, but you can just as easily use Mako, or other template languages. Point being: there is no standard way to define parent templates in Pyramid, because it doesn't care which template language you use.
The standard way in Chameleon looks something like the following.
First, a base template, main.pt:
<html>
<head>
Something generic in here...
</head>
<body>
<h1>${document.title}</h1>
<div id="content">
<metal:content define-slot="content" />
</div>
</body>
</html>
An example child.pt, which first loads the parent template, and then fills the content slot:
<metal:main use-macro="load: main.pt">
<p metal:fill-slot="content">${structure: document.body}<p/>
</metal:main>
A possible pyramid view function:
@view_config(route_name='view_document', renderer='child.pt')
def document_view(request):
foo = Document(title="bar", content="silly example")
return {'document': foo}
Of course, this assumes I've defined a Document class elsewhere. Only the child template is mentioned in the Pyramid code. My view_document function passes the document object foo to the child.pt template, which then calls the main.pt template in turn.