-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path03-cont.qmd
887 lines (655 loc) · 25.2 KB
/
03-cont.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
```{r}
#| include: false
source("_common.R")
```
# 조건부 실행 {#r-cont}
\index{부울 표현식}
\index{표현식!부울}
\index{논리 연산자}
\index{연산자!논리}
## 부울 표현식 {#r-cont-boolean}
**부울 표현식(boolean expression)**은 참(TRUE) 혹은 거짓(FALSE)를 지닌 표현식이다.
다음 예제는 `==` 연산자를 사용하여 두 개의 피연산자를 비교하여 값이 동일하면 참(TRUE),
그렇지 않으면 거짓(FALSE)을 산출한다.
\index{참 특별값}
\index{거짓 특별값}
\index{특별값!참}
\index{특별값!거짓}
\index{부울 자료형}
\index{자료형!부울}
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-true-false
5 == 5
5 == 6
```
### 파이썬
```{pyodide-python}
#| label: py-cont-true-false
5 == 5
5 == 6
```
:::
참(TRUE)과 거짓(FALSE)은 논리형(logical) 자료형(type)에 속하는 특별한 값으로 문자열이 아니다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-logical
typeof(TRUE)
#> [1] "logical"
typeof(FALSE)
#> [1] "logical"
```
### 파이썬
```{pyodide-python}
#| label: py-cont-logical
type(True)
#> <class 'bool'>
type(False)
#> <class 'bool'>
```
:::
`==` 연산자는 **비교 연산자(comparison operators)** 중 하나이고, 다른 연산자는 다음과 같다.
- x != y `#` x는 y와 값이 같지 않다.
- x > y `#` x는 y보다 크다.
- x < y `#` x는 y보다 작다.
- x >= y `#` x는 y보다 크거나 같다.
- x <= y `#` x는 y보다 작거나 같다.
- x == y `#` x는 y와 같다.
- x != y `#` x는 y와 개체가 동일하지 않다.
상기 연산자가 친숙할 수 있지만, R의 기호는 수학 기호와 다르다.
일반적인 오류로 비교를 해서 동일하다는 의미로 `==` 연산자 대신에 `=`를 사용하는 것이다.
R에서 대입연산자로 `<-`을 사용하지만, `=`으로 사용해도 프로그램은 돌아간다.
`=` 연산자는 대입 연산자이고, `==` 연산자는 비교 연산자이다.
`=<`, `=>`같은 비교 연산자는 R에는 없다.
\index{비교 연산자}
\index{연산자!비교}
## 논리 연산자 {#r-cont-logical-operator}
\index{논리 연산자}
\index{연산자!논리}
세 개의 **논리 연산자(logical operators): `&`, `||`, `!`**가 있다.
논리 연산자의 의미는 수식 기호의 의미와 유사하다.
영어로 표현하면 `&`은 `and`, `||`은 `or`, `!`은 `not`이 된다. 예를 들어,
- `x > 0 & x < 10`
x가 0보다 크고(and), 10보다 작으면 참이다.
\index{\& 연산자}
\index{연산자!\&}
<!-- \index{\textbar\textbar\space 연산자} -->
<!-- \index{연산자!\textbar\textbar} -->
\index{"! 연산자}
\index{연산자!"!}
`n %% 2 == 0 || n %% 3 == 0`은 두 조건문 중 하나만 참이 되면,
즉, 숫자가 2 혹은(`or`) 3으로 나누어지면 참이다.
마지막으로 `!` 연산자는 부울 연산 표현식을 부정한다.
`x > y`가 거짓이면, `!(x > y)`은 참이다. 즉, x가 y보다 작거나 같으면 참이다.
엄밀히 말해서, 논리 연산자의 두 피연산자는 모두 부울 표현식이지만, R에서 그다지 엄격하지는 않다. 0이 아닌 임의의 숫자는 모두 "참(TRUE)"으로 해석된다.
일반적으로 참(TRUE)이면 `1`, 그렇지 않는 경우 `0`으로 표현해서 사용한다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-logical-true
17 & TRUE
#> [1] TRUE
```
### 파이썬
```{pyodide-python}
#| label: py-cont-logical-true
17 and True
#> [1] True
```
:::
이러한 유연함이 유용할 수 있으나, 혼란을 줄 수도 있으니 유의해서 사용해야 한다.
무슨 일을 하고 있는지 정확하게 알지 못한다면 피하는 것이 상책이다.
## 조건문 실행 {#r-cont-run}
\index{조건문}
\index{문장!조건}
\index{if문}
\index{문장!if}
\index{조건문 실행}
유용한 프로그램을 작성하기 위해서는 거의 항상 조건을 확인하고 조건에 따라 프로그램 실행을 바꿀 수 있어야 한다.
**조건문(Conditional statements)**은 그러한 능력을 부여한다.
가장 간단한 형태는 `if` 문이다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-if
x <- 10
if (x > 0) {
print('x는 양수이다.')
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-if
x = 10
if x > 0:
print('x는 양수이다.')
```
:::
`if`문 뒤에 있는 불 표현식(boolean expression)을 조건(condition)이라고 한다.
![`if`문 순서도](images/cont-if.png){#fig-cont-if}
만약 조건문이 참이면, 첫 번째 괄호로 둘러싸인 문장이 실행된다.
만약 조건문이 거짓이면, 첫 번째 괄호로 둘러싸인 문장의 실행을 건너뛴다.
\index{조건}
\index{복합 문장}
\index{문장!복합}
`if`문은 함수 정의, `for` 반복문과 동일한 구조를 가진다.
`if`문은 `(`으로 시작되고, `)`으로 끝나는 헤더 머리부분과 괄호(`{`, `}`)로 둘러싸인 몸통 블록(block)으로 구성된다.
`if`문처럼 문장이 한 줄 이상에 걸쳐 작성되기 때문에 **복합 문장(compound statements)**이라고 한다.
`if`문 몸통 부분에 작성되는 실행 문장 숫자에 제한은 없으나 최소한 한 줄은 있어야 한다.
때때로, 몸통 부분에 어떤 문장도 없는 경우가 있다.
아직 코드를 작성하지 않아서 자리만 잡아 놓는 경우로, 그냥 놔두면 된다. 즉, 아무것도 작성하지 않고 괄호 내부를 비워 놓는다.
파이썬의 경우 아무것도 수행하지 않는 `pass`문을 넣어야 하는 것과 대비된다.
\index{pass 문장}
\index{문장!pass}
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-if-placeholder
if (x > 0) {
# 아무것도 작성하지 않고 자리만 잡아둔다. 나중에 코드를 채워 넣는다.
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-if-placeholder
if x > 0:
pass # 아무것도 작성하지 않고 자리만 잡아둔다. 나중에 코드를 채워 넣는다.
```
:::
`if`문을 R 인터프리터에서 타이핑하고 엔터를 치게 되면,
명령 프롬프트가 `+`로 바뀐다.
따라서 다음과 같이 `if`문 몸통 부분을 작성 중에 있다는 것을 나타낸다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-console, eval=FALSE}
x <- 3
if (x < 10) {
print('작다')
}
#> [1] "작다"
```
### 파이썬
```{pyodide-python}
#| label: py-cont-console, eval=FALSE}
x = 3
if x < 10:
print('작다')
#> "작다"
```
:::
## 대안 실행 {#r-cont-alternative}
\index{대안 실행}
\index{else 예약어}
\index{예약어!else}
`if`문의 두 번째 형태는 **대안 실행(alternative execution)**이다.
대안 실행의 경우 두 가지 경우의 수가 존재하고, 조건이 어느 방향으로 실행할 것인지 결정한다.
구문(Syntax)은 아래와 같다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-alternative
if (x %% 2 == 0){
print("x는 짝수이다.")
} else {
print("x는 홀수이다.")
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-alternative
if x % 2 == 0:
print("x는 짝수이다.")
else:
print("x는 홀수이다.")
```
:::
x를 2로 나누었을 때, 0이 되면, x는 짝수이고, 프로그램은 짝수("x는 짝수")라는 결과 메시지를 출력한다.
만약 조건이 거짓이라면, 두 번째 몸통 부분 문장이 실행된다.
![`if-else`문](images/cont-if-else.png){#fig-cont-if-else}
조건은 참 혹은 거짓이어서, 대안 중 하나만 정확하게 실행된다.
대안을 **분기(Branch)**라고도 하는데 이유는 실행 흐름이 분기되기 때문이다.
\index{분기}
## 연쇄 조건문 {#r-cont-chained}
\index{연쇄 조건문}
\index{조건문!연쇄}
때때로, 두 가지 이상의 경우의 수가 있으며, 두 가지 이상의 분기가 필요하다.
이와 같은 연산을 표현하는 방식이 **연쇄 조건문(chained conditional)**이다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-chained
y <- 5
if (x < y){
print("x는 y보다 작다.")
} else if (x > y) {
print("x는 y보다 크다.")
} else {
print("x와 y는 같다.")
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-chained
y = 5
if x < y:
print("x는 y보다 작다.")
elif x > y:
print("x는 y보다 크다.")
else:
print("x와 y는 같다.")
```
:::
"else if"로 연쇄 조건문을 표현하는 것에 주목한다. 이번에도 단 한 번의 분기만 실행된다.
![연쇄 조건문](images/cond-if-chained.png){#fig-cond-if-chained}
`if else` 문의 개수에 제한은 없다.
`else` 절이 있다면, 거기서 끝마쳐야 하지만, 연쇄 조건문에 반드시 있어야 하는 것은 아니다.
\index{else 예약어}
\index{예약어!else}
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-choice
if (choice == 'a') {
print("잘못된 추측이다.")
} else if(choice == 'b') {
print("좋은 추측이다.")
} else if (choice == 'c') {
print('가깝지만 틀렸다.')
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-choice
if choice == 'a':
print("잘못된 추측이다.")
elif choice == 'b':
print("좋은 추측이다.")
elif choice == 'c':
print('가깝지만 틀렸다.')
```
:::
각 조건은 순서대로 점검한다. 만약 첫 번째가 거짓이면, 다음을 점검하고 계속 점검해 나간다.
순서대로 진행 중에 하나의 조건이 참이면, 해당 분기가 수행되고, `if`문 전체는 종료된다.
설사 하나 이상의 조건이 참이라고 하더라도, 첫 번째 참 분기만 수행된다.
## 중첩 조건문 {#r-cont-nested}
\index{중첩 조건문}
\index{조건문!중첩}
하나의 조건문이 조건문 내부에 중첩될 수 있다.
다음과 같이 삼분 예제를 작성할 수 있다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-nested
if (x == y){
print("x와 y는 같다.")
} else {
if (x > y) {
print("x는 y보다 크다")
} else {
print("x는 y보다 작다")
}
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-nested
if x == y:
print("x와 y는 같다.")
else:
if x > y:
print("x는 y보다 크다.")
else:
print("x는 y보다 작다.")
```
:::
바깥 조건문에는 두 개의 분기가 있다.
첫 분기는 간단한 문장을 담고 있다.
두 번째 분기는 자체가 두 개의 분기를 가지고 있는 또 다른 `if`문을 담고 있다.
자체로 둘 다 조건문이지만, 두 분기 모두 간단한 문장이다.
![중첩 조건문](images/cond-if-nested.png){#fig-nested-if-stmt}
괄호를 사용하는 것이 구조를 명확히 하지만, **중첩 조건문의 경우 가독성이 급격히 저하된다.
일반적으로, 가능하면 중첩 조건문을 피하는 것을 권장한다.**
논리 연산자를 사용하여 중첩 조건문을 간략히 할 수 있다.
예를 들어, 단일 조건문으로 가지고 앞의 코드를 다음과 같이 재작성할 수 있다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-code-review
if (0 < x) {
if (x < 10) {
print('x는 한 자리 양수이다.')
}
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-code-review
if 0 < x:
if x < 10:
print('x는 한 자리 양수이다.')
```
:::
`print`문은 두 개 조건문을 통과될 때만 실행되어서,
`&` 연산자와 동일한 효과를 거둘 수 있다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-cont-code-simplify
if (0 < x & x < 10) {
print("x는 한 자리 양수이다.")
}
```
### 파이썬
```{pyodide-python}
#| label: py-cont-code-simplify
if 0 < x and x < 10:
print("x는 한 자리 양수이다.")
```
:::
## `try`, `catch` 활용 예외처리 {#r-cont-try-catch}
함수 `readline()`와 `as.integer()`을 사용하여 앞에서 사용자가 타이핑한 숫자를 읽어 정수로 파싱하는 프로그램 코드를 살펴보았다.
또한 이렇게 코딩하는 것이 얼마나 위험한 것인지도 살펴보았다.
``` {r}
#| label: r-cont-try-catch-error
#| eval: false
speed <- readline(prompt=prompts)
#> 속도가 얼마나 됩니까? 뭐라고 하셨나요!!!
as.integer(speed) + 5
#> [1] NA
```
R 인터프리터에서 상기 문장을 실행하면,
인터프리터에서 새로운 프롬프트로 되고, "이런(oops)" 잠시 후에, 다음 문장 실행으로 넘어간다.
하지만, 만약 코드가 R 스크립트로 실행이 되어 오류가 발생하면,
역추적해서 그 지점에서 즉시 멈추게 된다. 다음에 오는 문장은 실행하지 않는다.
\index{역추적}
화씨 온도를 섭씨 온도로 변환하는 간단한 프로그램이 있다.
다소 길이가 긴데, R 콘솔에서 실행하는 코드와 쉘에서 실행할 때 사용자 입력을 받는 것을 달리 처리하기 위함이다.
\index{화씨}
\index{섭씨}
\index{온도 변환}
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-var-fahren
# inp <- readline(prompt = "화씨 온도 입력해줘: ")
inp <- 72
tryCatch({
cel <- (as.numeric(inp) - 32.0) * 5.0 / 9.0
print(cel)
}, warning = function(w) {
print("경고: 숫자를 입력해주세요!")
}, error = function(e) {
print("에러: 숫자를 입력해주세요!")
})
```
### 파이썬
```{pyodide-python}
#| label: py-var-fahren
# inp = input('화씨 온도 입력해줘: ')
inp = 72
try:
fahr = float(inp)
cel = (fahr - 32.0) * 5.0 / 9.0
print(cel)
except ValueError:
print('숫자를 입력해주세요!')
```
:::
이 코드를 실행해서 적절하지 않은 입력값을 넣게 되면, 다소 불친절한 오류 메시지와 함께 간단히 작동을 멈춘다.
:::{.panel-tabset}
### R
```bash
D:\docs\r4inf\code> Rscript fahrenheit.R
화씨 온도를 입력하세요: 72
[1] 22.22222
D:\docs\r4inf\code> Rscript fahrenheit.R
화씨 온도를 입력하세요: fred
[1] "경고: 숫자를 입력해주세요!"
```
### 파이썬
```bash
D:\docs\r4inf\code> python fahrenheit.py
화씨 온도를 입력하세요: 72
22.22222
D:\docs\r4inf\code> python fahrenheit.py
화씨 온도를 입력하세요: fred
숫자를 입력해주세요!
```
:::
이런 종류의 예측을 하거나, 예측하지 못한 오류를 다루기 위해서 R에는
"try / catch"로 불리는 조건 실행 구조가 내장되어 있다.
`try`와 `catch`의 기본적인 생각은 일부 명령문에 문제가 있다는 것을 사전에 알고 있고,
만약 그 때문에 오류가 발생하게 된다면 대신 프로그램에 추가해서 명령문을 실행한다는 것이다.
`catch` 블록의 문장은 오류가 없다면 실행되지 않는다.
문장 실행에 대해서 R `try`, `catch` 기능을 보험으로 생각할 수도 있다.
R은 `tryCatch` 블록 문장을 우선 실행한다.
만약 모든 것이 순조롭다면, 코드를 실행하고 `error` 블록은 건너뛴다.
만약 `tryCatch` 블록에서 오류 `error` 혹은 경고 `warning`이 발생하면,
R은 `tryCatch` 블록에서 빠져나와 `warning`, `error` 문장을 수행한다.
`tryCatch`문으로 예외사항을 다루는 것을 예외 처리한다(catching an exception)고 부른다.
예제에서 `warning` 절에서는 단순히 오류 메시지를 출력만 한다.
이유는 `as.numeric` 함수가 잘못된 자료형의 입력값을 받아 경고를 발생시키며 `NA`를 반환했기 때문에 `tryCatch`는 `error` 블록 대신 `warning` 블록을 실행했다.
대체로, 예외 처리를 통해서 경고하거나, 오류를 고치거나, 재시작하거나, 최소한 프로그램이 정상적으로 종료될 수 있게 한다.
## 논리 연산식 단락 평가 {#r-cont-short-circuit}
\index{단락}
`x >= 2 & (x/y) > 2`와 같은 논리 표현식을 R에서 처리할 때, 왼쪽에서부터 오른쪽으로 표현식을 평가한다.
`&` 정의 때문에 x가 2보다 작다면, `x >= 2`은 거짓(FALSE)으로,
전체적으로 `(x/y) > 2`이 참(TRUE) 혹은 거짓(FALSE)이냐에 관계없이 거짓(FALSE)이 된다.
나머지 논리 표현식을 평가해도 나아지는 것이 없다고 R이 자동으로 탐지할 때,
평가를 멈추고 나머지 논리 표현식에 대한 연산도 중지한다.
최종값이 이미 결정되었기 때문에 더 이상의 논리 표현식의 평가가 멈출 때, 이를 **단락(Short-circuiting) 평가**라고 한다.
\index{가디언 패턴}
\index{패턴!가디언}
좋은 점처럼 보일 수 있지만, 단락 행동은 **가디언 패턴(guardian pattern)**으로 불리는 좀 더 똑똑한 기술로 연계된다.
R 인터프리터의 다음 코드를 살펴보자.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-var-short-circuit
x <- 6
y <- 2
x >= 2 & (x/y) > 2
#> [1] TRUE
x <- 1
y <- 0
x >= 2 & (x/y) > 2
#> [1] FALSE
x <- 6
y <- 0
x >= 2 & (x/y) > 2
#> [1] TRUE
```
### 파이썬
```{pyodide-python}
#| label: py-var-short-circuit
x = 6
y = 2
x >= 2 and (x/y) > 2
#> True
x = 1
y = 0
x >= 2 and (x/y) > 2
#> False
x = 6
y = 0
x >= 2 and (x/y) > 2
#> Traceback (most recent call last):
#> File "<pyshell#28>", line 1, in <module>
#> x >= 2 and (x/y) > 2
#> ZeroDivisionError: division by zero
```
:::
세 번째 연산은 일반적으로 실패하는데 이유는 `(x/y)` 연산을 평가할 때 y가 0이어서 실행 오류가 발생된다.
하지만, R에서는 0으로 나누게 되면 `Inf`가 되어 계산결과는 참이 되어 전체적으로 참이 된다.
하지만, 두 번째 예제의 경우 거짓(FALSE)이 되지 않는데 이유는 `x >= 2`이 거짓(FALSE)으로,
전체가 거짓(FALSE)이 되어 단락(Short-circuiting) 평가 규칙에 의해 `(x/y)` 평가는 실행되지 않게 된다.
평가 오류가 발생되기 전에 가디언(guardian) 평가식을 전략적으로 배치해서 논리 표현식을 다음과 같이 구성한다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-var-gardian-pattern
x <- 1
y <- 0
x >= 2 & y != 0 & (x/y) > 2
#> [1] FALSE
x <- 6
y <- 0
x >= 2 & y != 0 & (x/y) > 2
#> [1] FALSE
x >= 2 & (x/y) > 2 & y != 0
#> [1] FALSE
```
### 파이썬
```{pyodide-python}
#| label: py-var-gardian-pattern
x = 1
y = 0
x >= 2 and y != 0 and (x/y) > 2
#> False
x = 6
y = 0
x >= 2 and y != 0 and (x/y) > 2
#> False
x >= 2 and (x/y) > 2 and y != 0
#> Traceback (most recent call last):
#> File "<pyshell#37>", line 1, in <module>
#> x >= 2 and (x/y) > 2 and y != 0
#> ZeroDivisionError: division by zero
```
:::
첫 번째 논리 표현식은 `x >= 2`이 거짓(FALSE)이라 `&`에서 멈춘다.
두 번째 논리 표현식은 `x >= 2`이 참(TRUE), `y != 0`은 거짓(FALSE)이라 `(x/y)`까지 갈 필요가 없다.
세 번째 논리 표현식은 `(x/y)` 연산이 끝난 후에 `y != 0`이 수행되어서 오류가 발생한다.
두 번째 표현식에서 y가 0이 아닐 때만, (x/y)을 실행하도록 `y != 0`이 가디언(guardian) 역할을 수행한다고 말할 수 있다.
## 디버깅 {#r-cont-debugging}
\index{디버깅}
\index{역추적}
오류가 발생했을 때, 화면에 출력되는 역추적(traceback)에는 상당한 정보가 담겨있다.
하지만, 특히 스택에 많은 프레임이 있는 경우 엄청나게 보여 엄두가 나지 않을 수도 있다. 대체로 가장 유용한 정보는 다음과 같은 것이 있다.
- 어떤 종류의 오류인가.
- 어디서 발생했는가.
구문 오류는 대체로 발견하기 쉽지만, 몇 가지는 애매하다. 파이썬의 경우 공백(space)과 탭(tab)의 차이가 눈에 보이지 않아 통상 무시하고 넘어가기 쉽기 때문에
공백 오류를 잡아내기가 까다롭다. R로 텍스트 데이터를 분석할 경우 눈에는 보이지 않지만 공백문자(White space) 문자가 여러 가지 문제를 일으키는 경우가 많다.
특히 한글 인코딩과 결합될 경우 더욱 그렇다.
\index{공백}
대체로 오류 메시지는 문제가 어디에서 발견되었는지를 지칭하지만, 실제 오류는 코드 앞에 종종 선행하는 줄에 있을 수 있다.
동일한 문제가 실행 오류에도 있다. 데시벨(decibels)로 신호 대비 잡음비를 계산한다고 가정하자.
공식은 $\text{SNR}_{db} = 10 \log_{10} (P_{signal} / P_{noise})$이다.
R에서 아래와 같이 작성할 수 있다.
:::{.panel-tabset}
### R
```{webr-r}
#| label: r-var-debug
signal_power <- 9
noise_power <- -10
ratio <- signal_power / noise_power
decibels <- 10 * log10(ratio)
cat("데시벨(db):", decibels)
```
### 파이썬
```{pyodide-python}
#| label: py-var-debug
import math
# 초기값 설정
signal_power = 9
noise_power = -10
# signal-to-noise 비율 계산
ratio = signal_power / noise_power
# 데시벨 계산
decibels = 10 * math.log10(ratio)
print("데시벨(db):", decibels)
```
:::
`signal_power`와 `noise_power`를 부동 소수점값으로 표현되어 R 코드에는 `NaN`를 출력하며 문제가 없지만, 파이썬으로 실행하게 되면 다음과 같은 오류가 나온다.
``` {r r-var-python-debug, eval=FALSE}
Traceback (most recent call last):
File "C:/Users/tidyverse/AppData/Local/Programs/Python/Python311/book.py", line 11, in <module>
decibels = 10 * math.log10(ratio)
ValueError: math domain error
```
오류 메시지가 7번째 줄에 있다고 지칭하지만, 잘못된 것은 없다. 실제 오류를
발견하기 위해서, 출력값이 음수인 `ratio` 값을 `print`문을 사용해서 출력하는 것이
도움이 된다. 문제는 5번째 줄에 있는데, 왜냐하면 `noise_power`가 음수로 설정되어 있어 `signal_power / noise_power` 계산 자체는 문제가 없어 보이지만, 결과적으로 `ratio`는 음수가 된다. 따라서 `math.log10(ratio)` 호출 시 `ValueError`가 발생하는데 로그 함수는 음수에 대해 정의되지 않기 때문이다. 따라서 오류 메시지는 `math.log10` 함수 호출에서 발생하는 것처럼 보이지만, 실제 원인은 `noise_power`에 부적절한 값 설정에 있기 때문에 발생한다.
\index{오류!실행}
\index{실행 오류}
\index{예외!오버플로 오류}
\index{오버플로 오류}
대체로, 오류 메시지는 문제가 어디에서 발견되었는지를 알려주지만,
종종 문제의 원인이 어디에서 발생했는지는 알려주지 않는다.
\index{내림 나눗셈}
\index{나눗셈!내림}
## 용어 정의 {#r-cont-terminology}
- 몸통 부분(body): 복합 문장 내부에 일련의 문장
\index{몸통 부분}
- 부울 표현식(boolean expression): 참(TRUE) 혹은 거짓(FALSE)의 값을 가지는 표현식
\index{부울 표현식}
- 분기(branch): 조건문에서 대안 문장의 한 흐름
\index{분기}
- 연쇄 조건문(chained conditional): 일련의 대안 분기가 있는 조건문
\index{연쇄 조건문}
- 비교 연산자(comparison operator): 피연산자를 ==, !=, >, <, >=, <=로 비교하는 연산자
\index{비교 연산자}
- 조건문(conditional statement): 조건에 따라 명령의 흐름을 제어하는 명령문
\index{조건문}
- 조건(condition): 조건문에서 어느 분기를 실행할지 결정하는 불 표현식
\index{조건}
- 복합문(compound statement): 머리부분(head)과 몸통부분(body)으로 구성된 문장. 머리부분은 콜론(:)으로 끝나며, 몸통부분은 머리부분을 기준으로 들여쓰기로 구별된다.
\index{복합문}
- 가디언 패턴(guardian pattern): 단락(short circuit) 행동을 잘 이용하도록 논리 표현식을 구성하는 것
\index{가디언 패턴}
- 논리 연산자(logical operator): 불 표현식을 결합하는 연산자 중 하나(and, or, not)
\index{논리 연산자}
- 중첩 조건문(nested conditional): 하나의 조건문이 다른 조건문 분기에 나타나는 조건문
\index{중첩 조건문}
- 역추적(traceback): 예외 사항이 발생했을 때 실행되고, 출력되는 함수 리스트
\index{역추적}
- 단락(short circuit): 나머지 표현식 평가를 할 필요 없이 최종 결과를 알기 때문에, 파이썬이 논리 표현식 평가를 진행하는 중간에 평가를 멈출 때
\index{단락}
## 연습문제 {.unnumbered #r-cont-ex}
1. 40시간 이상 일할 경우 시급을 1.5배 더 지급하는 종업원 봉급계산 프로그램을 다시 작성하라.
``` {r}
#| label: r-cont-ex01
#| eval: false
시간을 입력하시오: 35.51
시급을 입력하시오: 7530
알바비: 263550
```
2. `tryCatch`를 사용하여 봉급계산 프로그램을 다시 작성하라.
숫자가 아닌 입력값을 잘 처리해서 숫자 아닌 입력값이 들어왔을 때 메시지를 출력하고 정상적으로 프로그램을 종료하도록 하라.
프로그램 출력 결과는 다음과 같다.
``` {r}
#| label: r-cont-ex02
#| eval: false
시간을 입력하시오: 35.51
시급을 입력하시오: 칠만원
오류, 다시 숫자를 입력하세요
시급을 입력하시오: 7만원
오류, 다시 숫자를 입력하세요
```
3. 0.0과 1.0 사이의 점수를 출력하는 프로그램을 작성하라.
만약 점수가 범위 밖이면 오류를 출력한다.
만약 점수가 0.0과 1.0 사이라면, 다음의 테이블에 따라 등급을 출력한다.
``` {r}
#| label: r-cont-ex03
#| eval: false
점수 등급
>= 0.9 A
>= 0.8 B
>= 0.7 C
>= 0.6 D
< 0.6 F
점수를 입력하시오: 0.95
A
점수를 입력하시오: 만점
올바른 점수가 아닙니다.
점수를 입력하시오: 10.0
올바른 점수가 아닙니다.
점수를 입력하시오: 0.75
C
점수를 입력하시오: 0.5
F
```
4. 상기 보이는 것처럼 반복적으로 프로그램을 실행해서 다양한 다른 입력값을 테스트해 보라.