Можете ли вы обезопасить методы исправления основных типов в Python?

StackOverflow https://stackoverflow.com/questions/192649

Вопрос

Ruby может добавлять методы к классу Number и другим основным типам, чтобы получать подобные эффекты:

1.should_equal(1)

Но, похоже, Python не может этого сделать.Это правда?И если да, то почему?Имеет ли это какое-то отношение к тому факту, что Тип не может быть изменен?

Обновить:Вместо того чтобы говорить о различных определениях обезьяньего исправления, я хотел бы просто сосредоточиться на приведенном выше примере.Я уже пришел к выводу, что это невозможно сделать, как ответили некоторые из вас.Но я хотел бы получить более подробное объяснение, почему это невозможно сделать, и, возможно, какая функция, если она доступна в Python, позволила бы это.

Чтобы ответить некоторым из вас:Причина , по которой я мог бы хочу сделать это просто для эстетики / удобочитаемости.

 item.price.should_equal(19.99)

Это больше похоже на английский и четко указывает, какое значение является тестируемым, а какое - ожидаемым, как и предполагалось:

should_equal(item.price, 19.99)

Эта концепция заключается в том, что Rspec и некоторые другие фреймворки Ruby основаны на них.

Это было полезно?

Решение

Что именно вы подразумеваете под Monkey Patch здесь? Существует несколько слегка отличающихся определений .

Если вы имеете в виду, «можете ли вы изменить методы класса во время выполнения?», то ответ решительно да:

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, и некоторые абстракции приносятся в жертву, чтобы сделать реализацию проще и эффективнее.

Мне не совсем понятно, по почему вы бы хотели изменить поведение встроенных числовых классов в любом случае. Если вам нужно изменить их поведение, создайте их подклассы !!

Другие советы

Нет, вы не можете. В Python все данные (классы, методы, функции и т. Д.), Определенные в модулях расширения C (включая встроенные), являются неизменяемыми. Это связано с тем, что модули C совместно используются несколькими интерпретаторами в одном и том же процессе, поэтому их извлечение также может повлиять на несвязанные интерпретаторы в одном и том же процессе. (Несколько переводчиков в одном и том же процессе возможны через C API , и были предприняты некоторые усилия по их использованию на Уровень Python.)

Однако классы, определенные в коде Python, могут быть пропатчены, потому что они локальны для этого интерпретатора.

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")

Веселись;)

Вы можете сделать это, но это займет немного взлома. К счастью, теперь есть модуль под названием «Запретный плод» это дает вам возможность очень просто исправлять методы встроенных типов. Вы можете найти его на

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)

и ты в порядке! Есть также «обратный» функция для удаления исправленного метода.

Основные типы 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 ; с таким же успехом можно определить его встроенный непосредственно в функции или методе, который создает экземпляр.

Конечно, есть несколько ситуаций, когда программисты на 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)

или что-то в этом роде. Это немного многословно, но, без сомнения, его можно улучшить. Возможное преимущество такого подхода перед патчированием обезьян в стиле Ruby заключается в том, что should_equal () может легко выполнить сравнение для любого типа, а не только для int или float . Но, возможно, я слишком увлечен деталями конкретного примера, который вы предоставили.

Вы не можете исправлять основные типы в python. Тем не менее, вы можете использовать pipe для написания более удобочитаемого кода:

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)

Вот пример реализации item.price.should_equal , хотя в реальной программе я использовал бы десятичное число вместо плавающего:

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)

Если вы действительно действительно хотите сделать патч для обезьян в Python, вы можете сделать (sortof) хак с помощью команды " import foo as bar " Методика.

Если у вас есть такой класс, как TelnetConnection, и вы хотите расширить его, создайте подкласс в отдельном файле и назовите его чем-то вроде TelnetConnectionExtended.

Затем в верхней части кода, где вы обычно говорите:

import TelnetConnection

измените это на

import TelnetConnectionExtended as TelnetConnection

и везде в вашем коде, на который вы ссылаетесь, TelnetConnection будет фактически ссылаться на TelnetConnectionExtended.

К сожалению, это предполагает, что у вас есть доступ к этому классу, а " как " работает только в этом конкретном файле (это не глобальное переименование), но я обнаружил, что время от времени это полезно.

Нет, но у вас есть UserDict UserString и UserList, которые были созданы именно для этого.

Если вы Google, вы найдете примеры для других типов, но это встроенные.

Как правило, исправления обезьян используются в Python меньше, чем в Ruby.

Нет, вы не можете сделать это в Python. Я считаю, что это хорошо.

Что делает 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)

и определите подходящие методы для класса или суперкласса элемента.

Похоже, вы действительно хотели написать:

assert item.price == 19.99

(Конечно, сравнивать числа с плавающей точкой на равенство или использовать цены с плавающей точкой - это плохая идея , поэтому вы должны написать assert item.price == Decimal (19.99) или любой другой числовой класс, который вы использовали для цены.)

Вы также можете использовать среду тестирования, например py.test , чтобы получить больше информации о неудачных утверждениях в ваших тестах.

Нет, к сожалению, вы не можете расширять типы, реализованные на C во время выполнения.

Вы можете создать подкласс int, хотя это нетривиально, возможно, вам придется переопределить __new__.

У вас также есть проблема с синтаксисом:

1.somemethod()  # invalid

Однако

(1).__eq__(1)  # valid

Вот как я могу добиться поведения .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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top