๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ Python

์ดํŽ™ํ‹ฐ๋ธŒ ํŒŒ์ด์ฌ 5์žฅ - ํด๋ž˜์Šค์™€ ์ธํ„ฐํŽ˜์ด์Šค

by dev.py 2025. 3. 21.

Better way 38 ๊ฐ„๋‹จํ•œ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ฒฝ์šฐ ํด๋ž˜์Šค ๋Œ€์‹  ํ•จ์ˆ˜๋ฅผ ๋ฐ›์•„๋ผ

ํ›…(hook)

  • Python ๋‚ด์žฅ API ์‚ฌ์šฉํ•  ๋•Œ, ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ
  • Python์€ ํ•จ์ˆ˜๋ฅผ ์ผ๊ธ‰ ์‹œ๋ฏผ ๊ฐ์ฒด๋กœ ์ทจ๊ธ‰ํ•˜๊ธฐ์— ๊ฐ€๋Šฅ 

์ผ๊ธ‰ ์‹œ๋ฏผ (first-class citizen)

  • ์•„๋ฌด๋Ÿฐ ์ œ์•ฝ ์—†์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ๊ฐ’
  • ํ•จ์ˆ˜ ์ธ์ž๋กœ ๋„˜๊ธฐ๊ธฐ ๊ฐ€๋Šฅ
  • ํ•จ์ˆ˜ ๋ฐ˜ํ™˜ ๊ฐ’์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ๋ณ€์ˆ˜๋‚˜ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์— ์ €์žฅ ๊ฐ€๋Šฅ

 

languages = ['python', 'swift', 'java']
languages.sort(key=len) # hook
print(languages)
>>>
['java', 'swift', 'python']

 

 

๋”•์…”๋„ˆ๋ฆฌ์— ํ‚ค๊ฐ€ ์ถ”๊ฐ€ ์—ฌ๋ถ€๋ฅผ ์ถ”์ ํ•˜๋Š” ๋‚ด์šฉ์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ํ•˜์ž.

defaultdict ์ธ์ž์— log_missing ์„ ๊ตฌํ˜„ํ•˜์—ฌ, ํ‚ค ์ถ”๊ฐ€๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

def log_missing():
    print('ํ‚ค ์ถ”๊ฐ€') # ํ‚ค๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด ์ถœ๋ ฅ
    return 0 # ๊ธฐ๋ณธ๊ฐ’ 0 


from collections import defaultdict

colors = {'์ดˆ๋ก': 1, 'ํŒŒ๋ž‘': 2} # ๊ธฐ์กด
increments = [('์ดˆ๋ก',3), ('๋นจ๊ฐ•',4), ('๋…ธ๋ž‘',5)] # ์—…๋ฐ์ดํŠธ ๋‚ด์šฉ
result = defaultdict(log_missing, colors)
print(f'์ด์ „ : {dict(result)}')
for color, count in increments:
    result[color] += count
print(f'์ดํ›„ : {dict(result)}')

>>>
์ด์ „ : {'์ดˆ๋ก': 1, 'ํŒŒ๋ž‘': 2}
ํ‚ค ์ถ”๊ฐ€
ํ‚ค ์ถ”๊ฐ€
์ดํ›„ : {'์ดˆ๋ก': 4, 'ํŒŒ๋ž‘': 2, '๋นจ๊ฐ•': 4, '๋…ธ๋ž‘': 5}

 

 

__call__ ๋ฉ”์†Œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด, ํด๋ž˜์Šค๋กœ ๊ด€๋ฆฌ ํ•  ์ˆ˜ ์žˆ๋‹ค.

class BetterCountMissing:
    def __init__(self):
        self.added = 0

    def __call__(self): # ๊ฐ์ฒด๋ฅผ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ํ˜ธ์ถœ์ด ๊ฐ€๋Šฅ
        self.added += 1
        return 0

from collections import defaultdict
counter = BetterCountMissing()

colors = {'์ดˆ๋ก': 1, 'ํŒŒ๋ž‘': 2}
increments = [('์ดˆ๋ก',3), ('๋นจ๊ฐ•',4), ('๋…ธ๋ž‘',5)]
result = defaultdict(counter, colors)  # __call__ ๋ฅผ ํ˜ธ์ถœํ•จ
print(f'์ด์ „ : {dict(result)}')
for color, count in increments:
    result[color] += count
print(f"ํ‚ค๊ฐ€ ์ถ”๊ฐ€๋œ ํšŸ์ˆ˜ : {counter.added}")
print(f'์ดํ›„ : {dict(result)}')

>>>
์ด์ „ : {'์ดˆ๋ก': 1, 'ํŒŒ๋ž‘': 2}
ํ‚ค๊ฐ€ ์ถ”๊ฐ€๋œ ํšŸ์ˆ˜ : 2
์ดํ›„ : {'์ดˆ๋ก': 4, 'ํŒŒ๋ž‘': 2, '๋นจ๊ฐ•': 4, '๋…ธ๋ž‘': 5}

 

 

Better way 40 super๋กœ ๋ถ€๋ชจ ํด๋ž˜์Šค๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ผ

# ์•ˆ ์ข‹์€ ์˜ˆ์‹œ - ๋ถ€๋ชจ ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜์—ฌ ์ดˆ๊ธฐํ™”
class Base:
    def __init__(self, value):
        self.value = value

class Child(Base):
    def __init__(self):
        Base.__init__(self, 5) # ์ž˜ ์ž‘๋™ํ•œ๋‹ค.

 

๋ฌธ์ œ์ 

  • ๋‹ค์ค‘ ์ƒ์†์„ ํ•  ๋•Œ, ํ˜ธ์ถœ์˜ ์ˆœ์„œ๊ฐ€ ์ธ์ž์˜ ์ˆœ์„œ์™€ ๋ฌด๊ด€
  • ๋‹ค์ด์•„๋ชฌ๋“œ ์ƒ์†์„ ํ•  ๋•Œ, ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
class Base:
    def __init__(self, value):
        self.value = value

class TimesSeven(Base):
    def __init__(self, value):
        Base.__init__(self, value)
        self.value *= 7

class PlusNine(Base):
    def __init__(self, value):
        Base.__init__(self, value)
        self.value += 9


class Diamond(TimesSeven, PlusNine):
    def __init__(self, value):
        TimesSeven.__init__(self, value)
        PlusNine.__init__(self, value)

diamond = Diamond(5)
print(f'(5 * 7) + 9 = 44 ๋ฅผ ๊ธฐ๋Œ€ํ•˜์ง€๋งŒ, ์‹ค์ œ ๊ฐ’ {diamond.value}')
>>>
(5 * 7) + 9 = 44 ๋ฅผ ๊ธฐ๋Œ€ํ•˜์ง€๋งŒ, ์‹ค์ œ ๊ฐ’ 14

 

์™œ๋ƒํ•˜๋ฉด ์˜ TimesSenve.__init__ ๋๋‚œ ํ›„, value ๋Š” 35 ์ด์ง€๋งŒ, PlusNine์—์„œ Base.__init__ ์„ ๋‹ค์‹œ ํ•œ๋ฒˆ ๋” ํ˜ธ์ถœํ•˜์—ฌ value๊ฐ€ 5๋กœ ๋‹ค์‹œ ์ดˆ๊ธฐํ™” ๋œํ›„ 5+9๋ฅผ ์‹คํ–‰ํ•˜์—ฌ 14๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด super()์„ ์‚ฌ์šฉํ•˜์ž. super()๋Š” ๋‹ค์ด์•„๋ชฌ๋“œ ๊ณ„์ธต์—์„œ ๊ณตํ†ต ์ƒ์œ„ ํด๋ž˜์Šค๋ฅผ ๋‹จ ํ•œ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜๋„๋ก ๋ณด์žฅํ•œ๋‹ค.

  • ํ‘œ์ค€ ๋ฉ”์„œ๋“œ ๊ฒฐ์ • ์ˆœ์„œ (Method Resolution Order, MRO) - C3 ์„ ํ˜•ํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์‚ฌ์šฉ
class Base:
    def __init__(self, value):
        self.value = value

class TimesSeven(Base):
    def __init__(self, value):
        super().__init__(value)
        self.value *= 7

class PlusNine(Base):
    def __init__(self, value):
        super().__init__(value)
        self.value += 9


class Diamond(PlusNine, TimesSeven):
    def __init__(self, value):
        super().__init__(value)

diamond = Diamond(5)
print(f'(5 * 7) + 9 = 44 ๋ฅผ ๊ธฐ๋Œ€ํ•˜์ง€๋งŒ, ์‹ค์ œ ๊ฐ’ {diamond.value}')
>>>
(5 * 7) + 9 = 44 ๋ฅผ ๊ธฐ๋Œ€ํ•˜์ง€๋งŒ, ์‹ค์ œ ๊ฐ’ 44

 

Better way 41 ๊ธฐ๋Šฅ์„ ํ•ฉ์„ฑํ•  ๋•Œ๋Š” ๋ฏน์Šค์ธ ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

๋ฏน์Šค์ธ(mix-in)

  • ์ž์‹ ํด๋ž˜์Šค๊ฐ€ ์‚ฌ์šฉํ•  ๋ฉ”์„œ๋“œ ๋ช‡ ๊ฐœ๋งŒ ์ •์˜ํ•˜๋Š” ํด๋ž˜์Šค
  • ์ž์ฒด ์• ํŠธ๋ฆฌ๋ทฐํŠธ ์ •์˜๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์ž์‹์ด ๋ถ€๋ชจ ๋ฏน์Šค์ธ ํด๋ž˜์Šค __init__ ์„ ํ˜ธ์ถœํ•  ํ•„์š”๋„ ์—†๋‹ค.
class LogMixin:
    def log(self, message):
        print(f"[LOG]: {message}")

class TimestampMixin:
    def timestamp(self):
        from datetime import datetime
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

class App(LogMixin, TimestampMixin):
    def run(self):
        self.log("์•ฑ์ด ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค.")
        print(f"ํ˜„์žฌ ์‹œ๊ฐ„: {self.timestamp()}")

app = App()
app.run()

Better way 42 ๋น„๊ณต๊ฐœ ์• ํŠธ๋ฆฌ๋ทฐํŠธ๋ณด๋‹ค๋Š” ๊ณต๊ฐœ ์• ํŠธ๋ฆฌ๋ทฐํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

ํŒŒ์ด์ฌ ํด๋ž˜์Šค์˜ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ ๊ฐ€์‹œ์„ฑ

  • ๊ณต๊ฐœ (public)
  • ๋น„๊ณต๊ฐœ (private)
class MyClass:
    def __init__(self):
        self.public_field = 5 # public
        self.__private_field = 10 # private


    def get_private_field(self):
        return self.__private_field


my_class = MyClass()
print(my_class.public_field) # 5
print(my_class.get_private_field()) # 10
print(my_class.__private_field) # AttributeError: 'MyClass' object has no attribute

 

๋น„๊ณต๊ฐœ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์ธ __private_field๋ฅผ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

ํŒŒ์ด์ฌ์—์„œ ๋น„๊ณต๊ฐœ ์–ดํŠธ๋ฆฌ๋ทฐํŠธ์˜ ๋™์ž‘์€ ๊ทธ์ € ์ด๋ฆ„์„ ๋ฐ”๊พธ๋Š” ๋‹จ์ˆœํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด MyClass ๋‚ด์˜ __private_field ์–ดํŠธ๋ฆฌ๋ทฐํŠธ๋Š”  _MyClass__private_field ์œผ๋กœ ๋ณ€๊ฒฝ๋˜์–ด ๋™์ž‘ํ•œ๋‹ค.

๊ทธ๋ž˜์„œ ํ•ด๋‹น ์ด๋ฆ„์œผ๋กœ ์™ธ๋ถ€์—์„œ ์ ‘๊ทผํ•˜๋ฉด, ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

print(my_class._MyClass__private_field) # 10

 

 

์™œ ํŒŒ์ด์ฌ์—์„œ๋Š” ๊ฐ€์‹œ์„ฑ์„ ์—„๊ฒฉํ•˜๊ฒŒ ์ œํ•œํ•˜์ง€ ์•Š์•˜์„๊นŒ? 

"์šฐ๋ฆฌ๋Š” ๋ชจ๋‘ ์ฑ…์ž„์งˆ์ค„ ์•Œ๋Š” ์„ฑ์ธ์ด๋‹ค" ๋ผ๋Š” ๋ชจํ† ๋กœ ์ •๋ง ๊ตณ์ด๊ตณ์ด ์œ„ํ—˜์„ ํƒํ•  ํ•„์š”์„ฑ์ด ์žˆ๋‹ค๋ฉด, ๊ฐœ๋ฐœ์ž์—๊ฒŒ ์„ ํƒ์„ ์กด์ค‘ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๊ด€๋ก€์ ์œผ๋กœ๋Š” _ 1๊ฐœ๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ protect ์˜๋ฏธ๋กœ ์กฐ์‹ฌํžˆ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค.

 

Better way 43 ์ปค์Šคํ…€ ์ปจํ…Œ์ด๋„ˆ ํƒ€์ž…์€ collections.abc๋ฅผ ์ƒ์†ํ•˜๋ผ

์žฅ์  1. ํ•„์š”ํ•œ ๋ฉ”์†Œ๋“œ๊ฐ€ ๊ตฌํ˜„๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, ์‹ค์ˆ˜ํ•œ ๋ถ€๋ถ„์„ ์•Œ๋ ค์ค€๋‹ค.

from collections.abc import  Sequence

class BadType(Sequence):
    pass

foo = BadType()

>>>
    foo = BadType()
          ^^^^^^^^^
TypeError: Can't instantiate abstract class BadType with abstract methods __getitem__, __len__

 

์žฅ์  2. ์š”๊ตฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋‚˜๋จธ์ง€ ๋ฉ”์†Œ๋“œ - Sequence์˜ ๊ฒฝ์šฐ (index, count)

# ์ง์ ‘ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ 
class IndexableNode:
    def __init__(self, value, next_node=None):
        self.value = value
        self.next_node = next_node

    def __getitem__(self, index):
        current = self
        for _ in range(index):
            if current.next_node is None:
                raise IndexError("Index out of range")
            current = current.next_node
        return current.value

    def __len__(self):
        count = 0
        current = self
        while current:
            count += 1
            current = current.next_node
        return count

    def __contains__(self, item):
        current = self
        while current:
            if current.value == item:
                return True
            current = current.next_node
        return False

    def index(self, item):
        current = self
        idx = 0
        while current:
            if current.value == item:
                return idx
            current = current.next_node
            idx += 1
        raise ValueError(f"{item} is not in list")

    def count(self, item):
        current = self
        count = 0
        while current:
            if current.value == item:
                count += 1
            current = current.next_node
        return count

 

from collections.abc import Sequence

# โœ… collections.abc Sequence๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ (๋” ๊ฐ„๊ฒฐํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด)
class IndexableNode(Sequence):
    def __init__(self, value, next_node=None):
        self.value = value
        self.next_node = next_node

    def __getitem__(self, index):
        current = self
        for _ in range(index):
            if current.next_node is None:
                raise IndexError("Index out of range")
            current = current.next_node
        return current.value

    def __len__(self):
        count = 0
        current = self
        while current:
            count += 1
            current = current.next_node
        return count

 

# ์‚ฌ์šฉ ์˜ˆ์‹œ๋Š” ๋™์ผํ•˜๋‹ค
node3 = IndexableNode(3)
node2 = IndexableNode(2, node3)
node1 = IndexableNode(1, node2)

print(node1[1])  # 2
print(len(node1))  # 3
print(3 in node1)  # True  (์ž๋™ ์ง€์›)
print(node1.index(2))  # 1  (์ž๋™ ์ง€์›)
print(node1.count(3))  # 1  (์ž๋™ ์ง€์›)