Frage

Ruby kann Methoden zur Zahlenklasse und anderen Kerntypen hinzufügen, um solche Effekte zu erzielen:

1.should_equal(1)

Aber es scheint, als würde Python das nicht können. Ist das wahr? Und wenn ja, warum? Hat es etwas mit der Tatsache zu tun, dass Typ Kannst du nicht geändert werden?

UPDATE: Anstatt über verschiedene Definitionen des Monkey -Patching zu sprechen, möchte ich mich nur auf das obige Beispiel konzentrieren. Ich bin bereits zu dem Schluss gekommen, dass es nicht wie einige von Ihnen getan werden kann. Aber ich möchte eine detailliertere Erklärung, warum es nicht getan werden kann, und vielleicht, welche Funktion, wenn sie in Python verfügbar ist, würde dies zulassen.

Um einige von Ihnen zu beantworten: der Grund, warum ich könnte Dies möchte, dass dies einfach Ästhetik/Lesbarkeit ist.

 item.price.should_equal(19.99)

Dies liest eher wie Englisch und zeigt deutlich an, welcher Wert der getestete Wert ist und welcher Wert der erwartete Wert ist, wie es soll:

should_equal(item.price, 19.99)

Dieses Konzept ist was RSPEC und einige andere Ruby -Frameworks basieren auf.

War es hilfreich?

Lösung

Was genau meinst du mit Monkey Patch hier? Es gibt Mehrere leicht unterschiedliche Definitionen.

Wenn Sie meinen: "Können Sie die Methoden einer Klasse zur Laufzeit ändern?"

class Foo:
  pass # dummy class

Foo.bar = lambda self: 42

x = Foo()
print x.bar()

Wenn Sie meinen: "Können Sie die Methoden einer Klasse zur Laufzeit ändern und Machen Sie alle Fälle dieser Klasse nach dem Faktenänderung? "Dann ist die Antwort auch ja. Ändern Sie einfach die Bestellung leicht:

class Foo:
  pass # dummy class

x = Foo()

Foo.bar = lambda self: 42

print x.bar()

Sie können dies jedoch nicht für bestimmte integrierte Kurse tun, wie int oder float. Die Methoden dieser Klassen werden in C implementiert und es gibt bestimmte Abstraktionen, die geopfert werden, um die Implementierung zu vereinfachen und effizienter zu gestalten.

Ich bin nicht wirklich klar warum Sie möchten das Verhalten der integrierten numerischen Klassen sowieso verändern. Wenn Sie ihr Verhalten ändern müssen, unterkrass sie!

Andere Tipps

Nein, du kannst nicht. In Python sind alle Daten (Klassen, Methoden, Funktionen usw.), die in C -Erweiterungsmodulen (einschließlich Inbuminen) definiert sind, unveränderlich. Dies liegt daran, dass C -Module zwischen mehreren Dolmetschern im selben Prozess geteilt werden, sodass MonkeyPatching sie auch nicht verwandte Dolmetscher im selben Prozess beeinflussen würde. (Mehrere Dolmetscher im gleichen Prozess sind durch die möglich C API, und es hat gegeben einige Anstrengungen Auf der Ebene der Python -Ebene nutzbar zu machen.)

Klassen, die im Python -Code definiert sind, können jedoch MonkeyPatchatchatchated sind, da sie zu diesem Interpreter lokal sind.

def should_equal_def(self, value):
    if self != value:
        raise ValueError, "%r should equal %r" % (self, value)

class MyPatchedInt(int):
    should_equal=should_equal_def

class MyPatchedStr(str):
    should_equal=should_equal_def

import __builtin__
__builtin__.str = MyPatchedStr
__builtin__.int = MyPatchedInt

int(1).should_equal(1)
str("44").should_equal("44")

Habe Spaß ;)

Sie können das tun, aber es braucht ein bisschen Hacking. Glücklicherweise gibt es ein Modul, das jetzt "Forbidden Fruit" bezeichnet wird, das Ihnen die Kraft gibt, Methoden der eingebauten Typen sehr einfach zu patchen. Sie können es finden bei

http://clarete.github.io/forbiddenfruit/?goback=.gde_50788_member_228887816

oder

https://pypi.python.org/pypi/forbiddenfruit/0.1.0

Nachdem Sie mit dem ursprünglichen Frage die Funktion "Sollte_Equal" geschrieben haben, würden Sie einfach nur tun

from forbiddenfruit import curse
curse(int, "should_equal", should_equal)

Und du bist gut zu gehen! Es gibt auch eine "Reverse" -Funktion, um eine Patch -Methode zu entfernen.

Die Kerntypen von Python sind mit Design unveränderlich, wie andere Benutzer betont haben:

>>> int.frobnicate = lambda self: whatever()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'int'

Du sicherlich könnte Erreichen Sie den Effekt, den Sie durch eine Unterklasse beschreiben, da benutzerdefinierte Typen in Python standardmäßig veränderlich sind.

>>> class MyInt(int):
...   def frobnicate(self):
...     print 'frobnicating %r' % self
... 
>>> five = MyInt(5)
>>> five.frobnicate()
frobnicating 5
>>> five + 8
13

Es ist nicht nötig, das zu machen MyInt Auch die öffentliche Unterklasse; Man könnte es genauso gut in der Funktion oder Methode, die die Instanz konstruiert, direkt definieren.

Es gibt sicherlich einige Situationen, in denen Python -Programmierer, die fließend in der Redewendung sprechen, diese Art von Unterklasse als das Richtige betrachten. Zum Beispiel, os.stat() Gibt eine zurück tuple Unterklasse, die genannte Mitglieder hinzufügt, um genau die Art von Lesbarkeitsanliegen zu beseitigen, auf die Sie sich in Ihrem Beispiel beziehen.

>>> import os
>>> st = os.stat('.')
>>> st
(16877, 34996226, 65024L, 69, 1000, 1000, 4096, 1223697425, 1223699268, 1223699268)
>>> st[6]
4096
>>> st.st_size
4096

Das heißt, in dem spezifischen Beispiel, das Sie geben, glaube ich nicht, dass die Unterklasse float in item.price (oder anderswo) wäre sehr wahrscheinlich als die pythonische Sache, die zu tun ist. ich kann Stellen Sie sich leicht jemanden vor, der sich entscheidet, eine hinzuzufügen price_should_equal() Methode an item Wenn das der primäre Anwendungsfall wäre; Wenn man nach etwas Allgemeineren suchte, könnte es vielleicht sinnvoller sein, benannte Argumente zu verwenden, um die beabsichtigte Bedeutung klarer zu machen, wie in

should_equal(observed=item.price, expected=19.99)

oder etwas in dieser Richtung. Es ist ein bisschen ausführlich, aber zweifellos könnte es verbessert werden. Ein möglicher Vorteil für einen solchen Ansatz gegenüber dem Affenpatching im Ruby-Stil ist das should_equal() könnte seinen Vergleich auf irgendeiner Art problemlos durchführen, nicht nur, nicht nur int oder float. Aber vielleicht bin ich zu sehr in die Details des jeweiligen Beispiels verwickelt, das Sie zufällig angegeben haben.

Sie können keine Kerntypen in Python patchen. Sie können jedoch Pipe verwenden, um einen menschlich lesbaren Code zu schreiben:

from pipe import *

@Pipe
def should_equal(obj, val):
    if obj==val: return True
    return False

class dummy: pass
item=dummy()
item.value=19.99

print item.value | should_equal(19.99)

Hier ist ein Beispiel für die Implementierung item.price.should_equal, Obwohl ich Dezimalstelle anstelle von Float in einem echten Programm verwenden würde:

class Price(float):
    def __init__(self, val=None):
        float.__init__(self)
        if val is not None:
            self = val

    def should_equal(self, val):
        assert self == val, (self, val)

class Item(object):
    def __init__(self, name, price=None):
        self.name = name
        self.price = Price(price)

item = Item("spam", 3.99)
item.price.should_equal(3.99)

Wenn Sie wirklich wirklich wirklich Ja wirklich Wenn Sie in Python einen Affenpatch machen möchten, können Sie einen (Sortof) Hack mit der Technik "Foo als Bar" machen.

Wenn Sie eine Klasse wie TelnetConnection haben und sie erweitern möchten, klassen Sie sie in eine separate Datei und nennen Sie sie so etwas wie telnetConnectionEtded.

Dann oben in Ihrem Code, wo Sie normalerweise sagen würden:

import TelnetConnection

Ändern Sie das, um zu sein:

import TelnetConnectionExtended as TelnetConnection

Und dann überall in Ihrem Code, auf den Sie telnetConnection verweisen, beziehen sich tatsächlich telnetConnectionEtended.

Leider wird davon ausgegangen, dass Sie Zugriff auf diese Klasse haben und das "AS" nur in dieser bestimmten Datei arbeitet (es ist kein globaler Rename), aber ich habe festgestellt, dass es von Zeit zu Zeit nützlich ist.

Nein, aber Sie haben Benutzerdokte und Benutzerliste, die genau so vorgenommen wurden.

Wenn Sie Google Google finden, finden Sie Beispiele für andere Typen, dies ist jedoch integriert.

Im Allgemeinen wird das Affenflicken in Python weniger verwendet als in Ruby.

Nein, das kann man in Python nicht tun. Ich halte es für eine gute Sache.

Was macht should_equal tun? Ist es ein Boolescher Rückkehr True oder False? In diesem Fall wird es geschrieben:

item.price == 19.99

Es gibt keinen Geschmack, aber kein regulärer Python -Entwickler würde sagen, dass dies weniger lesbar ist als Ihre Version.

Tut should_equal Setzen Sie stattdessen einen Validator? (Warum sollte ein Validator auf einen Wert beschränkt sein? Warum nicht einfach den Wert festlegen und danach nicht aktualisieren?) Wenn Sie einen Validator wünschen, könnte dies sowieso nie funktionieren, da Sie vorschlagen, entweder eine bestimmte Ganzzahl oder alle zu ändern Ganzzahlen. (Ein Validator, der erfordert 18.99 gleich 19.99 wird immer scheitern.) Stattdessen könnten Sie es so buchstabieren:

item.price_should_equal(19.99)

oder dieses:

item.should_equal('price', 19.99)

und definieren Sie geeignete Methoden in der Klasse oder Superklassen des Elements.

Es scheint, was Sie wirklich schreiben wollten, ist:

assert item.price == 19.99

(Natürlich ist der Vergleich von Schwimmer für Gleichheit oder die Verwendung von Floats für Preise, ist natürlich eine schlechte Idee, also würdest du schreiben assert item.price == Decimal(19.99) oder welche numerische Klasse, die Sie zum Preis verwendet haben.)

Sie können auch ein Testframework verwenden wie Py.test Um weitere Informationen zu fehlenden Behauptungen in Ihren Tests zu erhalten.

Nein, leider können Sie nicht in C zur Laufzeit in C implementiert werden.

Sie können int unterklassen, obwohl es nicht trivial ist, müssen Sie möglicherweise überschreiben __new__.

Sie haben auch ein Syntaxproblem:

1.somemethod()  # invalid

Jedoch

(1).__eq__(1)  # valid

So erzähle ich das.

result = calculate_result('blah') # some method defined somewhere else

the(result).should.equal(42)

oder

the(result).should_NOT.equal(41)

Ich habe eine Dekorationsmethode zur Erweiterung dieses Verhaltens zur Laufzeit auf eine eigenständige Methode aufgenommen:

@should_expectation
def be_42(self)
    self._assert(
        action=lambda: self._value == 42,
        report=lambda: "'{0}' should equal '5'.".format(self._value)
    )

result = 42

the(result).should.be_42()

Sie müssen ein wenig über die Interna wissen, aber es funktioniert.

Hier ist die Quelle:

https://github.com/mdwhatcott/pyspecs

Es ist auch auf PYPI unter Pyspecs.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top