A Byte of Python - Object Oriented Programming Python


Object Oriented Programming

지금까지 우리가 적성한 프로그램은 데이터를 다루는 문장 블럭과 같이 함수로 프로그램을 설계했다. 이를 프로그램의 프로시져 기반 방법이라 부른다. 프로그램을 조직화하는 다른 방법은 데이터와 기능은 결합해 객체라는 곳에 감싸는 것이다. 이를 객체 지향적 프로그래밍이라고 한다. 대부분에 우리는 프로시져럴 프로그래밍을 사용할 수 있지만, 거대한 프로그램이나 이 방법에 적당한 문제에 대해서는 객체 지향적 프로그래밍 기법을 사용할 수 있다.

클래스와 객체는 객체 지향적 프로그래밍의 기본적인 주요 측면이다. 클래스는 새로운 형식을 만들고 객체는 그 클래스의 인스턴스이다. 이는 int형식 변수가 정수를 저장하지만 이는 int 클래스의 인스턴스이다.

정적 언어 프로그래머를 위한 일러두기

정수가 객체로 처리되었지만, 이는 C++과 자바(버전1.5이전) 에서는 다르며 정수는 프리미티브 네이티브 형식이다.
help(int) 를 통해 클래스의 세부사항을 확인한다.
C#과 자바1.5 프로그래머는 박싱 언박싱 개념과 비슷함을 찾을 것이다.

객체는 객체가 포함하는 일반 변수를 사용하는 데이터를 저장할 수 있다. 객체나 클래스에 소유된 변수는 필드라 한다. 객체는 또한 클래스에 소유된 함수를 사용함으로서 기능성을 가지는데. 이런 함수를 해당 클래스의 메소드라고 한다. 이 용어는 함수와 변수가 독립적인지 아니면 클래스나 객체에 소유되었는지를 구별하는데 도움을 주므로 중요하다. 종합적으로, 필드와 메소드는 클래스의 애트리뷰트로 참조될 수 있다.

필드는 두가지 형식이 있다. - 클래스의 각 인스턴스/객체에 소유되거나 클래스 자체에 소유되는 경우이다. 이들을 인스턴스 변수나 클래스 변수라고 각각 부른다.

클래스는 class 키워드로 생성한다. 필드와 메소드는 인덴트된 블럭에 리스팅된다.

self

클래스 메소드는 보통 함수와는 특별히 다른 점이 있다. 이들은 추가적인 시작이름을 가져 파라미터 리스트의 시작부분에 더해지지만, 이 파라미터에 값을 주지않으며 파이썬이 여기에 제공한다. 이 특정한 변수는 객체 자체에 대한 것으로 이름은 self이다.

비록, 이 파라미터로 어떤 이름이든 줄 수 있지만 self 로 지정하기를 강력히 추천한다. 표준 이름을 지정하면 많은 잇점이 있는데, 쉽게 이해할 수 있다는 점이 있다.

C++/Java/C# 프로그래머를 위한 일러두기
파이썬의 self 는 C++에서의 this 포인터 그리고 자바와 C#에서의 this 레퍼런스와 동등하다

파이썬이 어떻게 self를 위한 값을 제공하는지 이에 대한 값을 직접 넣을 필요가 없는지 궁금할 것이다. 예시를 통해 살펴본다. 클래스를 MyClass라 하고 이 인스턴스를 myobject 라고 하자. 이 객체의 메소드를 myobject.method(arg1,arg2) 로 호출한다. 그러면 파이썬은 자동적으로 MyClass.method(myobject, arg1, arg2) 로 전달하여 해결한다.

이 의미는 만약 메소드가 매개변수가 없더라도 여전히 self 를 위한 하나는 존재한다는 점이다.

클래스

가능한 가장 단순한 클래스는 다음예시이다. (oop_simplestclass.py)

class Person:
  pass # 빈 블럭

p = Person()
print(p)

Output:

$ python oop_simplestclass.py<__main__.Person instance at 0x10171f518>

동작 방식

class 문을 사용해 새로운 클래스와 그 이름을 지정한다. 계속되는 인덴트된 블럭이 클래스의 바디이다. 빈 블록을 가진다면, pass 문을 사용해 지정한다.

다음, 이 클래스에 대한 객체/인스턴스를 클래스이름과 괄호로 생성했다. (다음 센션에서 초기화에 대한 더 자세한 사항을 살펴본다.) 확인을 위해 우리는 단순 프린트로 변수의 형식을 확인한다. 이는 __main__ 모듈의 Person 클래스의 인스턴스임을 나타낸다.

메소드

이미 클래스/객체는 함수와 같은 메소드를 갖는데 추가적인 self변수가 있다고 배웠다. 다음에서 그 예시를 살펴본다.

oop_method.py

class Person:
  def say_hi(self):
    print('Hello, how are you?')

p = Person()
p.say_hi()
# 이전 두 라인은 Person().say_hi() 라고도 쓰여질 수 있다.

Output:

$ python oop_method.pyHello, how are you?
동작 방식

여기에 실제적인 self를 보여준다. say_hi 메소드는 파라미터가 없지만 함수 정의에 self 를 가지고 있음을 보자

__init__ 메소드

파이썬 클래스에서 특별한 메소드 이름이 많은데 그 중의 __init__ 메소드를 지금 살펴본다.

__init__ 메소드는 클래스의 객체가 초기화되고 바로 실행된다. 메소드는 초기화를 할 때 유용하다. 이름의 시작과 끝에 더블언더스코어가 있음을 확인한다.

예(oop_init_py)

class Person:
  def __init__(self, name):
    self.name=name

  def say_hi(self):
    print('Hello, my name is', self.name)

p = Person('Swaroop')
p.say_hi()
# 두 라인은 Person('Swaroop').say_hi() 로도 쓸 수 있다

Output:

$ python oop_init.pyHello, my name is Swaroop
How It WorksHere, we define the __init__ method as taking a parameter name (along with the usual self). Here, we just create a new field also called name. Notice these are two different variables even though they are both called 'name'. There is no problem because the dotted notation self.name means that there is something called "name" that is part of the object called "self" and the other name is a local variable. Since we explicitly indicate which name we are referring to, there is no confusion.When creating new instance p, of the class Person, we do so by using the class name, followed by the arguments in the parentheses: p = Person('Swaroop').We do not explicitly call the __init__ method. This is the special significance of this method.Now, we are able to use the self.name field in our methods which is demonstrated in the say_hi method.
클래스 그리고 객체 변수

We have already discussed the functionality part of classes and objects (i.e. methods), now let us learn about the data part. The data part, i.e. fields, are nothing but ordinary variables that are bound to the namespaces of the classes and objects. This means that these names are valid within the context of these classes and objects only. That's why they are called name spaces.

There are two types of fields - class variables and object variables which are classified depending on whether the class or the object owns the variables respectively.

Class variables are shared - they can be accessed by all instances of that class. There is only one copy of the class variable and when any one object makes a change to a class variable, that change will be seen by all the other instances.

Object variables are owned by each individual object/instance of the class. In this case, each object has its own copy of the field i.e. they are not shared and are not related in any way to the field by the same name in a different instance. An example will make this easy to understand (save as oop_objvar.py):

class Robot:
  """Represents a robot, with a name. """
  # 클래스 변수, 로봇의 갯수를 센다
  population = 0
  def __init__(self,name):
    """Initialize the data."""
    self.name= name
    print("(Initializing {})".format(self.name))
    # 사람이 생성되면 로봇은 인구를 늘린다
    Robot.population += 1

  def die(self):
    """I am dying."""
    print("{} is being destroyed!".format(self.name))
    Robot.population -= 1
    if Robot.population == 0:
      print("{} was the last one.".format(self.name))
    else:
      print("There are still {:d} robots working.".format(Robot.population))

  def say_hi(self):
    """Greeting by the robot.

    Yeah, they can do that."""
    print("Greetings, my masters call me {}.".format(self.name))

  @classmethod
  def how_many(cls):
    """Prints the current population."""
    print("We have {:d} robots.".format(cls.population))

droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()

droid2 = Robot("C-3P0")
droid2.say_hi()
Robot.how_many()

print("\nRobots can do some work here.\n")

print("Robots have finished their work. So let's destroy them.")
droid1.die()
droid2.die()

Robot.how_many()

Output:

$ python oop_objvar.py(Initializing R2-D2)Greetings, my masters call me R2-D2.We have 1 robots.(Initializing C-3PO)Greetings, my masters call me C-3PO.We have 2 robots.Robots can do some work here.Robots have finished their work. So let's destroy them.R2-D2 is being destroyed!There are still 1 robots working.C-3PO is being destroyed!C-3PO was the last one.We have 0 robots.

동작 방식

긴 예시이지만 클래스와 객체 변수의 기능을 보여주는데 도움을 준다. 여기에, population 은 Robot 클래스가 소유하고 이는 클래스 변수이다. name 변수는 객체에 소유되며 이는 객체 변수이다.

그러므로, population 클래스 변수는 self.population이 아닌 Robot.population 으로 접근한다. 객체의 메소드내에 self.name 지정자를 사용해 객체 변수 name에 참조한다. 클래스와 객체 변수 사이의 단순 차이를 기억하자. 객체 변수가 클래스 변수와 이름이 같으면 클래스 변수에 접근할 수 없다.

Robot.population 대신에 self.__class__.population 을 사용할 수도 있는데 모든 객체는 클래스를 self.__class__ 속성으로 참조할 수 있기 때문이다.

how_many는 실제로는 메소드이지만 객체가 아닌 클래스에 속해 있다. 이 뜻은 classmethod 또는 staticmethod 를 사용해 지정되었다는 뜻이다. 클래스 변수라고 했으므로 classmethod 를 사용하자.

how_many 메소드를 decorator 를 사용해 클래스 메소드로 지정했다.

decorator 는 래퍼 함수(다른 함수 주위를 감싸는 함수로서 내부함수 시작이나 이후에 뭔가를 수행한다)를 호출하는 숏컷으로 생각할 수 있다. @classmethod 는 다음과 같은 호출을 수행한다.
how_many = classmethod(how_many)

Robot 인스턴스에서 초기화에 사용하는 __init__메소드를 보자. 이 메소드에서 population을 1만큼 증가시킨다. self.name 의 값은 각 객체에 특정해 객체 변수를 나타낸다.

같은 객체에서는 변수와 메소드에 접근할 때 self를 사용한다. 이 것은 애트립 레퍼런스라 부른다.

이 프로그램에서, 클래스와 메소드에 docstrings을 사용했다. class docstring은 Robot.__doc__ 으로 접근할 수 있으며 메소드 docstring은 Robot.say_hi.__doc__ 으로 사용할 수 있다.

die메소드에서는 간단히 Robot.population 을 1만큼 감소한다.

모든 클래스 멤버는 퍼블릭이다. 한 예외: 만약 이름이 __privatevar 와 같이 더블 언더스코어로 시작하면 파이썬은 이름 맹글링을 사용해 프라이비트 변수로 만든다.

그러므로, 다음 컨벤션은 모든 변수가 클래스에서 또는 객체에서만 사용되면 언더스코어로 싲가하도록 하고 퍼블릭인 다른 이름은 다른 클래스/객체에서 사용할 수 있다. 이는 컨벤션이고 파이썬에의해 강제되지 않음을 기억하자 (더블 언더스코어 프리픽스를 제외하고)

C++/자바/C#프로그래머를 위한 윌러두기
모든 클래스 멤버 (데이터 멤버를 포함) 은 퍼블릭이고 모든 메소드가 파이썬에서는 버추얼이다.

인헤리턴스

객체 지향 프로그래밍의 주요 잇점중에 하나는 코드를 재사용할 수 있다는 점이다. 이를 위한 방법은 상속을 통한 것이다. 상속은 클래스 사이의 형식과 서브형식을 구현함으로서 상상할 수 있다.

대학에서 선생과 학생을 트랙하는 프로그램을 작성한다고 하자. 이들은 이름, 나이, 주소와 같은 공통적인 속성이 있다. 또한 월급, 코스, 레벨은 선생에게 마크와 비용은 학생에게 있다.

두개의 독립적 클래스를 각 형식과 처리에 생성한다면 이들 독립 클래스에 공통적 부분도 양쪽에 추가되어야 함을 의미한다. 이는 좋지 않다.

더 나은 방법은 SchoolMember 라는 클래스를 생성하고 선생과 학생 클래스가 이들을 상속하는 것이다. 예를들어, 이들 형식(클래스)의 부분형식이되고 이들 부분 형식에 특정 속성을 추가할 수 있다.

이 접근은 많은 잇점을 제공한다. 만약 SchoolMember에 기능을 추가/변경하면 이는 자동적으로 부분형식에도 적용된다는 점이다. 예를들어, 새로운 ID카드 필드를 선생과 학생 양쪽에 추가한다면 그저 SchoolMember클래스에만 추가하면된다. 그러나, 서브타입에서의 변화는 다른 서브타입에서 영향 주지 않는다. 다른 잇점은 선생이나 학생인 객체를 SchoolMember 객체로 참조할 수 있어 스쿨 멤버의 숫자를 계산하는 것과 같은 몇몇 상황에서 유용하게 사용할 수 있다. 이를 폴리모피즘이라고 하는데 부분형식은 상황에 따라 부모형식으로 대입될 수 있다.

부모 클래스의 코드를 재사용하여 독립적인 다른 클래스에서 라면 추가로 해야할 작업을 할 필요가 없다는 점이다.

이 상황에서의 SchoolMember 클래스는 기반 클래스 또는 슈퍼클래스라고한다. Teacher, Student 클래스는 파생클래스 또는 서브클래스라고 한다.

이 예시를 프로그램으로 확인한다
oop_subclass.py

class SchoolMember:
  '''Represents any school member. '''
  def __init__(self, name, age):
    self.name = name
    self.age = age
    print('(Initialized SchoolMember: {})'.format(self.name))
  def tell(self):
    '''Tell my details'''
    print('Name: "{}" Age: "{}"'.format(self.name, self.age), end=" ")

class Teacher(SchoolMember):
  '''Represents a teacher.'''
  def __init__(self,name,age,salary):
    SchoolMember.__init__(self,name,age)
    self.salary = salary
    print('(Initialized Teacher: {})'.format(self.name))
  def tell(self):
    SchoolMember.tell(self)
    print('Salary: "{:d}"'.format(self.salary))

class Student(SchoolMember):
  '''Represents a student'''
  def __init__(self, name, age, marks):
    SchoolMember.__init__(self, name, age)
    self.marks = marks
    print('(Initialized Student: {})'.format(self.name))

  def tell(self):
    SchoolMember.tell(self)
    print('Marks: "{:d}"'.format(self.marks))

t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)

print()

members = [t,s]
for member in members:
  member.tell()

Output:

$ python oop_subclass.py(Initialized SchoolMember: Mrs. Shrividya)(Initialized Teacher: Mrs. Shrividya)(Initialized SchoolMember: Swaroop)(Initialized Student: Swaroop)Name:"Mrs. Shrividya" Age:"40" Salary: "30000"Name:"Swaroop" Age:"25" Marks: "75"

동작 방식

상속을 사용하려면 클래스 정의에서 클래스 이름 다음에 튜플에 기반 클래스 이름을 지정한다. (예, class Teacher(SchoolMember)) 다음으로, 기반 클래스의 __init__  메소드는 self 변수로 명시적으로 호출되어 기반 클래스를 초기화한다. 이 부분은 중요한 부분인데, Teacher와 Student 부분클래스에서 __init__메소드를 정의함으로서 파이썬은 SchoolMember의 생성자를 자동적으로 호출하지 않고 명시적으로 직접 호출해야 한다는점이다.

대조적으로, 만약 서브클래스에서 __init__메소드를 정의하지 않으면 파이썬은 자동적으로 베이스클래스의 생성자를 호출한다.

Teacher또는 Student 의 인스턴스를 SchoolMember인스턴스로 처리할 수 있으므로 Teacher.tell 또는 Student.tell 로 타이핑하여 SchoolMember의 tell메소드에 접근할 수 있다. 이를 수행함에 따라 Teacher.tell 을 작성할 때 파이썬은 슈퍼클래스대신 부분클래스의 tell메소드를 사용한다. 만약 서브클래스에 tell메소드가 없다면 파이썬은 슈퍼클래스의 tell메소드를 사용한다. 파이썬은 항상 먼저 실제 서브클래스의 메소드를 먼저 살펴보고 없으면 베이스클래스의 메소드를 살피며 튜플에 지정한 하나하나에 확읺나다. (지금은 하나의 베이스 클래스만 있지만 다중 베이스클래스를 지정할 수 있다)

용어에 대해 일러두기 - 상속 튜플에 하나이상을 지정하면 다중 상속이라 부른다.

end파라미터는 슈퍼클래스의 tell() 메소드내에서 print함수에서 사용되어 같은 라인에 다음 프린트가계속되도록 할 수 있다. 이 것은 print가 \n를 프린트 끝에 프린트하지 않게 한다.

갈무리

We have now explored the various aspects of classes and objects as well as the various terminologies associated with it. We have also seen the benefits and pitfalls of object-oriented programming. Python is highly object-oriented and understanding these concepts carefully will help you a lot in the long run.

Next, we will learn how to deal with input/output and how to access files in Python.














덧글

댓글 입력 영역