Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Декоратор для контракту

with 15 comments

Почитав я тут недавно круту книжку “Програміст прагматик”, і чогось стрельнуло мені в голову реалізувати примітивне контрактне прорамування в Pyhon. Примітивне, бо йде перевірка лише трійки Хоара: якщо виконується передумова, то ми можемо запустити функцію, і повинна виконатись постумова. Я написав декоратор, якому передаються два параметри – предикат передумови, і післяумови. Невиконання будь-якої з умов викликає відповідне виключення.

Предикат передумови повинен приймати всі ті ж параметри що й функція, і повертати інформацію про те чи передумова виконується. Предикат післяумови як перший параметр приймає результат, а всі інші – тіж ж самі вхідні параметри, які передавались в функцію. Таким чином предикат післяумови може перевіряти чи відповідають результати вхідним даним. Зазвичай перевірка коректності має бути простішою ніж сам алгоритм. Наприклад квадрат квадратного кореня числа повинен дорівнювати цьому числу. Ну хоча б приблизно. Як обчислювати квадратний корінь – зовсім інша справа.

Залишилось придумати де б застосувати цей модуль в серйозніших програмах. Здається можна в тих же місцях що й модульні тести, особливо якщо придумати як перебирати аргументи що відповідають передумові, запускати для них фунцію й перевіряти постумову. Але я й модульними тестами ще нормально не користувався… Ну, але все в світі починається з малого…

class ContractError(Exception):
	def __init__(self,msg):
		return Exception.__init__(self,"Contract Error: %s" % msg)
	
class PreconditionError(ContractError):
	def __init__(self,msg):
		return ContractError.__init__(self,"Precondition \"%s\" don't meet contract" % msg)

class PostconditionError(ContractError):
	def __init__(self,msg):
		return ContractError.__init__(self,"Postcondition \"%s\" don't meet contract" % msg)

def condoc(func):
	''' get condtition documentation '''
	res = func.__doc__
	return res.strip() if res else "unknown condition (please fill in docstrings)"

def contract(pre,post):
	def Decorator(f):
		def wrapped(*args, **kwargs):
			if not pre(*args,**kwargs):
				raise PreconditionError(condoc(pre))
			res = f(*args,**kwargs)
			if not post(res,*args,**kwargs):
				raise PostconditionError(condoc(post))
			return res
		return wrapped
	return Decorator

####### TESTING #########

import types
def is_int(x):
	return type(x) == types.IntType
def is_float(x):
	return type(x) == types.FloatType

def positive(x):
	''' x is positive number '''
	return (is_int(x) or is_float(x)) and x>=0

def is_sqrt(f,x):
	''' exact square of f is x '''
	return f*f == x

def is_sqrt_e(f,x, epsilon = 0.1):
	''' square of f is x plus/minus epsilon'''
	return abs(f*f - x) < epsilon


import math

@contract(is_int,is_sqrt_e)
def sqrt(x):
	return math.sqrt(x)

try:
	print sqrt(4)
	print sqrt(3.14)
	print sqrt(9)
	print sqrt(0)

	print sqrt(5)
	print sqrt(-1)
	print sqrt('')
except ContractError, e:
	print "Some tests failed, because of",e
Advertisements

Written by bunyk

Серпень 26, 2011 at 08:09

Оприлюднено в Кодерство

Tagged with ,

Відповідей: 15

Subscribe to comments with RSS.

  1. >>> @contract(is_int,
    о так, динамічні мови такі динамічні =)

    danbst

    Серпень 26, 2011 at 12:15

    • Не зрозумів.

      bunyk

      Серпень 26, 2011 at 12:50

    • значить ти ще мало знаєш. Існують мови динамічної типізації і статичної. У динамічних є принципова проблема з невідоповідністю типів, якої немає в статичних. Перевірка типу на вході Python функції – це фактично боротьба з мовою. Мови статичної типізації у цьому плані допомагають позбутись даної рутини.

      заміть, про сам декоратор я промовчав, типу “данило одобряє”. Щоправда, є зауваження до функції is_sqrt_e(f,x, epsilon = 0.1). Якщо функція викликається тільки для декоратора, то який зміст у необов’язковому параметрі? Для варіювання допустимої помилки тобі доведеться використати каррінг, типу так:

      def is_sqrt_e(epsilon):
          ''' square of f is x plus/minus epsilon'''
          return lambda f,x: abs(f*f - x) < epsilon
      
      import math
      
      @contract(is_int,is_sqrt_e(0.1))
      def sqrt(x):
          return math.sqrt(x)+1
      

      danbst

      Серпень 26, 2011 at 13:18

    • Не договорив )

      Можна піти навіть далі,

      def is_sqrt_e(arg1, arg2 = None):
          ''' square of f is x plus/minus epsilon'''
          worker_func = lambda epsilon: lambda f,x: abs(f*f - x) < epsilon
          if arg2 is None: return worker_func(epsilon = arg1)
          else:            return worker_func(epsilon = 0.1)(arg1, arg2)
      
      @contract(is_int,is_sqrt_e(1e-8))
      def sqrt_precisius(x):
      	return math.sqrt(x)
      
      @contract(is_int,is_sqrt_e)
      def sqrt(x):
      	return math.sqrt(x)
      

      Але від цього пітон код кращим не стане. Якщо штинькати пітон-код різноманітними лямбдами, він стає досить нечитаємим. Можна навіть сказати, що Пітон все менше і менше починає мені подобатись…

      danbst

      Серпень 26, 2011 at 13:57

      • Тю. Можна лямбду прямо при передачі декоратору написати й не паритись.

        А про боротьбу з мовою – math.sqrt will raise TypeError, бо качина типізація, то я можу передумову на цілі і не писати. Але суть тут – не перевірка типів, а якомога жорсткіші вимоги до вхідних даних.

        bunyk

        Серпень 26, 2011 at 15:56

      • не все лямбдами можна виразити в пітоні. Колись я писав на цей рахунок пост – багаторядкові лямби. так шо фейл.

        А взагалі так, роздупляйся з юніт-тестами. Я колись писав свої на пітоні – http://paste2.org/p/1610919

        danbst

        Серпень 26, 2011 at 17:37

        • Зате все можна def-ами. Принципово лямбда нічим не відрізняється від звичайної функції, окрім того що початково немає імені.

          bunyk

          Серпень 26, 2011 at 17:38

          • Так я ж не про лямбди ) я ж про необов’язковий epsilon і як навіть за допомогою лямбдбля важко зробити epsilon саме параметром (а не, наприклад, глобальною константою) при використанні у декораторах

            лан забий =)

            danbst

            Серпень 26, 2011 at 18:37

        • Так само не кожен тип можна виразити лямбда-класами. Чому ж цього ніхто не робить? Невже тільки тому що я лише недавно придумав слово “лямбда-клас”?

          bunyk

          Серпень 26, 2011 at 17:40

          • прикольне слово лямбда-клас =) В C# він навіть має тип “Type”… хоча в C# навіть лямбда-функції мають тип, наприклад Action, Func, delegate…

            danbst

            Серпень 26, 2011 at 18:40

  2. Тарас, я вже не встигаю читати книги, що ти рекомендуєш 🙂

    presidentua

    Серпень 27, 2011 at 13:16

  3. Просто приятно знать, что в Украине есть люди, которые знают что такое тройка Хоара и используют это.

    A2K

    Вересень 4, 2011 at 03:28

    • Ти ж точно повинен бути знайомим з Вінником і Нікітченком. Крім того, ніякого застосування найближчим часом не планується.

      bunyk

      Вересень 7, 2011 at 20:33

  4. Що цікаво, коли я писа цю статтю, я написав

    type(x) == types.IntType
    

    бо ще не знав що краще

    isinstance(x, int)
    

    отак от…

    bunyk

    Квітень 9, 2012 at 17:27


Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: