Трета задача

  1. Това е темата за въпроси свързани с условието на задача 3.

    Условието можете да намерите тук. Примерният тест е в GitHub.

    Сега е добър момент да споменем, че за разлика от споделянето на готови решения, споделянето на unit test-ове е позволено (и дори препоръчително).

    Публикувано преди
  2. Според мен тук има грешка:

    >>> lazy_number = Lazy(8) + Lazy(42)
    >>> number = number.force()
    >>> print(number)
    50
    

    Трябва да е:

    >>> number = lazy_number.force()
    

    И имам един въпрос: number в горния пример какво число е? Обикновено число или lazy?

    И имам втори въпрос: като почнем да си споделяме unit test-овете, ще ги проверявате ли? Защото има вероятност някой да разбрал-недоразбрал условието, да си направи някой unit test според неговото разбиране и след като го постне тук, всички ще изгърмят накрая.

    Публикувано преди
  3. force трябва да върне обикнонвено число, т'ва което се получава от смятането на всичко записано в мързеливото. Ако връщаше мързеливо, немаше да му се види края, буквално :) А за тестовете, да, освен да ги споделяме тук, има ли смисъл да ги предаваме като част от решението?

    Публикувано преди
  4. @Евгений:

    Да, така е, има грешка. Ще бъде оправена.

    За първия въпрос:

    В горния пример number е обикновено число (в нашата имплементация е int). lazy_number е мързеливо.

    Относно втория въпрос:

    Няма да проверяваме вашите тестове, разбира се! Това ще бъде изцяло ваша работа. А всички ще изгърмят само в случая, в който всички ползват нечий код и никой дори не го погледне.

    Публикувано преди
  5. @Евгени:

    Относно първите две изречения - да, именно.

    Относно последното - не, не предавайте тестовете си.

    Публикувано преди
  6. @Евгений, ами споделяйте тук, тестове или линкове към тях. Така ние, от екипа, ще ги прочитаме и ако забележим нещо криво - ще поправяме.

    Публикувано преди
  7. force трябва да върне обикнонвено число, т'ва което се получава от смятането на всичко записано в мързеливото. Ако връщаше мързеливо, немаше да му се види края, буквално :)

    Не е задължително. Може да пресметне експрешъна, така ще получи число и после да го запише като lazy.

    Ето и нагледен пример за какво имам в предвид:

    >>> lazy_number = Lazy(8) + Lazy(42)
    >>> number = lazy_number.force()
    >>> print(number)
    50
    >>> number2 = number + 10
    >>> print(number2)
    ?
    >>> #Някакви предположения?
    

    И се сетих за още един въпрос

    >>> print(lazy_number) какво ще даде?
    
    Публикувано преди
  8. Не е задължително. Може да пресметне експрешъна, така ще получи число и после да го запише като lazy.

    Не съм убеден, че разбирам горното. В случай, че възникне изключение при force няма да поучиш число.

    >>> number2 = number + 10
    >>> print(number2)
    60
    

    Няма нищо особено в number; той си е просто цялото число 50.

    >>> print(lazy_number) какво ще даде?
    

    Можеш да намериш отговора във документацията на print (второто изречение).

    Публикувано преди
  9. Въпросът ми беше дали

    >>> number = lazy_number.force()
    

    ще е еквивалентно на

    >>> lazy(50)
    

    Но и без това ми отговориха преди няколко поста, така че няма значение.

    Публикувано преди
  10. Трябва ли да имплементираме аритметични оператори с числа, които не са споменати в условието на задачата?

    Публикувано преди
  11. Да. Точно това е и причината в условието да ви молим да не проверявате дали аргументът на конструктора на Lazy е наистина число. Ако проверявате, със сигурност ще пропуснете нещо.

    Иначе, за протокола, има модул numbers, в който има базов тип Number и това е начинът да се проверява дали даден обект е число.

    Публикувано преди
  12. Аз се присетих да питам след като веднъж извикаме force() на едно lazy число, доброто възпитание предполага при следващо извикване да не се налага смятане на ново, нали?

    Публикувано преди
  13. След force(), числото се пресмята и става обикновено int, float или каквото е там и се пази като такова.

    Публикувано преди
  14. Хмм, чудя се дали Кирил разбра съвсем както трябва въпроса на Ивайло Пашов - или дали аз съм го разбрал съвсем както трябва :) Въпросът може да бъде parse-нат по два начина:

    а) трябва ли да реализираме аритметични операции, които работят върху някакви типове, които уж са числа, ама не са точно int, а някакви по-особени? (май Кирил на това отговори)

    б) трябва ли да реализираме аритметични операции, различни от изброените в условието (бинарни +, -, *, /, //, %, унарни +, -), които работят върху числа?

    Струва ми се, че отговорът на втория въпрос би трябвало да бъде "не" (все пак операциите са изброени в условието), но поне за пълнота реших да го спомена :)

    Публикувано преди
  15. @Петър

    б) Не, не трябва. Но пък е добро упражнение.

    Публикувано преди
  16. Ако Lazy e с аргумент комплексно число как да го преобразуваме до int,bool,float,str и дали изобщо е задължително да го правим?

    Публикувано преди
  17. Според мен, който се опитва да каства complex към int си е лично негов проблем :)

    Публикувано преди
  18. Аз имам един въпрос относно "отложено пресмятане на аритметичните операции". Пускаме някакъв израз в конструктора и той не се пресмята докато не викнем force. Правилно ли съм разбрал?

    Публикувано преди
  19. @Стоицев:

    Израза, който дадеш на конструктора ще се изпълни още преди конструктора ти. Това е поведението в Python. Т.е.

    >> Lazy(10) / Lazy(0) # работи
    >> Lazy(10 / 0)       # поражда грешка
    
    Публикувано преди
  20. Мммдааа, сигурно вече ме знаете - да, този с откАчените въпроси :P

    Та сега въпросът ми е разрешено ли е на force() да губи/унищожава информация за "произхода" на стойността на числото? Т.е. разрешено ли е при:

    a = Lazy(4)
    b = Lazy(5) + a
    print b
    
    a += 1
    print b
    

    ...вторият "print" да изведе същата стойност като първия?

    ПП. Хм, като се замисля, ако "[в]сички оператори трябва да връщат нова инстанция на мързеливо число", то няма начин a += 1 да направи каквото мислех, че ще направи... Добре де, въпросът ми е ако след това стойността на "обекта, известен като a" се промени по някакъв начин, трябва ли да се променя и резултатът от b.force() и str(b)?

    Публикувано преди
  21. @Петър:

    Мю на 'ако след това стойността на "обекта, известен като a" се промени по някакъв начин'. Ако добавиш mutability, всичко става прекалено сложно.

    Публикувано преди
  22. Пфффф, всъщност съвсем не е чак толкова сложно, ама както и да е :) Така и така успях да го направя и по двата начина, и то без твърде голяма промяна :) Но щом казваш, значи няма значение :P

    Публикувано преди
  23. Съгласен съм, че не е сложно, но тук въпросът по-скоро трябва да бъде дали е правилно и кое е правилното поведение на мързеливите числа.

    В първата лекция за ООП пишеше, че in-place оператори е добре да се имплементират, така че да променят self и да връщат self. Всъщност не е ли логично и унарните оператори да работят така?

    Ако това е правилно поведение, то в примера какво се очаква за b? (По-скоро ако го нямаше първия print, т.к. доколкото разбирам след принта числото се смята и няма значение какви са били операциите и операндите вътре...)

    Публикувано преди
  24. Според мен въпроса по-скоро опира до това доколко е сложно и объркващо от гледна точка на човека, който ще ползва този клас, а не доколко е сложно за реализиране mutability-то (колко му е да навържеш една дървовидна структура от мързеливи числа така или иначе :P ). Поне аз така разбрах коментара на Стефан.

    Публикувано преди
  25. Каквото Александър каза. Малко ще се разсея в размисли за дизайн:

    Няма нищо сложно в това да се имплементира някакво mutability, както въпроса на Петър показва. Безумно лесно е да се направи:

    num = Lazy(10)
    denom = Lazy(20)
    ratio = num / denom
    answer = ratio.force()
    
    denom.value = 0
    newAnswer = num / denom
    
    print(answer)
    print(newAnswer)
    

    Където .value = е някакъв начин да мутираме такова число. Обаче това поражда неудобни въпроси. Например:

    Трябва ли answer да е равен на newAnswer?

    И в двата отговора има логика. Да, защото са създадени от едни и същи обекти. Не, защото обектите са имали различна стойност при различните създавания.

    Къде ще се породи грешката?

    Да допуснем, че отговора на предния въпрос е "да". Тогава? При принтиране на newAnswer? При принтиране на answer? При denom.value = 0? Всичките имат логика. Последното е изключително дървено за имплементация.

    ...и ред други.

    Има и по-тънък момент. Като дизайнер на кода, вие отговаряте на тези въпроси веднъж. Може би след задълбочен размисъл и разглеждане на алтернативи. Потребителите на кода, обаче, трябва да отговарят на тези въпроси всеки път, когато го ползват. На човек му е трудно да държи в главата си всички подробности на всички инструменти. Затова предпочитам по-простата семантика, която не поражда въпроси и е напълно очевидна (immutable), пред по-сложната, в която въпросите имат ред адекватни отговори и вярният не е очевиден. Това може да се разглежда като principle of least surprise.

    Добавете в картинката, че две седмици след като сте написали (и забравили) един код се превръщате от автор в потребител, и кръгът се затваря. Почвате да се чудите какво точно решихте и защо точно така.

    Immutability-то е голяма стъпка в посоката на по-прост и разбираем код (за сметка на производителност). Точно както Python, Java и дори C.

    Публикувано преди
  26. @Станко:

    Къде казваме, че е по-добре __iadd__ да променя и връща self? Ако разгледаш въпроса на Пешо, ще видиш че е лесно човек да се обърка с in-place assignment. Аз лично предпочитам a += b да не променя a, а просто да го насочва към друг обект.

    Ако искам да изменям обекта на място, щях да го правя с метод (ала [].append()). Така е очевидно, че променяме съществуващия обект, а не това накъде сочи lvalue-то.

    Конкретно в рамките на тази задача, в примера на Петър (написан на Python 2), двата print-а трябва да извеждат едно и също. a += 1 трябва да работи като a = a + 1. Другото би било неконсистентно с останалите числа в езика.

    Публикувано преди
  27. Лекция 7. Обектно-ориентирано програмиране (29 март)

    [...]

    Аритметични оператори (2)

    Всеки оператор има и метод за прилагане на операцията "на място" - например +=, /= и т.н.

    Метода има същото име, но със i след двете подчертавки - add става iadd

    Хубаво е когато дефинирате такива методи, те да променят self и да връщат self

    При a OP= b и недефиниран iOP се извиква a = a OP b

    [...]

    Публикувано преди
  28. Мда. Не твърдя, че е особено разбираемо, но акцента е на когато дефинирате такива методи. За мен по-добрия вариант е да не ги дефинираме въобще и да зависим от имплементацията по подразбиране.

    Бързодействието е стандартното изключение.

    Публикувано преди
  29. Колкото на другия ти въпрос, не е логично унарните да работят така. Например.

    balance = getBalance()
    absolute = balance if balance > 0 else -balance
    
    print(balance)
    print(absolute)
    

    Тук очаквам първия print да принтира резултата от getBalance(). Какво щеше да стане, ако унарните оператори променят на място и getBalance() връща отрицателна стойност. Добра идея ли е?

    За допълнително объркване, представи си, че ползваме abs() и тя е имплементирана с този ... if ... else ... израз. Щеше ли да е объркващ кода тогава? Защо?

    Публикувано преди

Трябва да сте влезли в системата, за да може да отговаряте на теми.