qutebrowser ObjectRegistry

In the source code of qutebrowser, there are numerous accesses to the objreg object, which corresponds to the ObjectRegistry class. Simply put, it's a singleton manager that facilitates accessing instances conveniently across different parts of the code. What makes it unique is its association with the lifecycle of QObject; when a QObject is released, the instance is automatically removed from the ObjectRegistry, preventing memory leaks.

This article discusses the following two aspects:

  1. Implementation Principle
  2. Usage

Implementation Principle

In Python, a dictionary (Dict) is a commonly used data structure for key-value storage. Python's collections standard library provides a UserDict class specifically for custom dictionary extensions. For usage of UserDict, please click on the note UserDict.

In the ObjectRegistry class of qutebrowser, a dictionary associated with the lifecycle of QObject is implemented based on UserDict.

When the added object is a QObject instance, the class automatically binds a slot function to the destroyed signal of the instance. When the instance is destroyed in Qt, it is automatically deleted from the ObjectRegistry dictionary, thus achieving automatic release.

The class signature is as follows:

class ObjectRegistry(collections.UserDict):

Key implementation is as follows.

Binding destroyed Signal

Override the __setitem__ method:

def __setitem__(self, 
				name: _IndexType, 
				obj: Any) -> None:
	if isinstance(obj, QObject):
		func = functools.partial(
			self.on_destroyed, 
			name)
		obj.destroyed.connect(func)
		self._partial_objs[name] = func

	super().__setitem__(name, obj)

Note that this uses Python functools partial, for which you can click the note for reference.

The slot function bound here is on_destroyed.

on_destroyed

def on_destroyed(self, name: str) -> None:
	QTimer.singleShot(
		0, 
		functools.partial(
			self._on_destroyed, 
			name))

def _on_destroyed(self, name: str) -> None:
	if not hasattr(self, 'data'):
		# ...
		return
	try:
		del self[name]
		del self._partial_objs[name]
	except KeyError:
		pass

It's important to note that the deletion of the instance is not synchronous but is converted to asynchronous execution using QTimer. The reason is that the instances registered need to be used during the synchronous destruction process, so they cannot be immediately deleted.

Usage

objreg.register

Instances are registered to the ObjectRegistry via the register method.

In the code, the usage is as follows:

Register a window instance under the name 'last-focused-main-window'. If an object already exists, it is updated:

objreg.register(
	'last-focused-main-window', 
	window, 
	update=True)

Register save_manager as a global instance:

objreg.register(
	'save-manager', 
	save_manager)

In addition to the global singleton, ObjectRegistry also supports a local Registry. For example, in browsertab, abstractTab registers itself to the local Registry:

objreg.register(
	'tab', 
	self, 
	registry=self.registry)

ObjectRegistry also supports different scopes for hierarchical management of instances, such as a window scope managing instances under a particular window. This ensures that instances with the same name in different scopes are isolated from each other:

objreg.register(
	'mode-manager', 
	modeman, 
	scope='window', 
	window=win_id)

From the examples given, it's evident that ObjectRegistry is quite flexible and powerful in its functionality. It provides a way to manage instances conveniently across different parts of the application, ensuring that objects are accessible and correctly managed in terms of memory and lifecycle, especially in a complex application like a web browser where multiple components and instances need to interact efficiently.

objreg.get

The ObjectRegistry's get method is used to obtain an instance.

Get last-focused window:

tabbed_browser = objreg.get(
	'tabbed-browser', 
	scope='window',
    window='last-focused')

Get download manager:

download_manager = objreg.get(
	'qtnetwork-download-manager')

Get tabbed browser of given window id:

tb = objreg.get(
	'tabbed-browser', 
	scope='window', 
	window=self._tab.win_id)

本文作者:Maeiee

本文链接:qutebrowser ObjectRegistry

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!