PEP 422 – Einfachere Anpassung der Klassenerstellung
- Autor:
- Alyssa Coghlan <ncoghlan at gmail.com>, Daniel Urban <urban.dani+py at gmail.com>
- Status:
- Zurückgezogen
- Typ:
- Standards Track
- Erstellt:
- 05-Juni 2012
- Python-Version:
- 3.5
- Post-History:
- 05-Juni 2012, 10-Feb-2013
Zusammenfassung
Derzeit erfordert die Anpassung der Klassenerstellung die Verwendung einer benutzerdefinierten Metaklasse. Diese benutzerdefinierte Metaklasse bleibt für den gesamten Lebenszyklus der Klasse bestehen, was das Potenzial für irrtümliche Metaklassenkonflikte schafft.
Diese PEP schlägt vor, stattdessen eine breite Palette von Anpassungsszenarien durch einen neuen Parameter namespace im Klassen-Header und einen neuen Hook __autodecorate__ im Klassen-Body zu unterstützen.
Der neue Mechanismus sollte einfacher zu verstehen und zu verwenden sein als die Implementierung einer benutzerdefinierten Metaklasse und somit eine sanftere Einführung in die volle Leistungsfähigkeit der Metaklassen-Mechanismen von Python bieten.
Rücknahme eines PEP
Dieser Vorschlag wurde zugunsten des Vorschlags von Martin Teichmann in PEP 487 zurückgezogen, der die gleichen Ziele durch einen einfacheren, leichter zu verwendenden __init_subclass__ Hook erreicht, der für die Basisklasse, die den Hook definiert, einfach nicht aufgerufen wird.
Hintergrund
Für eine bereits erstellte Klasse cls hat der Begriff "Metaklasse" eine klare Bedeutung: Es ist der Wert von type(cls).
Während der Klassenerstellung hat er eine andere Bedeutung: Er wird auch verwendet, um sich auf den Metaklassen-Hinweis zu beziehen, der als Teil der Klassendefinition angegeben werden kann. Während sich in vielen Fällen diese beiden Bedeutungen auf dasselbe Objekt beziehen, gibt es zwei Situationen, in denen dies nicht der Fall ist
- Wenn der Metaklassen-Hinweis auf eine Instanz von
typeverweist, wird er zusammen mit den Metaklassen aller Eltern der zu definierenden Klasse als Kandidaten-Metaklasse betrachtet. Wenn eine geeignetere Metaklasse unter den Kandidaten gefunden wird, wird diese anstelle der im Metaklassen-Hinweis angegebenen verwendet. - Andernfalls wird davon ausgegangen, dass ein expliziter Metaklassen-Hinweis eine Factory-Funktion ist und direkt aufgerufen wird, um das Klassenobjekt zu erstellen. In diesem Fall wird die endgültige Metaklasse durch die Definition der Factory-Funktion bestimmt. Im typischen Fall (wenn die Factory-Funktion nur
typeoder in Python 3.3 oder spätertypes.new_classaufruft) wird die tatsächliche Metaklasse dann basierend auf den Elternklassen bestimmt.
Es ist erwähnenswert, dass nur die tatsächliche Metaklasse vererbt wird – eine als Metaklassen-Hook verwendete Factory-Funktion sieht nur die gerade definierte Klasse und wird für keine Unterklassen aufgerufen.
In Python 3 wird der Metaklassen-Hinweis über die metaclass=Meta Keyword-Syntax im Klassen-Header bereitgestellt. Dies ermöglicht die Verwendung der Methode __prepare__ auf der Metaklasse, um den Namensraum locals() zu erstellen, der während der Ausführung des Klassen-Bodies verwendet wird (z. B. die Verwendung von collections.OrderedDict anstelle eines normalen dict).
In Python 2 gab es keine Methode __prepare__ (diese API wurde für Python 3 von PEP 3115 hinzugefügt). Stattdessen konnte ein Klassen-Body das Attribut __metaclass__ setzen, und der Klassenerstellungsprozess extrahierte diesen Wert aus dem Klassen-Namensraum, um ihn als Metaklassen-Hinweis zu verwenden. Es gibt veröffentlichten Code, der diese Funktion nutzt.
Eine weitere neue Funktion in Python 3 ist die Null-Argument-Form des integrierten super(), eingeführt von PEP 3135. Diese Funktion verwendet eine implizite Referenz __class__ auf die zu definierende Klasse, um die "by name"-Referenzen zu ersetzen, die in Python 2 erforderlich waren. So wie Code, der während der Ausführung einer Python 2-Metaklasse aufgerufen wird, keine Methoden aufrufen konnte, die auf die Klasse nach Namen verwiesen (da der Name noch nicht im enthaltenden Gültigkeitsbereich gebunden war), können Python 3-Metaklassen keine Methoden aufrufen, die von der impliziten Referenz __class__ abhängen (da diese erst nach Rückgabe der Metaklasse an die Klassenerstellungs-Mechanismen populär wird).
Schließlich kann die Verwendung einer benutzerdefinierten Metaklasse zusätzliche Herausforderungen für die Nutzung von Mehrfachvererbung mit sich bringen, da eine neue Klasse nicht von Elternklassen mit nicht verwandten Metaklassen erben kann. Dies bedeutet, dass es unmöglich ist, einer bereits veröffentlichten Klasse eine Metaklasse hinzuzufügen: Eine solche Ergänzung ist eine rückwärts inkompatible Änderung aufgrund des Risikos von Metaklassenkonflikten.
Vorschlag
Diese PEP schlägt vor, dass ein neuer Mechanismus zur Anpassung der Klassenerstellung in Python 3.4 hinzugefügt wird, der die folgenden Kriterien erfüllt
- Lässt sich gut in Klassenerbstrukturen integrieren (einschließlich Mixins und Mehrfachvererbung)
- Lässt sich gut in die implizite Referenz
__class__und die Null-Argument-Syntax vonsuper()integrieren, die von PEP 3135 eingeführt wurden - Kann zu einer bestehenden Basisklasse hinzugefügt werden, ohne ein erhebliches Risiko von Rückwärtskompatibilitätsproblemen einzugehen
- Stellt die Fähigkeit wieder her, dass Klassen-Namensräume einen Einfluss auf den Klassenerstellungsprozess haben (über das einfache Befüllen des Namensraums hinaus), aber potenziell ohne die volle Flexibilität des Python 2-Stil-Hooks
__metaclass__
Ein Mechanismus, der dieses Ziel erreichen kann, ist das Hinzufügen eines neuen impliziten Klassen-Dekorations-Hooks, der direkt an den bestehenden expliziten Klassen-Dekoratoren orientiert ist, aber im Klassen-Body oder in einer Elternklasse definiert ist, anstatt Teil des Klassen-Definitions-Headers zu sein.
Insbesondere wird vorgeschlagen, dass Klassendefinitionen einen Hook zur Klasseninitialisierung wie folgt bereitstellen können
class Example:
def __autodecorate__(cls):
# This is invoked after the class is created, but before any
# explicit decorators are called
# The usual super() mechanisms are used to correctly support
# multiple inheritance. The class decorator style signature helps
# ensure that invoking the parent class is as simple as possible.
cls = super().__autodecorate__()
return cls
Zur Vereinfachung des kooperativen Mehrfachvererbungsfalls erhält object eine Standardimplementierung des Hooks, die die Klasse unverändert zurückgibt
class object:
def __autodecorate__(cls):
return cls
Wenn eine Metaklasse aus irgendeinem Grund die implizite Klassen-Dekoration blockieren möchte, muss sie sicherstellen, dass cls.__autodecorate__ einen AttributeError auslöst.
Wenn dieser neue Hook auf dem erstellten Objekt vorhanden ist, wird er von der Klassenerstellungs-Mechanik aufgerufen, nachdem die Referenz __class__ initialisiert wurde. Für types.new_class() wird er als letzter Schritt aufgerufen, bevor das erstellte Klassenobjekt zurückgegeben wird. __autodecorate__ wird bei der Erstellung der Klasse implizit in eine Klassenmethode umgewandelt (bevor der Hook aufgerufen wird).
Beachten Sie, dass zum Zeitpunkt des Aufrufs von __autodecorate__ der Name der Klasse noch nicht an das neue Klassenobjekt gebunden ist. Infolgedessen kann die Zwei-Argument-Form von super() nicht verwendet werden, um Methoden aufzurufen (z. B. wäre super(Example, cls) im obigen Beispiel nicht funktionsfähig). Die Null-Argument-Form von super() funktioniert jedoch wie erwartet, da die Referenz __class__ bereits initialisiert ist.
Dieser allgemeine Vorschlag ist keine neue Idee (er wurde zuerst für die Aufnahme in die Sprachdefinition vor mehr als 10 Jahren vorgeschlagen, und ein ähnlicher Mechanismus wird seit langem von Zope's ExtensionClass unterstützt), aber die Situation hat sich in den letzten Jahren ausreichend geändert, dass die Idee eine erneute Berücksichtigung für die Aufnahme als natives Sprachfeature wert ist.
Darüber hinaus ermöglicht die Einführung der Metaklassenmethode __prepare__ in PEP 3115 eine weitere Verbesserung, die in Python 2 nicht möglich war: Diese PEP schlägt außerdem vor, dass type.__prepare__ aktualisiert wird, um eine Factory-Funktion als Keyword-Only-Argument namespace zu akzeptieren. Wenn vorhanden, wird der Wert, der als Argument namespace bereitgestellt wird, ohne Argumente aufgerufen, um das Ergebnis von type.__prepare__ zu erstellen, anstatt eine neu erstellte Dictionary-Instanz zu verwenden. Zum Beispiel wird die folgende Zeile ein geordnetes Dictionary als Klassen-Namensraum verwenden
class OrderedExample(namespace=collections.OrderedDict):
def __autodecorate__(cls):
# cls.__dict__ is still a read-only proxy to the class namespace,
# but the underlying storage is an OrderedDict instance
Hinweis
Diese PEP hebt zusammen mit der bestehenden Möglichkeit, __prepare__ zu verwenden, um einen einzelnen Namensraum unter mehreren Klassenobjekten zu teilen, ein mögliches Problem mit dem Caching der Attributsuche hervor: Wenn die zugrundeliegende Zuordnung auf andere Weise aktualisiert wird, wird der Cache der Attributsuche nicht korrekt invalidiert (dies ist ein wesentlicher Teil des Grundes, warum Klassen-__dict__-Attribute eine schreibgeschützte Ansicht des zugrundeliegenden Speichers ergeben).
Da die Optimierung durch diesen Cache sehr wünschenswert ist, muss die Verwendung eines vorhandenen Namensraums als Klassen-Namensraum möglicherweise offiziell als nicht unterstützt deklariert werden (da das beobachtete Verhalten ziemlich seltsam ist, wenn die Caches außer Takt geraten).
Hauptvorteile
Einfachere Nutzung benutzerdefinierter Namensräume für eine Klasse
Derzeit ist es notwendig, eine benutzerdefinierte Metaklasse zu schreiben und zu verwenden, um einen anderen Typ (wie collections.OrderedDict) für einen Klassen-Namensraum zu verwenden oder einen vorab befüllten Namensraum zu verwenden. Mit dieser PEP wird die Verwendung eines benutzerdefinierten Namensraums so einfach wie die Angabe einer geeigneten Factory-Funktion im Klassen-Header.
Einfacheres Vererben von Verhaltensweisen zur Definitionszeit
Das Verständnis von Python-Metaklassen erfordert ein tiefes Verständnis des Typsystems und des Klassenkonstruktionsprozesses. Dies wird zu Recht als herausfordernd angesehen, da mehrere bewegliche Teile (der Code, der Metaklassen-Hinweis, die tatsächliche Metaklasse, das Klassenobjekt, Instanzen des Klassenobjekts) klar voneinander unterschieden werden müssen. Selbst wenn man die Regeln kennt, ist es immer noch leicht, einen Fehler zu machen, wenn man nicht extrem vorsichtig ist. Eine frühere Version dieser PEP enthielt tatsächlich einen solchen Fehler: Sie gab "Unterklasse von type" an, für eine Einschränkung, die tatsächlich "Instanz von type" ist.
Das Verständnis des vorgeschlagenen impliziten Klassen-Dekorations-Hooks erfordert nur das Verständnis von Dekoratoren und gewöhnlicher Methodenvererbung, was keine so einschüchternde Aufgabe ist. Der neue Hook bietet einen allmählicheren Weg zum Verständnis aller Phasen des Klassendefinitionsprozesses.
Reduziertes Risiko von Metaklassenkonflikten
Eines der großen Probleme, die Bibliotheksautoren zögern lassen, Metaklassen zu verwenden (auch wenn sie angebracht wären), ist das Risiko von Metaklassenkonflikten. Diese treten auf, wenn zwei nicht verwandte Metaklassen von den gewünschten Eltern einer Klassendefinition verwendet werden. Dieses Risiko macht es auch sehr schwierig, einer Klasse, die zuvor keine hatte, eine Metaklasse hinzuzufügen.
Im Gegensatz dazu birgt das Hinzufügen einer __autodecorate__-Methode zu einem bestehenden Typ ein ähnliches Risiko wie das Hinzufügen einer __init__-Methode: Technisch gesehen besteht das Risiko, schlecht implementierte Unterklassen zu beschädigen, aber wenn dies geschieht, wird es als Fehler in der Unterklasse erkannt und nicht als Bruch der Rückwärtskompatibilitätsgarantien durch den Bibliotheksautor. Tatsächlich ist das Risiko in diesem Fall aufgrund der eingeschränkten Signatur von __autodecorate__ sogar noch geringer als im Fall von __init__.
Lässt sich sauber in PEP 3135 integrieren
Im Gegensatz zu Code, der als Teil der Metaklasse ausgeführt wird, kann Code, der als Teil des neuen Hooks ausgeführt wird, frei Methoden aufrufen, die von der impliziten Referenz __class__ abhängen, die von PEP 3135 eingeführt wurde, einschließlich Methoden, die die Null-Argument-Form von super() verwenden.
Ersetzt viele Anwendungsfälle für das dynamische Setzen von __metaclass__
Für Anwendungsfälle, die nicht das vollständige Ersetzen der definierten Klasse beinhalten, kann Python 2-Code, der __metaclass__ dynamisch gesetzt hat, nun stattdessen __autodecorate__ dynamisch setzen. Für fortgeschrittenere Anwendungsfälle ist die Einführung einer expliziten Metaklasse (möglicherweise als erforderliche Basisklasse verfügbar) immer noch notwendig, um Python 3 zu unterstützen.
Design-Überlegungen
Feststellen, ob die dekorierte Klasse die Basisklasse ist
Im Body einer Methode __autodecorate__ ist, wie bei jeder anderen Klassenmethode, __class__ an die Klasse gebunden, die die Methode deklariert, während der übergebene Wert eine Unterklasse sein kann.
Dies macht es relativ einfach, die Verarbeitung der Basisklasse bei Bedarf zu überspringen
class Example:
def __autodecorate__(cls):
cls = super().__autodecorate__()
# Don't process the base class
if cls is __class__:
return
# Process subclasses here
...
Ersetzen einer Klasse durch ein anderes Objekt
Als impliziter Dekorator ist __autodecorate__ in der Lage, die definierte Klasse relativ einfach durch ein anderes Objekt zu ersetzen. Technisch gesehen können benutzerdefinierte Metaklassen und sogar __new__-Methoden dies bereits implizit tun, aber das Dekorationsmodell macht solchen Code viel einfacher zu verstehen und zu implementieren.
class BuildDict:
def __autodecorate__(cls):
cls = super().__autodecorate__()
# Don't process the base class
if cls is __class__:
return
# Convert subclasses to ordinary dictionaries
return cls.__dict__.copy()
Es ist unklar, warum jemand dies implizit basierend auf Vererbung tun sollte, anstatt einfach einen expliziten Dekorator zu verwenden, aber die Möglichkeit scheint erwähnenswert zu sein.
Offene Fragen
Ist das Konzept namespace die zusätzliche Komplexität wert?
Im Gegensatz zum neuen Hook __autodecorate__ wird das vorgeschlagene Schlüsselwortargument namespace nicht automatisch von Unterklassen geerbt. In der Art und Weise, wie dieser Vorschlag derzeit geschrieben ist, ist die einzige Möglichkeit, einen speziellen Namensraum zu verwenden, der konsistent in Unterklassen verwendet wird, immer noch das Schreiben einer benutzerdefinierten Metaklasse mit einer geeigneten Implementierung von __prepare__.
Die Änderung der benutzerdefinierten Namensraum-Factory, sodass sie ebenfalls geerbt wird, würde die Komplexität dieses Vorschlags erheblich erhöhen und eine Reihe der gleichen potenziellen Basisklassenkonfliktprobleme einführen, wie sie bei der Verwendung benutzerdefinierter Metaklassen auftreten.
Eric Snow hat einen separaten Vorschlag unterbreitet, um stattdessen den Ausführungsnamensraum für Klassen-Bodies standardmäßig als geordnetes Dictionary zu verwenden und die Reihenfolge der Klassenzuordnungsdefinition für zukünftige Referenzen als Attribut (z. B. __definition_order__) im Klassenobjekt zu erfassen.
Erics vorgeschlagener Ansatz könnte eine bessere Wahl für ein neues Standardverhalten für den Typ sein, das gut mit dem vorgeschlagenen Hook __autodecorate__ kombiniert wird und die komplexere konfigurierbare Namensraum-Factory-Idee einer benutzerdefinierten Metaklasse wie der unten gezeigten überlässt.
Neue Wege zur Nutzung von Klassen
Der neue Schlüssel namespace im Klassen-Header ermöglicht eine Reihe interessanter Optionen zur Steuerung der Art und Weise, wie eine Klasse initialisiert wird, einschließlich einiger Aspekte der Objektmodelle von JavaScript und Ruby.
Alle unten aufgeführten Beispiele sind heute tatsächlich durch die Verwendung einer benutzerdefinierten Metaklasse möglich
class CustomNamespace(type):
@classmethod
def __prepare__(meta, name, bases, *, namespace=None, **kwds):
parent_namespace = super().__prepare__(name, bases, **kwds)
return namespace() if namespace is not None else parent_namespace
def __new__(meta, name, bases, ns, *, namespace=None, **kwds):
return super().__new__(meta, name, bases, ns, **kwds)
def __init__(cls, name, bases, ns, *, namespace=None, **kwds):
return super().__init__(name, bases, ns, **kwds)
Der Vorteil der direkten Implementierung des neuen Schlüssels in type.__prepare__ ist, dass die *einzige* dauerhafte Auswirkung die Änderung des zugrundeliegenden Speichers der Klassenattribute ist. Die Metaklasse der Klasse bleibt unverändert, wodurch viele der Nachteile, die typischerweise mit diesen Arten von Anpassungen verbunden sind, eliminiert werden.
Ordnungserhaltende Klassen
class OrderedClass(namespace=collections.OrderedDict):
a = 1
b = 2
c = 3
Vorab befüllte Namensräume
seed_data = dict(a=1, b=2, c=3)
class PrepopulatedClass(namespace=seed_data.copy):
pass
Klonen einer Prototypklasse
class NewClass(namespace=Prototype.__dict__.copy):
pass
Erweitern einer Klasse
Hinweis
Nur weil die PEP es *möglich* macht, dies relativ sauber zu tun, heißt das nicht, dass es jemand tun *sollte*!
from collections import MutableMapping
# The MutableMapping + dict combination should give something that
# generally behaves correctly as a mapping, while still being accepted
# as a class namespace
class ClassNamespace(MutableMapping, dict):
def __init__(self, cls):
self._cls = cls
def __len__(self):
return len(dir(self._cls))
def __iter__(self):
for attr in dir(self._cls):
yield attr
def __contains__(self, attr):
return hasattr(self._cls, attr)
def __getitem__(self, attr):
return getattr(self._cls, attr)
def __setitem__(self, attr, value):
setattr(self._cls, attr, value)
def __delitem__(self, attr):
delattr(self._cls, attr)
def extend(cls):
return lambda: ClassNamespace(cls)
class Example:
pass
class ExtendedExample(namespace=extend(Example)):
a = 1
b = 2
c = 3
>>> Example.a, Example.b, Example.c
(1, 2, 3)
Abgelehnte Designoptionen
Aufruf von __autodecorate__ aus type.__init__
Das automatische Aufrufen des neuen Hooks von type.__init__ würde die meisten Ziele dieser PEP erreichen. Die Verwendung dieses Ansatzes würde jedoch bedeuten, dass Implementierungen von __autodecorate__ keine Methoden aufrufen könnten, die auf die Referenz __class__ angewiesen sind (oder die Null-Argument-Form von super() verwenden), und könnten diese Funktionen selbst nicht nutzen.
Das aktuelle Design stellt stattdessen sicher, dass der implizite Dekorations-Hook alles tun kann, was ein expliziter Dekorator kann, indem er ihn nach Abschluss der anfänglichen Klassenerstellung ausführt.
Aufruf des automatischen Dekorations-Hooks __init_class__
Frühere Versionen der PEP verwendeten den Namen __init_class__ für den Namen des neuen Hooks. Es gab drei signifikante Probleme mit diesem Namen
- es war schwer zu merken, ob die richtige Schreibweise
__init_class__oder__class_init__war - die Verwendung von "init" im Namen deutete darauf hin, dass die Signatur mit der von
type.__init__übereinstimmen sollte, was nicht der Fall ist - die Verwendung von "init" im Namen deutete darauf hin, dass die Methode als Teil der initialen Erstellung des Klassenobjekts ausgeführt würde, was nicht der Fall ist
Der neue Name __autodecorate__ wurde gewählt, um deutlich zu machen, dass der neue Initialisierungs-Hook am nützlichsten als implizit aufgerufener Klassen-Dekorator betrachtet werden kann und nicht als eine __init__-Methode.
Erfordern eines expliziten Dekorators für __autodecorate__
Ursprünglich erforderte diese PEP die explizite Verwendung von @classmethod auf dem Dekorator __autodecorate__. Er wurde implizit gemacht, da es keine sinnvolle Interpretation gibt, ihn wegzulassen, und dieser Fall sowieso erkannt werden müsste, um eine nützliche Fehlermeldung zu geben.
Diese Entscheidung wurde verstärkt, nachdem festgestellt wurde, dass die Benutzererfahrung der Definition von __prepare__ und dem Vergessen des Methoden-Dekorators @classmethod einzigartig unverständlich ist (insbesondere da PEP 3115 ihn als normale Methode dokumentiert und die aktuelle Dokumentation nichts Eindeutiges dazu sagt).
Machen von __autodecorate__ implizit statisch, wie __new__
Obwohl __new__ die zu instanziierende Klasse als erstes Argument akzeptiert, wird sie tatsächlich implizit als statische Methode und nicht als Klassenmethode behandelt. Dies ermöglicht es, sie leicht aus ihrer definierenden Klasse zu extrahieren und direkt auf einer Unterklasse aufzurufen, anstatt an das Klassenobjekt gekoppelt zu sein, von dem sie abgerufen wird.
Ein solches Verhalten erscheint zunächst potenziell nützlich für den neuen Hook __autodecorate__, da es __autodecorate__-Methoden erlauben würde, leicht als explizite Dekoratoren auf anderen Klassen verwendet zu werden.
Diese scheinbare Unterstützung wäre jedoch eine Illusion, da sie nur dann korrekt funktionieren würde, wenn sie auf einer Unterklasse aufgerufen wird, in welchem Fall die Methode genauso leicht aus der Unterklasse abgerufen und auf diese Weise aufgerufen werden kann. Im Gegensatz zu __new__ gibt es kein Problem mit der möglichen Änderung von Methodensignaturen an verschiedenen Stellen der Vererbungskette.
Übergabe des Namensraums direkt anstelle einer Factory-Funktion
Zu einem Zeitpunkt schlug diese PEP vor, dass der Klassen-Namensraum direkt als Keyword-Argument übergeben wird, anstatt eine Factory-Funktion zu übergeben. Dies fördert jedoch ein nicht unterstütztes Verhalten (d. h. die Übergabe desselben Namensraums an mehrere Klassen oder die Beibehaltung des direkten Schreibzugriffs auf eine Zuordnung, die als Klassen-Namensraum verwendet wird), daher wurde die API auf die Version mit der Factory-Funktion umgestellt.
Referenzimplementierung
Eine Referenzimplementierung für __autodecorate__ wurde auf dem Issue Tracker veröffentlicht. Sie verwendet die ursprüngliche Benennung __init_class__, erlaubt noch nicht, dass der implizite Dekorator die Klasse durch ein anderes Objekt ersetzt und implementiert nicht den vorgeschlagenen Parameter namespace für type.__prepare__.
TODO
- Adresse die 5 Punkte unter https://mail.python.org/pipermail/python-dev/2013-February/123970.html
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0422.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT