pdb 모듈을 사용하거나 Jupyter 코드 셀에서 %debug를 사용하여 Python 코드를 디버깅합니다.
Python에서 ’클래스’와 ’함수’의 차이점을 설명하세요.
클래스를 만들 수 있습니다.
’인스턴스 속성’과 ’클래스 속성’을 구분합니다.
‘메서드’, ‘클래스 메소드’, ’정적 메소드’를 구분합니다.
Python 클래스를 사용하여 서브클래싱/상속을 이해하고 구현합니다.
1. 단위 테스트
지난 장에서 우리는 Python 함수에 대해 논의했습니다. 하지만 우리의 함수가 우리가 기대하는 대로 정확히 수행되고 있는지 어떻게 확신할 수 있습니까? 단위 테스트는 우리가 기대하는 결과를 제공하는지 확인하기 위해 함수를 테스트하는 프로세스입니다. 여기서는 개념을 간략하게 소개하겠습니다.
assert 문
‘assert’ 문은 함수를 테스트하는 가장 일반적인 방법입니다. 테스트된 조건이 False인 경우 프로그램이 실패하게 됩니다. 구문은 다음과 같습니다.
``파이썬 Assert 표현식, “표현식이 False이거나 오류를 발생시키는 경우 오류 메시지.” ````
assert1==2, "1 is not equal to 2."
---------------------------------------------------------------------------AssertionError Traceback (most recent call last)
<ipython-input-1-74b71e52d7cf> in <module>----> 1assert1==2,"1 is not equal to 2."AssertionError: 1 is not equal to 2.
두 숫자가 대략 같다고 주장하는 것도 도움이 될 수 있습니다. 컴퓨터의 부동 소수점 연산의 한계로 인해 우리가 같을 것으로 기대하는 숫자가 때때로 그렇지 않을 수 있습니다.
assert0.1+0.2==0.3, "Not equal!"
---------------------------------------------------------------------------AssertionError Traceback (most recent call last)
<ipython-input-2-be33c16f53c8> in <module>----> 1assert0.1+0.2==0.3,"Not equal!"AssertionError: Not equal!
import math # we'll learn about importing modules next chapterassert math.isclose(0.1+0.2, 0.3), "Not equal!"
부울로 평가되는 모든 명령문을 테스트할 수 있습니다.
assert"varada"in ["mike", "tom", "tiffany"], "Instructor not present!"
---------------------------------------------------------------------------AssertionError Traceback (most recent call last)
<ipython-input-5-ff5cfa3cbd54> in <module>----> 1assert'varada'in['mike','tom','tiffany'],"Instructor not present!"AssertionError: Instructor not present!
테스트 주도 개발
TDD(테스트 기반 개발)는 실제 기능을 수행하기 전에 테스트를 작성하는 곳입니다(자세한 내용은 여기). 이는 다소 반직관적으로 보일 수 있지만 실제 함수 이전에 함수에 대한 기대치를 생성하는 것입니다. 이는 여러 가지 이유로 도움이 될 수 있습니다. - 어떤 코드를 작성해야 하는지 정확히 이해하게 될 것입니다. - 미리 테스트를 작성해야 합니다. - 시간이 많이 걸리는 버그가 발생하지 않습니다. 그리고, - 작고 점진적인 코드 개선 및 추가에 중점을 두어 작업 흐름을 관리 가능하게 유지하는 데 도움이 됩니다.
일반적으로 접근 방식은 다음과 같습니다. 1. 스텁(stub) 작성: 모든 입력 매개변수를 받아들이고 올바른 데이터 유형을 반환하는 함수만 작성합니다. 2. 설계 사양을 충족하는 테스트를 작성합니다. 3. 의사 코드를 사용하여 프로그램 개요를 작성합니다. 4. 코드를 작성하고 자주 테스트하십시오. 5. 문서를 작성하세요.
EAFP 대 LBYL
테스트 및 기능 설계와 다소 관련이 있는 철학은 EAFP 및 LBYL입니다. EAFP = “허가보다 안개를 요청하는 것이 더 쉽습니다”. 코딩 용어에서: 뭔가를 시도해 보고, 작동하지 않으면 오류를 잡아보세요. LBYL = “도약하기 전에 살펴보세요”. 코딩 용어: 무언가를 시도하기 전에 먼저 할 수 있는지 확인하세요. 이 두 약어는 코드 작성 방법에 대한 코딩 철학을 나타냅니다. 예를 살펴보겠습니다:
d = {"name": "Doctor Python","superpower": "programming","weakness": "mountain dew","enemies": 10,}
# LBYLif"address"in d.keys(): d["address"]else:print("Saved you before you leapt!")
Saved you before you leapt!
EAFP는 종종 Python에서 보증되지만 코딩에는 옳고 그른 방법이 없으며 상황에 따라 달라지는 경우가 많습니다. 나는 개인적으로 대부분의 경우 두 가지 철학을 혼합합니다.
2. 디버깅
그렇다면 Python 코드가 작동하지 않으면 어떻게 해야 할까요? 현재 대부분의 사용자는 “수동 테스트” 또는 “탐색 테스트”를 수행할 것입니다. 작동할 때까지 코드를 계속 변경하고, 문제를 격리하기 위해 인쇄 문을 주변에 추가할 수도 있습니다. 예를 들어, COS 126, 조건문 및 루프의 허가를 받아 조정된 아래 random_walker 코드를 고려해 보세요.
from random import randomdef random_walker(T):""" Simulates T steps of a 2D random walk, and prints the result after each step. Returns the squared distance from the origin. Parameters ---------- T : int The number of steps to take. Returns ------- float The squared distance from the origin to the endpoint, rounded to 2 decimals. Examples -------- >>> random_walker(3) (0, -1) (0, 0) (0, -1) 1.0 """ x =0 y =0for i inrange(T): rand = random()if rand <0.25: x +=1if rand <0.5: x -=1if rand <0.75: y +=1else: y -=1print((x, y))returnround((x**2+ y**2) **0.5, 2)random_walker(5)
(-1, 1)
(-2, 2)
(-3, 3)
(-3, 4)
(-3, 3)
4.24
위의 코드를 다시 실행하면 랜덤 워커가 절대 오른쪽으로 가지 않습니다(x 좌표는 절대 +ve가 아닙니다). 무슨 일이 일어나고 있는지 확인하기 위해 여기에 몇 가지 인쇄 문을 추가하려고 할 수 있습니다.
def random_walker(T):""" Simulates T steps of a 2D random walk, and prints the result after each step. Returns the squared distance from the origin. Parameters ---------- T : int The number of steps to take. Returns ------- float The squared distance from the origin to the endpoint, rounded to 2 decimals. Examples -------- >>> random_walker(3) (0, -1) (0, 0) (0, -1) 1.0 """ x =0 y =0for i inrange(T): rand = random()print(rand)if rand <0.25:print("I'm going right!") x +=1if rand <0.5:print("I'm going left!") x -=1if rand <0.75: y +=1else: y -=1print((x, y))returnround((x**2+ y**2) **0.5, 2)random_walker(5)
아! 우리는 나는 오른쪽으로 가고 있어요!'' 이후에도 즉시나는 왼쪽으로 가고 있어요!’’라는 메시지를 받는 것을 봅니다. 문제는 if 문에 있습니다. 초기 if 다음에 각 문에 elif를 사용해야 합니다. 그렇지 않으면 매번 여러 조건이 충족될 수 있습니다.
이것은 매우 간단한 디버깅 사례였으며, print 문을 추가하는 것이 항상 도움이 되거나 효율적인 것은 아닙니다. 대안으로 pdb 모듈을 사용할 수 있습니다. pdb는 Python 디버거입니다. 표준 라이브러리에 포함되어 있습니다. breakpoint()를 사용하여 pdb를 활용하고 코드의 어느 지점에나 “중단점”을 설정한 다음 변수를 검사할 수 있습니다. 디버거 콘솔과 상호 작용하는 데 도움이 필요하면 pdb 문서 여기 및 이 치트시트를 참조하세요.
def random_walker(T):""" Simulates T steps of a 2D random walk, and prints the result after each step. Returns the squared distance from the origin. Parameters ---------- T : int The number of steps to take. Returns ------- float The squared distance from the origin to the endpoint, rounded to 2 decimals. Examples -------- >>> random_walker(3) (0, -1) (0, 0) (0, -1) 1.0 """ x =0 y =0for i inrange(T): rand = random()breakpoint()if rand <0.25:print("I'm going right!") x +=1if rand <0.5:print("I'm going left!") x -=1if rand <0.75: y +=1else: y -=1print((x, y))returnround((x**2+ y**2) **0.5, 2)random_walker(5)
> <ipython-input-11-005ed635a05e>(31)random_walker() 29 rand = random() 30 breakpoint()---> 31 if rand <0.25: 32 print("I'm going right!") 33 x +=1
def random_walker(T):""" Simulates T steps of a 2D random walk, and prints the result after each step. Returns the squared distance from the origin. Parameters ---------- T : int The number of steps to take. Returns ------- float The squared distance from the origin to the endpoint, rounded to 2 decimals. Examples -------- >>> random_walker(3) (0, -1) (0, 0) (0, -1) 1.0 """ x =0 y =0for i inrange(T): rand = random()if rand <0.25: x +=1elif rand <0.5: x -=1elif rand <0.75: y +=1else: y -=1print((x, y))returnround((x**2+ y**2) **0.5, 2)random_walker(5)
(-1, 0)
(-1, -1)
(-2, -1)
(-3, -1)
(-3, -2)
3.61
표준 Python 디버거이기 때문에 pdb를 보여주고 싶었습니다. 대부분의 Python IDE에는 자체 디버깅 워크플로도 있습니다. 예를 들어 VSCode에서 디버깅에 대한 자습서는 다음과 같습니다. Jupyter 내에는 사용할 수 있는 몇 가지 “magic” 명령이 있습니다. 여기서 우리가 관심을 갖는 것은 %debug입니다. 이를 사용할 수 있는 방법은 몇 가지가 있지만 가장 쉬운 방법은 셀에 오류가 발생하는 경우 아래에 새 셀을 만들고 ’%debug’를 작성하고 해당 셀을 실행하여 이전 오류를 디버그하는 것입니다.
x =1x +"string"
---------------------------------------------------------------------------TypeError Traceback (most recent call last)
<ipython-input-13-496b359b128e> in <module> 1 x =1----> 2x +'string'TypeError: unsupported operand type(s) for +: 'int' and 'str'
%debug
> <ipython-input-13-496b359b128e>(2)<module>() 1 x =1----> 2 x +'string'
우리는 Python에 내장된 dict 및 list와 같은 데이터 유형을 보았습니다. 우리는 또한 우리 자신의 데이터 유형을 만들 수도 있습니다. 이를 클래스라고 하며 클래스의 인스턴스를 객체라고 합니다(클래스 문서 여기). 클래스와 객체를 사용하는 프로그래밍에 대한 일반적인 접근 방식을 객체 지향 프로그래밍이라고 합니다.
d =dict()
여기서 d는 객체이고 dict는 유형입니다.
type(d)
dict
type(dict)
type
d는 유형dict의 인스턴스라고 말합니다. 따라서:
isinstance(d, dict)
True
자신만의 유형/클래스를 만드는 이유는 무엇입니까?
“클래스는 데이터와 기능을 함께 묶는 수단을 제공합니다”(Python 문서). 이는 사용, 재사용 및 구축이 쉬운 방식입니다. 예제를 통해 클래스의 유용성을 발견하는 것이 가장 쉬우므로 시작해 보겠습니다!
브리티시 컬럼비아 대학의 데이터 과학 석사 프로그램(MDS)에서 학생과 강사에 대한 정보를 저장하기 시작한다고 가정해 보겠습니다.
Note
이 사이트의 콘텐츠는 제가 브리티시 컬럼비아 대학의 데이터 과학 석사 프로그램을 위한 “DSCI 511 데이터 과학을 위한 Python 프로그래밍” 과정의 2020/2021 제공물을 가르치기 위해 사용한 자료에서 채택되었다는 점을 기억하세요.
위의 내용은 매우 비효율적이었습니다. 우리가 원하는 객체가 많아지고 객체가 더 복잡해질수록(더 많은 데이터, 더 많은 기능) 이 문제는 더욱 악화된다는 것을 상상할 수 있습니다! 그러나 이것은 수업에 대한 완벽한 사용 사례입니다! 클래스는 객체(이 경우 MDS 멤버)를 생성하기 위한 청사진으로 생각할 수 있습니다.
용어 경고: - 클래스 데이터 = “속성” - 클래스 함수 = “메서드”
구문 경고: - class 키워드와 이름, 콜론(:)을 사용하여 클래스를 정의합니다.
class mds_member:pass
mds_1 = mds_member()type(mds_1)
__main__.mds_member
예를 들어 인스턴스에 데이터를 추가하기 위해 새 인스턴스를 만들 때마다 실행되는 __init__ 메서드를 클래스에 추가할 수 있습니다. mds_member 클래스에 __init__ 메소드를 추가해 보겠습니다. self는 클래스의 인스턴스를 참조하며 항상 클래스 메서드의 첫 번째 인수로 전달되어야 합니다.
class mds_member:def__init__(self, first, last):# the below are called "attributes"self.first = firstself.last = lastself.email = first.lower() +"."+ last.lower() +"@mds.com"
‘속성’이 아닌 ’메서드’(함수로 생각)를 호출하기 때문에 위의 괄호가 필요하다는 점에 유의하세요.
인스턴스 및 클래스 속성
mds_1.first와 같은 속성을 인스턴스 속성이라고도 합니다. 이는 우리가 만든 개체에만 적용됩니다. 그러나 클래스의 모든 인스턴스에서 동일한 클래스 속성을 설정할 수도 있습니다. 이 속성은 __init__ 메소드 외부에서 정의됩니다.
class mds_member: role ="MDS member"# class attributes campus ="UBC"def__init__(self, first, last):self.first = firstself.last = lastself.email = first.lower() +"."+ last.lower() +"@mds.com"def full_name(self):returnf"{self.first}{self.last}"
우리 클래스의 모든 인스턴스는 클래스 속성을 공유합니다.
mds_1 = mds_member("Tom", "Beuzen")mds_2 = mds_member("Joel", "Ostblom")print(f"{mds_1.first} is at campus {mds_1.campus}.")print(f"{mds_2.first} is at campus {mds_2.campus}.")
Tom is at campus UBC.
Joel is at campus UBC.
인스턴스가 생성된 후에도 클래스 속성을 변경할 수도 있습니다. 이는 생성된 모든 인스턴스에 영향을 미칩니다.
mds_1 = mds_member("Tom", "Beuzen")mds_2 = mds_member("Mike", "Gelbart")mds_member.campus ="UBC Okanagan"print(f"{mds_1.first} is at campus {mds_1.campus}.")print(f"{mds_2.first} is at campus {mds_2.campus}.")
Tom is at campus UBC Okanagan.
Mike is at campus UBC Okanagan.
단일 인스턴스에 대해서만 클래스 속성을 변경할 수도 있습니다. 그러나 인스턴스에 대해 서로 다른 속성을 원할 경우 ’인스턴스 속성’을 사용해야 하기 때문에 이는 일반적으로 권장되지 않습니다.
class mds_member: role ="MDS member" campus ="UBC"def__init__(self, first, last):self.first = firstself.last = lastself.email = first.lower() +"."+ last.lower() +"@mds.com"def full_name(self):returnf"{self.first}{self.last}"
mds_1 = mds_member("Tom", "Beuzen")mds_2 = mds_member("Mike", "Gelbart")mds_1.campus ="UBC Okanagan"print(f"{mds_1.first} is at campus {mds_1.campus}.")print(f"{mds_2.first} is at campus {mds_2.campus}.")
Tom is at campus UBC Okanagan.
Mike is at campus UBC.
메서드, 클래스 메서드 및 정적 메서드
지금까지 본 메서드는 때때로 “일반” 메소드라고 불리며 클래스의 인스턴스에 대해 작동합니다(즉, self를 인수로 사용). 실제 클래스에 작용하는 ’클래스 메소드’도 있습니다. 클래스 메소드는 종종 “대체 생성자”로 사용됩니다. 예를 들어, 일반적으로 누군가가 다음과 같이 쉼표로 구분된 이름으로 우리 클래스를 사용하기를 원한다고 가정해 보겠습니다.
first, last = name.split(",")print(first)print(last)
Tom
Beuzen
그런 다음 우리 클래스의 인스턴스를 만들 수 있습니다.
mds_1 = mds_member(first, last)
이것이 우리 코드 사용자의 일반적인 사용 사례라면 우리는 그들이 우리 클래스를 사용하기 전에 매번 데이터를 강제할 필요가 없기를 바랍니다. 대신 클래스 메소드를 사용하여 사용 사례를 용이하게 할 수 있습니다. 클래스 메소드를 사용하려면 다음 두 가지 작업을 수행해야 합니다. 1. 데코레이터 @classmethod를 사용하여 메소드를 클래스 메소드로 식별합니다(데코레이터에 대해 조금 더 자세히 설명). 2. 첫 번째 인수로 self 대신 cls를 전달합니다.
class mds_member: role ="MDS member" campus ="UBC"def__init__(self, first, last):self.first = firstself.last = lastself.email = first.lower() +"."+ last.lower() +"@mds.com"def full_name(self):returnf"{self.first}{self.last}"@classmethoddef from_csv(cls, csv_name): first, last = csv_name.split(",")return cls(first, last)
’정적 메소드’라는 세 번째 종류의 메소드가 있습니다. 정적 메소드는 인스턴스나 클래스에서 작동하지 않으며 단순한 함수일 뿐입니다. 하지만 그것들이 우리 수업과 어느 정도 관련이 있기 때문에 우리 수업에 포함시키고 싶을 수도 있습니다. @staticmethod 데코레이터를 사용하여 정의됩니다.
class mds_member: role ="MDS member" campus ="UBC"def__init__(self, first, last):self.first = firstself.last = lastself.email = first.lower() +"."+ last.lower() +"@mds.com"def full_name(self):returnf"{self.first}{self.last}"@classmethoddef from_csv(cls, csv_name): first, last = csv_name.split(",")return cls(first, last)@staticmethoddef is_quizweek(week):returnTrueif week in [3, 5] elseFalse
is_quizweek() 메소드는 self 인수를 허용하거나 사용하지 않습니다. 하지만 여전히 MDS와 관련되어 있으므로 여기에 포함시키고 싶을 수도 있습니다.
mds_1 = mds_member.from_csv("Tom,Beuzen")print(f"Is week 1 a quiz week? {mds_1.is_quizweek(1)}")print(f"Is week 3 a quiz week? {mds_1.is_quizweek(3)}")
Is week 1 a quiz week? False
Is week 3 a quiz week? True
데코레이터
데코레이터는 꽤 복잡한 주제일 수 있습니다. 데코레이터에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 간단히 말하면, 함수/메서드를 추가 기능으로 “장식”하는 것입니다. 데코레이터는 다른 기능을 취하고 기능을 추가하는 함수로 생각할 수 있습니다.
예를 들어 데코레이터를 만들어 보겠습니다. 함수는 Python의 데이터 유형이며 다른 함수에 전달될 수 있다는 점을 기억하세요. 따라서 데코레이터는 단순히 함수를 인수로 사용하고, 여기에 더 많은 기능을 추가하고, 실행될 수 있는 “장식된 함수”를 반환합니다.
# some function we wish to decoratedef original_func():print("I'm the original function!")# a decoratordef my_decorator(original_func): # takes our original function as inputdef wrapper(): # wraps our original function with some extra functionalityprint(f"A decoration before {original_func.__name__}.") result = original_func()print(f"A decoration after {original_func.__name__}.")return resultreturn ( wrapper # returns the unexecuted wrapper function which we can can excute later )
A decoration before original_func.
I'm the original function!
A decoration after original_func.
데코레이터를 사용하여 임의의 기능을 꾸밀 수 있습니다.
def another_func():print("I'm a different function!")my_decorator(another_func)()
A decoration before another_func.
I'm a different function!
A decoration after another_func.
데코레이터를 호출하는 구문은 읽기 쉽지 않습니다. 대신 @ 기호를 “구문적 설탕”으로 사용하여 데코레이터의 가독성과 재사용성을 향상시킬 수 있습니다.
@my_decoratordef one_more_func():print("One more function...")one_more_func()
A decoration before one_more_func.
One more function...
A decoration after one_more_func.
좋아요, 좀 더 유용한 것을 만들어 보겠습니다. 임의 함수의 실행 시간을 측정하는 데코레이터를 만듭니다.
import time # import the time module, we'll learn about imports next chapterdef timer(my_function): # the decoratordef wrapper(): # the added functionality t1 = time.time() result = my_function() # the original function t2 = time.time()print(f"{my_function.__name__} ran in {t2 - t1:.3f} sec" ) # print the execution timereturn resultreturn wrapper
@timerdef silly_function():for i inrange(10_000_000):if (i %1_000_000) ==0:print(i)else:passsilly_function()
0
1000000
2000000
3000000
4000000
5000000
6000000
7000000
8000000
9000000
silly_function ran in 0.601 sec
classmethod 및 staticmethod와 같은 Python의 내장 데코레이터는 C로 코딩되어 있으므로 여기에 표시하지 않습니다. 저는 자체 데코레이터를 자주 만들지는 않지만 항상 내장된 데코레이터를 사용합니다.
상속과 서브클래스
들리는 것처럼 상속을 통해 다른 클래스의 메서드와 속성을 “상속”할 수 있습니다. 지금까지 우리는 mds_member 클래스를 사용해 작업해왔습니다. 하지만 좀 더 구체적으로 mds_student 및 mds_instructor 클래스를 만들어 보겠습니다. 이것이 mds_member였다는 것을 기억하세요:
class mds_member: role ="MDS member" campus ="UBC"def__init__(self, first, last):self.first = firstself.last = lastself.email = first.lower() +"."+ last.lower() +"@mds.com"def full_name(self):returnf"{self.first}{self.last}"@classmethoddef from_csv(cls, csv): first, last = csv_name.split(",")return cls(first, last)@staticmethoddef is_quizweek(week):returnTrueif week in [3, 5] elseFalse
mds_member 클래스를 mds_student 클래스 정의에 인수로 전달하면 mds_member 클래스의 모든 속성과 메서드를 상속하는 mds_student 클래스를 만들 수 있습니다.
여기서 일어난 일은 mds_student 인스턴스가 먼저 mds_student 클래스에서 __init__ 메서드를 찾았지만 찾지 못했다는 것입니다. 그런 다음 상속된 mds_member 클래스에서 __init__ 메소드를 찾고 사용할 것을 찾았습니다! 이 순서를 “메서드 해결 순서”라고 합니다. help() 함수를 사용하여 직접 검사할 수 있습니다.
help(mds_student)
Help on class mds_student in module __main__:
class mds_student(mds_member)
| mds_student(first, last)
|
| Method resolution order:
| mds_student
| mds_member
| builtins.object
|
| Methods inherited from mds_member:
|
| __init__(self, first, last)
| Initialize self. See help(type(self)) for accurate signature.
|
| full_name(self)
|
| ----------------------------------------------------------------------
| Class methods inherited from mds_member:
|
| from_csv(csv) from builtins.type
|
| ----------------------------------------------------------------------
| Static methods inherited from mds_member:
|
| is_quizweek(week)
|
| ----------------------------------------------------------------------
| Data descriptors inherited from mds_member:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| ----------------------------------------------------------------------
| Data and other attributes inherited from mds_member:
|
| campus = 'UBC'
|
| role = 'MDS member'
좋아요, mds_student 클래스를 미세 조정해 보겠습니다. 가장 먼저 할 일은 학생 인스턴스의 역할을 “MDS Student”로 변경하는 것입니다. 간단히 mds_student 클래스에 class attribute를 추가하면 됩니다. mds_student 클래스에서 “재정의”되지 않은 모든 속성이나 메소드는 mds_member 클래스에서 상속됩니다.
class mds_student(mds_member): role ="MDS student"
이제 grade라는 클래스에 instance 속성을 추가해 보겠습니다. 다음과 같이 하고 싶은 유혹을 느낄 수도 있습니다.
class mds_student(mds_member): role ="MDS student"def__init__(self, first, last, grade):self.first = firstself.last = lastself.email = first.lower() +"."+ last.lower() +"@mds.com"self.grade = gradestudent_1 = mds_student("John", "Smith", "B+")print(student_1.email)print(student_1.grade)
john.smith@mds.com
B+
괄호는 생략될 수 있으며(종종 생략됨), ’튜플’은 쉼표를 사용하여 정의된 대로 암시적으로 반환됩니다.
class mds_student(mds_member): role ="MDS student"def__init__(self, first, last, grade):super().__init__(first, last)self.grade = gradestudent_1 = mds_student("John", "Smith", "B+")print(student_1.email)print(student_1.grade)
john.smith@mds.com
B+
놀라운! 상속이 얼마나 강력한지 알 수 있기를 바랍니다. add_course()와 remove_course()라는 두 개의 새로운 메소드가 있는 mds_instructor라는 또 다른 하위 클래스를 만들어 보겠습니다.
class mds_instructor(mds_member): role ="MDS instructor"def__init__(self, first, last, courses=None):super().__init__(first, last)self.courses = [] if courses isNoneelse coursesdef add_course(self, course):self.courses.append(course)def remove_course(self, course):self.courses.remove(course)
아아… 이메일이 새 이름으로 업데이트되지 않았습니다! full_name() 메서드에서는 현재 이름과 성 이름만 호출하기 때문에 이 문제가 발생하지 않았습니다. 여기서 가장 좋은 일은 full_name()에 대해 했던 것처럼 email()에 대한 메소드를 만드는 것이라고 생각할 수도 있습니다. 그러나 이는 여러 가지 이유로 잘못된 코딩입니다. 예를 들어 코드 사용자는 email 속성에 대한 모든 호출을 email() 메서드에 대한 호출로 변경해야 함을 의미합니다. 우리는 이를 소프트웨어에 대한 획기적인 변경이라고 부르며 가능한 경우 이를 피하고 싶습니다. 대신에 우리가 할 수 있는 일은 email을 메소드처럼 정의하고 @property 데코레이터를 사용하여 속성으로 유지하는 것입니다.
---------------------------------------------------------------------------AttributeError Traceback (most recent call last)
<ipython-input-71-74e4ce79e805> in <module>----> 1mds_1.full_name ='Thomas Beuzen'AttributeError: can't set attribute
오류가 발생합니다… 클래스 인스턴스는 전달된 값으로 무엇을 해야할지 모릅니다. 이상적으로는 클래스 인스턴스가 이 전체 이름 정보를 사용하여 self.first 및 self.last를 업데이트하는 것이 좋습니다. 이 작업을 처리하려면 데코레이터 @<attribute>.setter를 사용하여 정의된 setter가 필요합니다.
거의 다 왔어요! 정보를 얻고 정보를 설정하는 것에 대해 이야기했는데 정보를 삭제하는 것은 어떻습니까? 이는 일반적으로 일부 정리를 수행하는 데 사용되며 @<attribute>.deleter 데코레이터로 정의됩니다. 나는 이 방법을 거의 사용하지 않지만 여러분이 이 방법을 보셨으면 합니다.