Skip to content

Latest commit

 

History

History
240 lines (209 loc) · 9.56 KB

211107.md

File metadata and controls

240 lines (209 loc) · 9.56 KB

Day 36

한 권으로 개발자가 원하던 파이썬 심화 A to Z

개체와 클래스의 관계 확인

클래스 정의

class Klass(object):
    pass

클래스와 최상위 클래스 object 간의 상속 관계 확인

issubclass(Klass, object) # True

클래스와 메타 클래스 간의 상속 관계 확인

issubclass(Klass, type) # False

파이썬에서 작성된 모든 클래스는 메타 클래스로 만들어져 있다.
그에 따라 Klass와 메타 클래스의 관계는 생성 관계가 된다.

isinstance(Klass, type) # True

최상위 클래스 object를 모든 클래스가 상속한다.
최상위 클래스는 object만 있다.

모든 클래스, 객체는 메타 클래스로 생성된다.
객체는 항상 클래스로 만들어진다.
모든 클래스이기 때문에 최상위 클래스도 메타 클래스로 생성된다.

isinstance(object, type) # True, 최상위 클래스도 메타 클래스로 생성
isinstance(int, type) # True, 정수 클래스도 메타 클래스로 생성

k = Klass()
isinstance(k, object) # True

객체 내부 검사

클래스와 객체별로 별도의 이름공간이 있다.
객체는 클래스 상속 관계에 있는 이름공간을 모두 참조해서 사용할 수 있고, dir 함수로 조회하면 객체의 이름공간보다 더 많은 정보를 확인할 수 있다.

클래스 정의

class Klass(object):
    클래스속성 = "Klass" # 속성도 한글 이름을 쓸 수 있다.

    def __init__(self, name):
        self.인스턴스속성 = name

k = Klass("Cat")

변수에 할당된 객체를 조회하면 클래스 이름 뒤에 레퍼런스가 16진수로 표시되는데 내장함수 id로 객체의 레퍼런스를 조회하면 10진수로 변환해준다.

id(k)

객체가 어떤 클래스로 생성되었는지 확인

type(k)
k.__class__

객체는 객체를 만든 클래스와 이 클래스가 상속한 상위 클래스 전체의 이름공간을 참조할 수 있다.
내장함수 dir에 객체를 인자로 전달하면 클래스에 없는 다양한 속성과 메소드를 출력한다.

count = 0
for i in dir(k):
    print(i, end=', ')
    count += 1
    if count % 5 == 0:
        print()

클래스 이름공간과 객체 이름공간에 보관하는 것은 __dict__로 조회한다.

Klass.__dict__ # 클래스의 이름공간

k.__dict__ # 객체의 이름공간

아무것도 하지 않는 클래스를 정의할 때, 상속한 object 클래스에 초기화 함수가 있어 이를 호출해서 사용하고, 클래스와 객체에서 초기화 함수에 접근하면 함수와 메소드로 구별된다.

object.__init__ # 최상위 클래스의 초기화 함수 조회
object().__init__ # 최상위 클래스로 객체를 생성해서 초기화 메소드 조회

__init__은 클래스에 정의된 것은 함수, 객체에서 접근하면 메소드이다.

import types # 함수와 메소드 클래스를 가진 모듈

type(Klass.__init__) == types.FunctionType # True, 함수
type(k.__init__) == types.MethodType # True, 메소드

객체 레퍼런스 비교 방식

객체는 항상 유일해야 한다.
객체는 유일성을 유지하기 위해 레퍼런스를 가진다.
레퍼런스로 객체의 유일성을 관리하지만, 파이썬 내부적으로 유일성을 준수하지 않는 경우가 발생한다.

정수는 항상 유일한 객체를 만들고 같은 정수의 값은 항상 유일한 객체이다.

i = 300
a = 300

i is a # False, 객체의 레퍼런스는 is로 확인 가능

id(i), id(a) # id로 확인해도 같지 않아, 두 개의 정수를 별도의 객체로 만든 것을 알 수 있다.

정수, 실수, 문자열, 튜플 등은 변경 불가능해서 유일한 객체로 처리해야 하지만, 내부적으로 새로운 객체를 만들 수 있으므로 유일성은 값으로 비교해야 한다.

i == a # True, 정수 객체의 유일성을 검사할 때는 값이 같은지 확인

문자열 클래스를 사용해서 기존 문자열을 인자로 전달하면 새롭게 문자열을 만들지 않고 기존 객체의 레퍼런스를 반환하는데, 이유는 먼저 만들어진 객체를 인지해서 새로운 객체를 만들지 않아 항상 하나의 객체가 공유되는 것이다.

s1 = str("고양이") # 문자열 생성자에 문자열을 넣고 객체 생성
s2 = str(s1) # 변수에 저장된 인자를 생성자에 전달해서 객체 생성
s1 is s2 # True, 기존에 만든 문자열을 생성자올 다시 객체를 생성하면 기존 문자열을 그대로 전달

dataclass로 클래스 정의

클래스를 정의할 때는 초기화 함수를 정의해서 객체의 속성을 만드는 방식을 사용한다.
속성이 많아지면 모든 것을 작성하기 불편해서 쉽게 작성하는 방법인 dataclass를 도입하고 타입 힌트만 작성해도 객체의 속성을 자동으로 생성할 수 있게 된다.

dataclass 사용

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int

Person.__dict__['__init__'] # 클래스의 이름공간에 __init__이 만들어진 것을 확인 가능

파이썬은 이름공간이 공개되어 어느 시점이라도 속성을 추가할 수 있다.

p = Person("Jack", 20)
p.__dict__ # {'name': 'Jack', 'age': 20}

p.sex = 'male'
p.__dict__ # {'name': 'Jack', 'age': 20, 'sex': 'male'}

dataclass내의 매개변수를 세팅해서 내부의 스페셜 메소드 생성을 조정할 수 있는 매게변수의 특징

  • init : 초기화 함수(__init__) 생성
  • repr : 객체 실행 환경에서 출력하는 __repr__ 함수 생성
  • eq : 객체 비교
  • order : 비교 연산에 대한 스페셜 메소드
  • unsafe_hash : __hash__ 함수 생성 여부 결정
  • frozen : __setattr__, __delattr__ 함수 생성 여부 결정

frozen을 True로 설정하면 객체 속성을 런타임에 추가할 수 없게 된다.

@dataclass(init=True, repr=False, eq=False, order=False, unsafe_hash=False, froze=True)
class People:
    name: str
    age: int

p1 = People("John", 23)
p1.age = 24 # 예외 발생, 객체의 속성 값 변경 불가
p1.sex = 'male' # 예외 발생

dataclass 사용, 미사용 비교

# dataclass 사용
@dataclass
class Product:
    '''Class 1'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

# dataclass 미사용
class Product_n:
    def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0):
        self.name = name
        self.unit_price = unit_price
        self.quantity_on_hand = quantity_on_hand
    
    def total_c0st(self) -> float:
        return self.unit_price * self.quantity_on_hand

예약어로 관리되는 객체

아무것도 없는 객체를 나타내는 None은 예약어이다.
None을 생성한 클래스를 확인하면 NoneType 클래스임을 확인할 수 있다.
이 클래스는 하나의 객체만 만들어서 사용한다.

None # 예약어 None을 출력하려고 하면 아무것도 출력하지 않지만, 하나의 객체가 들어있다.
type(None) # NoneType

NoneType 클래스와 최상위 클래스 object의 속성, 메소드 차이 비교

set(dir(None.__class__)) - set(dir(object)) # {'__bool__'}
'''
속성과 메소드를 문자열 원소로 만든 리스트로 변환하고 set 클래스로 집합을 만들고 두 집합의 차집합으로 차이를 비교한 것이다.

__bool__을 하나 더 가지고 있다는 것을 알 수 있다.
'''

None.__bool__() # False, None 객체의 __bool__ 메소드를 실행하면 항상 False이다.

True와 False도 예약어이고, 내장 클래스 bool로 만들어진 2개의 객체가 할당된다.

# 정수, 실수, 복소수 클래스에 있는 __bool__ 스페셜 메소드 사용, 없다 -> False, 있다 -> True
int.__bool__(0), float.__bool__(0.0), complex.__bool__(0+0j) # (False, False, False)

# 문자열, 튜플, 리스트, 딕셔너리 원소가 하나도 없는 상태일 때 False, 있다 -> True
bool(''), bool(()), bool([]), bool({}), bool(set()) # (False, False, False, False, False)

type(True) # bool
type(False) # bool

bool 클래스에서 상속한 클래스를 확인하는 속성은 __bases__이다.

# 정수 int 클래스를 상속한다
bool.__bases__ # (int, )

int와 bool 클래스 내부의 속성과 메소드의 차이가 없다.
True가 정수 클래스를 상속해서 만든 객체이므로 숫자 객체와의 덧셈 연산이 가능하다.

set(dir(bool)) - set(dir(int)) # set(), 차이가 없다.
True + 1 # 2

범위를 처리하는 클래스

slice와 range는 클래스이다.

type(slice), type(range) # (type, type)
s = slice(0, 1) # 색인 연산으로 슬라이스 객체 생성
r = range(0, 1) # 범위만 관리하는 range 객체 생성

두 클래스와 object 클래스와의 속성, 메소드 차이 확인

set(dir(s)) - set(dir(object))
# {'indices', 'start', 'step', 'stop'}

set(dir(r)) - set(dir(object)) # range 클래스는 반복형 클래스를 상속해서 스페셜 메소드와 속성이 더 구현되어 있다.
# {'__bool__', ~~~}

ll = [x for x in range(5)] # range 객체를 리스트 컴프리헨션의 for에 직접 만들어 원소를 생성

ll[s] # [0], 색인 연산에 슬라이스 객체를 넣어 원소 추출
ll[0:1] # 슬라이스 표기법으로 색인 연산을 하면 슬라이스 객체를 사용하는 것과 같다.