Можете ли вы использовать методы патчей обезьян для основных типов в python?
Ruby может добавлять методы к классу Number и другим основным типам, чтобы получить такие эффекты, как:
1.should_equal(1)
Но похоже, что python не может этого сделать. Это правда? И если да, то почему? Имеет ли это отношение к тому, что тип не может быть изменен?
Обновление: вместо того, чтобы говорить о различных определениях патчей обезьян, я хотел бы просто сосредоточиться на приведенном выше примере. Я уже пришел к выводу, что это невозможно сделать, поскольку некоторые из вас ответили. Но я хотел бы получить более подробное объяснение, почему это невозможно сделать, и, может быть, какая функция, если доступна в python, позволит это.
Чтобы ответить на некоторые из вас: причина, по которой я могу сделать это, - просто эстетика/удобочитаемость.
item.price.should_equal(19.99)
читается больше как на английском языке и четко указывает, что является тестируемым значением и которое является ожидаемым значением, как предполагается:
should_equal(item.price, 19.99)
Эта концепция - это Rspec, а некоторые другие структуры Ruby основаны на.
Ответы
Ответ 1
Что именно вы подразумеваете под патчем Monkey? Есть несколько несколько разных определений.
Если вы имеете в виду, "можете ли вы изменить методы класса во время выполнения?", тогда ответ будет решительно да:
class Foo:
pass # dummy class
Foo.bar = lambda self: 42
x = Foo()
print x.bar()
Если вы имеете в виду: "Можете ли вы изменить методы класса во время выполнения и сделать все экземпляры этого класса изменены после?" тогда и ответ да. Просто слегка измените порядок:
class Foo:
pass # dummy class
x = Foo()
Foo.bar = lambda self: 42
print x.bar()
Но вы не можете сделать это для определенных встроенных классов, например int
или float
. Эти методы классов реализованы в C, и есть некоторые абстракции, принесенные в жертву, чтобы сделать реализацию проще и эффективнее.
Я не совсем понимаю, почему почему вы хотите изменить поведение встроенных числовых классов. Если вам нужно изменить их поведение, подклассируйте их!
Ответ 2
Нет, вы не можете. В Python все данные (классы, методы, функции и т.д.), Определенные в модулях расширения C (включая встроенные), неизменяемы. Это связано с тем, что C-модули совместно используются несколькими интерпретаторами в одном и том же процессе, поэтому monkeypatching them также влияет на несвязанные интерпретаторы в том же процессе.
Однако классы, определенные в коде Python, могут быть обезврежены, поскольку они являются локальными для этого интерпретатора.
Ответ 3
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")
Удачи;)
Ответ 4
Вы можете сделать это, но это требует немного взлома. К счастью, теперь есть модуль, называемый "Forbidden Fruit", который дает вам возможность исправлять методы встроенных типов очень просто. Вы можете найти его на
http://clarete.github.io/forbiddenfruit/?goback=.gde_50788_member_228887816
или
https://pypi.python.org/pypi/forbiddenfruit/0.1.0
С исходным вопросом вопроса, после того как вы напишете функцию "should_equal", вы просто выполните
from forbiddenfruit import curse
curse(int, "should_equal", should_equal)
и вам хорошо идти! Также существует функция "обратного" удаления исправленного метода.
Ответ 5
Типы ядра Python неизменяемы по дизайну, как указывают другие пользователи:
>>> 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'
Конечно, вы могли бы добиться эффекта, который вы описываете, создавая подкласс, поскольку пользовательские типы в Python по умолчанию изменяются.
>>> class MyInt(int):
... def frobnicate(self):
... print 'frobnicating %r' % self
...
>>> five = MyInt(5)
>>> five.frobnicate()
frobnicating 5
>>> five + 8
13
Нет необходимости публиковать подкласс MyInt
public; можно точно определить его непосредственно в функции или методе, который создает экземпляр.
Есть, конечно, несколько ситуаций, когда программисты Python, свободно владеющие идиомой, считают, что этот класс подкласса правилен. Например, os.stat()
возвращает подкласс tuple
, который добавляет именованные члены, точно для того, чтобы обратиться к вопросу о степени удобочитаемости, о котором вы говорите в своем примере.
>>> import os
>>> st = os.stat('.')
>>> st
(16877, 34996226, 65024L, 69, 1000, 1000, 4096, 1223697425, 1223699268, 1223699268)
>>> st[6]
4096
>>> st.st_size
4096
Тем не менее, в конкретном примере, который вы даете, я не считаю, что подклассификация float
в item.price
(или в другом месте) будет очень вероятна, чтобы считаться питонической задачей. Я легко могу представить, что кто-то решил добавить метод price_should_equal()
к item
, если это был основной случай использования; если бы кто-то искал что-то более общее, возможно, было бы разумнее использовать именованные аргументы, чтобы сделать предполагаемое значение более ясным, как в
should_equal(observed=item.price, expected=19.99)
или что-то в этом роде. Это немного многословно, но, без сомнения, его можно было бы улучшить. Возможным преимуществом такого подхода по сравнению с рубиновым обезьян-патчем является то, что should_equal()
может легко выполнить сравнение по любому типу, а не только int
или float
. Но, возможно, я слишком зациклен на деталях конкретного примера, который вам предоставили.
Ответ 6
Вы не можете изменять основные типы в python.
Однако вы можете использовать трубу для написания более понятного для человека кода:
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)
Ответ 7
Вот пример реализации item.price.should_equal
, хотя я бы использовал Decimal вместо float в реальной программе:
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)
Ответ 8
Если вы действительно действительно хотите сделать патч обезьяны в Python, вы можете сделать (sortof) взломать с помощью метода "import foo as bar".
Если у вас есть класс, такой как TelnetConnection, и вы хотите его расширить, подклассируйте его в отдельный файл и назовите его чем-то вроде TelnetConnectionExtended.
Затем в верхней части вашего кода, где вы обычно говорите:
import TelnetConnection
измените это значение:
import TelnetConnectionExtended as TelnetConnection
а затем всюду в вашем коде, который вы ссылаетесь на TelnetConnection, действительно будет ссылаться на TelnetConnectionExtended.
К сожалению, это предполагает, что у вас есть доступ к этому классу, а "как" работает только в этом конкретном файле (это не глобальное переименование), но я нашел его полезным время от времени.
Ответ 9
Нет, к сожалению, вы не можете расширять типы, реализованные в C во время выполнения.
Вы можете подклассировать int, хотя это нетривиально, вам может потребоваться переопределить __new__
.
У вас также есть проблема с синтаксисом:
1.somemethod() # invalid
Однако
(1).__eq__(1) # valid
Ответ 10
Нет, вы не можете сделать это на Python. Я считаю, что это хорошо.
Ответ 11
Нет, но у вас есть UserDict UserString и UserList, которые были сделаны именно с учетом этого.
Если вы google, вы найдете примеры для других типов, но это встроенные функции.
В общем паттинге обезьян менее используется в Python, чем в Ruby.
Ответ 12
Что делает should_equal
? Это логическое возвращение True
или False
? В этом случае он пишется:
item.price == 19.99
Там нет учета вкуса, но никакой обычный разработчик python не сказал бы, что он менее читабельный, чем ваша версия.
Использует ли should_equal
какой-то валидатор? (почему валидатор должен быть ограничен одним значением? Почему бы просто не установить значение и не обновить его после этого?) Если вы хотите валидатор, это никогда не сможет работать в любом случае, так как вы предлагаете изменить либо определенное целое число, либо все целые числа. (Валидатор, который требует 18.99
равным 19.99
, всегда терпит неудачу.) Вместо этого вы можете записать его следующим образом:
item.price_should_equal(19.99)
или это:
item.should_equal('price', 19.99)
и определить соответствующие методы для класса или суперклассов.
Ответ 13
Кажется, что вы действительно хотели написать:
assert item.price == 19.99
(Конечно, сравнение float для равенства или использование float для цен - плохая идея, поэтому вы пишете assert item.price == Decimal(19.99)
или любой цифровой класс, который вы использовали для цены.)
Вы также можете использовать платформу тестирования, например py.test, чтобы получить дополнительную информацию о неудачных утверждениях в тестах.
Ответ 14
Здесь, как я достигаю поведения .should_something...:
result = calculate_result('blah') # some method defined somewhere else
the(result).should.equal(42)
или
the(result).should_NOT.equal(41)
Я включил метод декоратора для расширения этого поведения во время выполнения автономным способом:
@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()
Вы должны знать немного о внутренностях, но он работает.
Здесь источник:
https://github.com/mdwhatcott/pyspecs
Это также на PyPI под pyspecs.