상세 컨텐츠

본문 제목

클래스 기본

Python

by techbard 2024. 12. 20. 13:04

본문

반응형

 

- A class is like a form or questionnaire.
- An instance is like a form that has been filled out with information.
- Just like many people can fill out the same form with their own unique information,
many instances can be created from a single class.
- Python class names are written in CapitalizedWords notation by convention.
- Attributes created in .__init__()  are called instance attributes.
- An instance attribute’s value is specific to a particular instance of the class.
- On the other hand, class attributes are attributes that have the same value for all class instances.
- You can define a class attribute by assigning a value to a variable  name outside of .__init__().
- Use class attributes to define properties that should have the same value for every class instance.
- Use instance attributes for properties that vary from one instance to another.
- Instance methods are functions that are defined inside a class and can only be called from an instance of that class.
- Just like .__init__(), an instance method’s first parameter is always self.
- Methods like .__init__()  and .__str__()  are called dunder methods  because they begin and end with double underscores.
- Inheritance is the process by which one class takes on the attributes and methods of another.
- Newly formed classes are called child classes, and the classes that child classes are derived from are called 
parent classes.
- Inheritance is a way of creating a new class for using details of an existing class without modifying it.
- Encapsulation is one of the key features of object-oriented programming.
- Encapsulation refers to the bundling of attributes and methods inside a single class.
- Polymorphism is, the same entity (method or operator or object) can perform different operations in different scenarios.
- Uses of Inheritance:
- Since a child class can inherit all the functionalities of the parent's class, this allows code reusability.
- Once a functionality is developed, you can simply inherit it.
- No need to reinvent the wheel.
- This allows for cleaner code and easier to maintain.
- Since you can also add your own functionalities in the child class, you can inherit only the useful functionalities and define other required features.

 

# 가장 간단한 클래스 구조
class Person:
    def __init__(self, fname: str, lname: str) -> None:
        self.fname = fname
        self.lname = lname

    def get_full_name(self) -> str:
        return self.fname + " " + self.lname
    
    def __repr__(self) -> str:
        return f"Person: {self.fname} {self.lname}"

p1 = Person("Bob", "Smith")
print(f"{p1.fname}")
print(f"{p1.lname}")
print(f"{p1.get_full_name()}")
print(p1)

# 결과
Bob
Smith
Bob Smith
Person: Bob Smith

 

class Car:
    def __init__(self, brand: str, wheels: int) -> None:
        self.brand = brand
        self.wheels = wheels
    def turn_on(self) -> None:
        print(f"Turning on: {self.brand}")
    def turn_off(self) -> None:
        print(f"Turning off: {self.brand}")
    def drive(self, km: float) -> None:
        print(f"Driving: {self.brand} for {km}km")
    def describe(self) -> None:
        print(f"{self.brand} is a car with {self.wheels} wheels")

def main() -> None:
    bmw: Car = Car("BMW", 4)
    bmw.turn_on()
    bmw.drive(10)
    bmw.describe()
    
    # It seems like we went through a lot of effort just for
    # a simple BMW.
    # Just like below, we created a whole new car that
    # has all the same functionality as that of the BMW
    # with some custom details.
    # So we don't have to rewrite any functionality for this Volvo
    # because we already created a blueprint that fits it quite nicely,
    # which means now we can also turn on the Volvo.
    # And that's why object oriented programming can be very appealing
    # also in Python.
    volvo: Car = Car("Volve", 6)
    volvo.turn_on()
    volvo.drive(100)
    volve.describe()

main()

# 결과
Turning on: BMW
Driving: BMW for 10km
BMW is a car with 4 wheels

Turning on: Volve
Driving: Volve for 100km
Volve is a car with 6 wheels

 

# 클래스 기초
class Dog:
    # 여기에는 클래스 공통의 속성이 온다. (class object attribute)
    # 어떤 인스턴스든 간에 모두 가지고 있는 속성 (same for any instance of a class)
    # self 키워드는 사용하지 않는다.
    # 이유는 self는 클래스의 특정한 인스턴스에 대한 참조이기 때문에
    species = 'mammal'
    
    def __init__(self, breed, name, spots): # 인스턴스 멤버 또는 속성의 정의
        self.breed = breed
        self.name = name
        self.spots = spots
    
    # 메서드: 클래스 바디에 정의된 함수
    # 인스턴스의 개별 속성을 사용하는 작업을 정의
    # 메서드는 클래스 내에 있는 함수로 인스턴스와 함께 작동
    # 메서드는 클래스와 연결되어 있지만, 함수는 클래스와 무관
    def bark(self, number): # 인스턴스 멤버로 초기화하지 않은 인자를 메서드에 넘길 수 있다.
        print(f"Woof! my name is {self.name}, the num is {number}")

my_dog = Dog(breed='lab', name='Sammy', spots=False)

# 속성과 메서드의 차이점은 호출하는 방법에 있다.
# 속성은 괄호가 없다. 무언가를 실행하는 것이 아니기 때문이다.
# 단순히 인스턴스의 속성을 호출하는 것
# 이에 반해, 메서드는 실행이 필요하다. 실행을 위해서는 함수와 마찬가지로
# 인자가 필요하다.
print(my_dog.breed)
my_dog.bark(100)

# 결과
lab
Woof! my name is Sammy, the num is 100

 

# Methods are functions bound to a class. (인스턴스 메서드의 이해)
# - Only Car objects can call the drive method. (with . notation)
# - Error if attempt to call drive function.
#
# self는 명시적으로 인스턴스를 전달한다는 표시를 남기도록 설계했다.
# 

class Car:
    def drive(self, miles):
        print(f"The car drove {miles} miles.")

c = Car()
c.drive(100)

# 결과
The car drove 100 miles.

 

# simple class & inheritance, method overriding
class Vehicle():
    def __init__(self, body_style):
        self.bodystyle = body_style

    def drive(self, speed):
        self.mode = "driving"
        self.speed = speed

class Car(Vehicle):
    def __init__(self, engine_type):
        super().__init__("Car")
        self.wheels = 4
        self.doors = 4
        self.engine_type = engine_type
        
    def drive(self, speed):
         super().drive(speed)
         print("Driving my", self.engine_type, "car at", self.speed)

class Motocycle(Vehicle):
    def __init__(self, engine_type, has_sidecar):
        super().__init__("Motocycle")
        if has_sidecar:
            self.wheels = 3
        else:
            self.wheels = 2
        self.door = 0
        self.engine_type = engine_type

    def drive(self, speed):
         super().drive(speed)
         print("Driving my", self.engine_type, "motocycle at", self.speed)

car1 = Car("gas")
car2 = Car("electric")
mc1 = Motocycle("gas", True)

print(mc1.wheels)
print(car1.engine_type)
print(car2.doors)

# 결과
3
gas
4
Driving my gas car at 30
Driving my electric car at 40
Driving my gas motocycle at 50

 

# Classes & Instances: Combine Methods & Data

class Point():
    def getX(self):
        return self.x

    # When we call this method p1.getX
    # then one of the differences
    # between methods and functions,
    # remember, methods belong to a class.
    # So whenever we call a method we have
    # a particular actually instance that
    # we're calling that method on.
    # So in this case we aren't just calling
    # getX(), we're saying p1.getX().
    # And so when we say p1.getX() what happens
    # is that we're automatically passing in this
    # instance p1 as self when we call getX().
    # So when we say p1.getX(), even if we have no
    # arguments here and what Pytghon does is it automatically
    # takes what's befor the dot or the instance that we're
    # calling this on and it passes it in, As the value of self.
    # If we had instead replace that code to say, Print p2.getX().
    # Then what would happen is, instead of passing in p1,
    # then we would be passing in p2.
    # So we would've said p2.getX() would pass in p2 as the value
    # for the self.  

p1 = Point()
p2 = Point()

p1.x = 5
p2.x = 10

print(p1)
print(f"Are p1 and p2 equals? {p1 == p2}")

print(p1.getX())
print(p2.getX())

# 결과
<__main__.Point object at 0x0000020C547C6900>
Are p1 and p2 equals? False
5
10

 

# Sorting Lists of Instances

class Fruit():
    def __init__(self, name, price):
        self.name = name
        self.price = price
    def getName(self):
        return self.name
    def getPrice(self):
        return self.price
    def __str__(self):
        return f'({self.name}, {self.price})'

L = [
    Fruit("cherry", 10),
    Fruit("apple", 5),
    Fruit("blueberry", 20)
    ]

# So we need to be able to tell sorted how to compare
# two fruit items by passing in a value for key.
# Now remember that key is expecting a function as it's argument,
# so what I want to pass into key is a function that tell us what
# we want to sort by.
# print(sorted(L, key=Fruit.getName))

# for f in sorted(L, key=Fruit.getName):
    # print(f)

# Or

for f in sorted(L, key=lambda x: x.getName()):
    print(f'sorted by name: {f}')

print("")

for f in sorted(L, key=lambda x: x.getPrice()):
    print(f'sorted by price: {f}')

# 결과
sorted by name: (apple, 5)
sorted by name: (blueberry, 20)
sorted by name: (cherry, 10)

sorted by price: (apple, 5)
sorted by price: (cherry, 10)
sorted by price: (blueberry, 20)

 

# Creating Instances from Data

city_names = ['Detroit', 'Ann Arbor', 'Pittsburgh',
            'Mars', 'New York']
populations = [680250, 117070, 304391, 1683, 8406000]
states = ['MI', 'MI', 'PA', 'PA', 'NY']

city_tuples = zip(city_names, populations, states)

# print(list(city_tuples))

class City:
    def __init__(self, n, p, s):
        self.name = n
        self.population = p
        self.state = s
    def __str__(self):
        return f'{self.name}, {self.state} (pop: {self.population})'

# cities = []
# for city_tup in city_tuples:
    # name, pop, state = city_tup
    # city = City(name, pop, state) # instance of the City class
    # cities.append(city)

cities = [City(n, p, s) for (n, p ,s) in city_tuples]
# or
# cities = [City(*tup) for tup in city_tuples]

for city in cities:
    print(city)

# 결과
Detroit, MI (pop: 680250)
Ann Arbor, MI (pop: 117070)
Pittsburgh, PA (pop: 304391)
Mars, PA (pop: 1683)
New York, NY (pop: 8406000)

 

class Point():
    def __init__(self, initX, initY):
        self.x = initX
        self.y = initY
    def getX(self):
        return self.x
    def getY(self):
        return self.y
    def __str__(self):
        return f'(Point: {self.x}, {self.y})'
    def __add__(self, otherPoint):
        return Point(self.x + otherPoint.x, self.y + otherPoint.y)
    def __sub__(self, otherPoint):
        return Point(self.x - otherPoint.x, self.y - otherPoint.y)
    def halfway(self, otherPoint):
        midX = (self.x + otherPoint.x)/2
        midY = (self.y + otherPoint.y)/2
        return Point(midX, midY)

p1 = Point(1, 1)
p2 = Point(2, 2)

print(p1 + p2)
print(p1 - p2)

mid = p1.halfway(p2)
print(mid)
print(mid.getX())
print(mid.getY())

# 결과
(Point: 3, 3)
(Point: -1, -1)
(Point: 1.5, 1.5)
1.5
1.5

 

# 샘플 클래스
# 거리, 기울기 구하는 공식 적용
class Line:
    def __init__(self, coord1, coord2):
        self.coord1 = coord1 # tuple
        self.coord2 = coord2
    
    def distance(self):
        (x1, y1) = self.coord1
        (x2, y2) = self.coord2
        return ((x2-x1)**2 + (y2-y1)**2)**0.5

    def slope(self):
        (x1, y1) = self.coord1
        (x2, y2) = self.coord2
        return (y2-y1)/(x2-x1)

c1 = (3, 2)
c2 = (8, 10)

li = Line(c1, c2)
print(li.distance())
print(li.slope())

# 결과
9.433981132056603
1.6

 

# 동일한 두 점 거리와 기울기 구하는 공식을 가지고
# 리스트로 좌표 두 개가 주어질 때
# 그런데 해보니 리스트로 던져도 언패킹이 된다.
# 또한 구현하기 나름인데, 사실 그냥 멤버 초기화 없이 메서드에 값을 넣어도 된다.
class Line:
    def __init__(self, coor1, coor2):
        self.coor1 = coor1
        self.coor2 = coor2

    def distance(self):
        x1 = self.coor1[0]
        y1 = self.coor1[1]
        x2 = self.coor2[0]
        y2 = self.coor2[1]
        return ((x2-x1)**2 + (y2-y1)**2)**0.5

    def slope(self):
        x1 = self.coor1[0]
        y1 = self.coor1[1]
        x2 = self.coor2[0]
        y2 = self.coor2[1]
        return (y2-y1)/(x2-x1)

c1 = [3, 2]
c2 = [8, 10]

li = Line(c1, c2)
print(li.distance())
print(li.slope())

# 결과
9.433981132056603
1.6

 

# 은행 계좌 예제 클래스
class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance
        
    def withdraw(self, amount):
        if amount < 0:
            print("not a negative one.")
        elif amount > self.balance:
            print("You don't have enough balance.")
        else:
            self.balance -= amount
    
    def deposit(self, amount):
        if amount < 0:
            print("not a negative one.")
        else:
            self.balance += amount
    
    def __str__(self):
        return f'Hello {self.owner}, your balance is {self.balance}'

ac1 = Account('Jose', 100)
print(ac1)
ac1.deposit(50)
print(ac1)
ac1.withdraw(75)
print(ac1)
ac1.withdraw(80)
print(ac1)

# 결과
Hello Jose, your balance is 100
Hello Jose, your balance is 150
Hello Jose, your balance is 75
You don't have enough balance.
Hello Jose, your balance is 75

 

# Creating New Types
# - class keyword followed by:
# -- the name of the type and a colon
# -- similar to user-defined functions
# - Create the Car type

class Car:
    pass

# 객체를 생성하지 않았기 때문에 'type' 이다.
print(type(Car))

# built-in 'type' 이다.
print(type(int))

# t는 'str' 클래스의 멤버이다.
t = 'text'
print(type(t))

# Creating a Car instance
# - Car is no an instance
# - Car is a constructor that construc new Car instances
# -- Just like int constructs new integers and str constructs new strings
# - Must call Car() to create an instances

# 일종의 factory 이다.
my_car = Car()

# Factories
# Think of Car as a factory that constructs new Car instances
# This one factory can create any number of instances

print(type(int(1)))

print(type(my_car))
# <class '__main__.Car'>
# __main__ 은 이 모듈의 이름이다.

# Terminology: my_car is an instance of the Car class
# -- We created an instance of the Car class.
# An instance is a single member of the class.
# Another way of saying it - "we instantiated the Car class by creating the my_car instance"
# "We constructed a new instance of the Car class"

# 결과
<class 'type'>
<class 'type'>
<class 'str'>
<class 'int'>
<class '__main__.Car'>

 

# What is the parameter self doing here?
# - The first parameter refers to the calling instances
# - Instance is implicitly passed the method.

class Car:
    def drive(self, kms):
        print(f'The car drove {kms} kilometers.')

# Create new instance and call drive method
# my_car instance가 Car 클래스의 self에 암시적으로 전달된다.
# 이렇게 self를 쓰는 이유는 클래스 내부에서만 접근 가능한 메서드와
# instance에서 접근 가능한 메서드를 구분하기 위함으로 생각된다.
my_car = Car()
my_car.drive(10)
# 실상은 my_car.drive(my_car, 10) 이다. 내부적으로 생략해 처리한다.
# 즉, 인스턴스 메서드에 인스턴스 자체를 함께 던지는 개념이다.
# 결국 여러 인스턴스가 존재하며 그 각각의 인스턴스를 클래스 내부의
# 메서드에 함께 던져서 각각의 메서드를 사용할 수 있다.

# 결과
The car drove 10 kilometers.

 

# Object-oriented Programming
# - Creating classes, instantiating them, calling methods and
# retrieving attributes are all part of a programming paradigm
# called object-oriented programming.
# - In Python, every variable name created is an object.
# - All objects have types that are defined by their classes.


# Initializing an instance
# - Initialize instances in a specific and customized way.
# - Like choosing a car's color, number or seats, horsepower, transmission, and
# any other features.
# - No customization for current Car class - all instances are the same

# Define __init__ method
# - Gets called implicitly when consteucted

# __init__ 메서드를 이용하는 것은 결국 클래스를 커스토마이징하는 것
# 동일한 클래스를 커스토마이징해서 원하는 목적에 맞게 사용한다.
class Car:
    def __init__(self, color, price, transmission, make, model, mileage):
        self.color = color
        self.price = price
        self.transmission = transmission
        self.make = make
        self.model = model
        self.mileage = mileage
    
    def drive(self, kms):
        print(f'The car drove {kms} kilometers.')

my_car = Car(color='Red', price=80_000, transmission='Manual',
            make='Tesla', model='S3', mileage=0)

print(my_car.color)
print(my_car.model)

# 결과
Red
S3

 

# Object-oriented Programming
# - Creating classes, instantiating them, calling methods and
# retrieving attributes are all part of a programming paradigm called
# object-oriented programming.
# - In Python, every variable name is an object.
# - All objects have types that are defined by their classes.

# Initializing an instance
# - Lnitialize instances in a specific and customized way.
# - No customization for current Car class - all instances are the same


# 클래스 멤버 사용, 인스턴스 멤버와 메서드 인자를 함께 사용
class Car:
    depreciation = 0.25
    
    def __init__(self, color, price, make, model, mileage):
        self.color = color
        self.price = price
        self.make = make.upper()
        self.model = model
        self.mileage = mileage
        
    def drive(self, miles):
        self.mileage += miles
        self.price -= miles * 0.5
        print(f"Your {self.make} {self.model} car drove {miles} miles.")
        print(f"It has {self.mileage} miles.")
        print(f"Its current price is {self.price}")
        
    def future_lifetime(self):
        return self.price / Car.depreciation

my_car = Car(color='silver', price=35000, mileage=0, make='Tesla', model='S3')
my_car.drive(10)
my_car.drive(100)
my_car.drive(10_000)
my_car.future_lifetime()

# 결과
Your TESLA S3 car drove 10 miles.
It has 10 miles.
Its current price is 34995.0
Your TESLA S3 car drove 100 miles.
It has 110 miles.
Its current price is 34945.0
Your TESLA S3 car drove 10000 miles.
It has 10110 miles.
Its current price is 29945.0

 

# Object composition
# - When instrances have attributes that refer to instances of other classes
# - Change Car class so that a Person instance created in constructor

class Person:
    def __init__(self, name):
        self.name = name
        
    def greet(self):
        print(f'Hello, my name is {self.name}')

class Car:
    def __init__(self, color, price, model, mileage, person_name):
        self.color = color
        self.price = price
        self.model = model
        self.mileage = mileage
        # Person class instantiated here
        self.driver = Person(person_name)
        self.depreciation = .5

    def drive(self, miles):
        self.mileage += miles
        self.price -= miles * self.depreciation
        print(f'Your {self.model} drove {miles} miles')
        print(f'Total miles driven: {self.mileage}')
        print(f'Current value of car: {self.price}')

    def future_lifetime(self):
        return self.price / self.depreciation

car1 = Car(color='silver', price=35_000, mileage=0, model='Tesla', person_name='Bob')

car1.drive(10000)
ret = car1.future_lifetime()
print(f'future lifetime: {ret}')
car1.driver.greet()

# 결과
Your Tesla drove 10000 miles
Total miles driven: 10000
Current value of car: 30000.0
future lifetime: 60000.0
Hello, my name is Bob

 

# 상속과 합성

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
    def __str__(self):
        return f'{self.title} by {self.author}'

class PaperBook(Book):
    def __init__(self, title, author, numPages):
        super().__init__(title, author)
        self.numPages = numPages
    def __str__(self):
        return f'{self.title} by {self.author} ({self.numPages}p)'

class Ebook(Book):
    def __init__(self, title, author, fileSize):
        super().__init__(title, author)
        self.fileSize = fileSize
    def __str__(self):
        return f'{self.title} by {self.author} ({self.fileSize}MB)'
# 여기는 상속

class Library:
    def __init__(self):
        self.books = []
    def addBook(self, book):
        self.books.append(book)
    def getNumBooks(self):
        return len(self.books)
# 여기는 composition (a library contains or is composed of a list of books.)

p1 = PaperBook('The Odyssey', 'Homer', 499)
print(p1)

p2 = Ebook('The Odyssey', 'Homer', 2)
print(p2)

aLibrary = Library()
aLibrary.addBook(p1)
aLibrary.addBook(p2)
print(f'There (is)are {aLibrary.getNumBooks()} book(s).')

# 결과
The Odyssey by Homer (499p)
The Odyssey by Homer (2MB)
There (is)are 2 book(s).

 

# 클래스 파라미터의 사용

class BaseStudent:
    def __init__(self, name, age, marks):
        self.name = name
        self.age = age
        self.marks = marks

    def display(self):
        print(f'Hi {self.name}')
        print(f'Your age: {self.age}')
        print(f'Your marks: {self.marks}')

name = input('enter your name -> ')
age = int(input('enter your age -> '))
marks = int(input('enter your marks -> '))

s1 = BaseStudent(name, age, marks)
s1.display()

class ParamStudent:
    def __init__(self, n, a, *m):
        self.n = n
        self.a = a
        self.m = m

    def display(self):
        print('Hi', self.n)
        print('Your age', self.a)
        print('Your marks', self.m)

s2 = ParamStudent('kelly', 22, 70, 65, 90)
s2.display()

class KeyValueStudent:
    def __init__(self, n, a, **m):
        self.n = n
        self.a = a
        self.m = m
    
    def display(self):
        print('Hi', self.n)
        print('Your age', self.a)
        print('Your marks', self.m)

s3 = KeyValueStudent('John', 19, scienc = 95, english = 70, maths = 88)
s3.display()

# 결과
enter your name -> mike
enter your age -> 20
enter your marks -> 88
Hi mike
Your age: 20
Your marks: 88
Hi kelly
Your age 22
Your marks (70, 65, 90)
Hi John
Your age 19
Your marks {'scienc': 95, 'english': 70, 'maths': 88}

 

import random

# Single Responsibility Principle
# - Why two methods roll and _roll
# - SRP each component of a program responsibility over one task
# - _roll completes one roll of all dice
# - roll rolls n number of times
# - encourages user to call roll

# 간단한 클래스
# public, private method
class Dice:
    def __init__(self, sides=6, num=2, verbose=False):
        self.sides = sides
        self.num = num
        self.verbose = verbose
        self.rolls = []
        self.sums = []
    
    # private method
    def _roll(self):
        '''
        One complete roll of all dice
        '''
        curr_roll = tuple(random.randint(1, self.sides) for i in range(self.num))
        self.rolls.append(curr_roll)
        self.sums.append(sum(curr_roll))
        if self.verbose:
            print(f'Rolling {self.num} {self.sides}-sided dice: {curr_roll}')

    # public method
    def roll(self, n=1):
        '''
        Roll all dice n number of times
        '''
        for i in range(n):
            self._roll()

dice = Dice(sides=6, num=4, verbose=True)
print(type(dice))
dice.roll(3)
print(dice.sums)

# 결과
<class '__main__.Dice'>
Rolling 4 6-sided dice: (5, 4, 5, 1)
Rolling 4 6-sided dice: (1, 3, 5, 3)
Rolling 4 6-sided dice: (2, 1, 5, 6)
[15, 12, 14]

 

# Another big benefit of classes is that we
# can reuse them. We've already used many
# built-in types, including the string, the list,
# the dictionary, and the various numeric types.
# Instead of reimplementing the functionality provided by these types,
# we could focus on creating our programs.

class Pokemon:
    def __init__(self, name: str, max_armor: int, max_hit: int):
        self.name = name
        self.armor = max_armor
        self.hit_points = max_hit
        
    def attack(self):
        print(f'{self.name} attacks')
    
    def defend(self):
        print(f'{self.name} defends itself')
 
 
pikachu = Pokemon('Pikachu', 100, 1000)
pikachu.attack()
pikachu.defend()

snolax = Pokemon('Snorlax', 75, 900)
snolax.attack()

# 결과
Pikachu attacks
Pikachu defends itself
Snorlax attacks

 

### 클래스 생성자, 클래스 메소드

class myClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def myMethod(self):
        print(f'This is myMethod -> x: {self.x}, y: {self.y}')

def main():
    mc = myClass(1, 2)
    mc.myMethod()
    pass

if __name__ == '__main__':
    main()

# 결과
This is myMethod -> x: 1, y: 2

 

class PrivateMemberExample:
    def __init__(self):
        self.x = 1
        self._y = 2
        self.__z = 3

c1 = PrivateMemberExample()
print(f'멤버에 언더스코어 없는 self.x 접근 가능: {c1.x}')

c2 = PrivateMemberExample()
print(f'멤버에 1개 언더스코어 있는 self._y 접근 가능: {c2._y}')

# 결과
멤버에 언더스코어 없는 self.x 접근 가능: 1
멤버에 1개 언더스코어 있는 self._y 접근 가능: 2

# c3 = PrivateMemberExample()
# print(f'멤버에 2개 언더스코어 있는 self.__z 접근 불가: {c3.__z}')

# 결과
AttributeError: 'PrivateMemberExample' object has no attribute '__z'

 

 

### 스태틱 필드의 사용 (사실 함수와 다를게 없다)

class UseStaticField:
    result = []

    def __init__(self, *args):
        ret = []
        c = random.randint(0, 1)
        for arg in args:
            if c == 0 and arg % 2 == 0:
                ret.append(arg)
            if c == 1 and arg % 2 == 1:
                ret.append(arg)
        UseStaticField.result = ret

    def getResult(self):
        return UseStaticField.result

def main():
    u1 = UseStaticField(1, 2, 3, 4, 5)
    print(u1.getResult())

    u2 = UseStaticField(0, 7, 10, 23)
    print(u2.getResult())

    pass

if __name__ == '__main__':
    main()

#결과 1
[2, 4]
[0, 10]

#결과 2
[1, 3, 5]
[7, 23]

### 클래스 메쏘드로 쪼갠 버전

import random

class ChoiceNums:
    inList = []
    outList = []

    def __init__(self, *args):
        ChoiceNums.inList.clear()
        for arg in args:
            ChoiceNums.inList.append(arg)

    def getEvens(self):
        ChoiceNums.outList.clear()
        for e in ChoiceNums.inList:
            if e % 2 == 0:
                ChoiceNums.outList.append(e)
        return ChoiceNums.outList

    def getOdds(self):
        ChoiceNums.outList.clear()
        for e in ChoiceNums.inList:
            if e % 2 == 1:
                ChoiceNums.outList.append(e)
        return ChoiceNums.outList

def main():
    u1 = ChoiceNums(1, 2, 3, 4, 5)
    print('even:', u1.getEvens())
    print('odd:', u1.getOdds())

    print('\t')

    u2 = ChoiceNums(0, 7, 10, 23)
    print('even:', u2.getEvens())
    print('odd:', u2.getOdds())

    pass

if __name__ == '__main__':
    main()

# 결과
even: [2, 4]
odd: [1, 3, 5]
	
even: [0, 10]
odd: [7, 23]

### 객체 안의 데이터를 사용한 버전

class selectNums:

    def __init__(self, *args):
        self.args = args
        self.ret = []

    def getEvens(self):
        self.ret.clear()
        for e in self.args:
            if e % 2 == 0:
                self.ret.append(e)
        return self.ret

    def getOdds(self):
        self.ret.clear()
        for e in self.args:
            if e % 2 == 1:
                self.ret.append(e)
        return self.ret

def main():
    u1 = selectNums(1, 2, 3, 4, 5)
    print('even:', u1.getEvens())
    print('odd:', u1.getOdds())

    print('\t')

    u2 = selectNums(0, 7, 10, 23)
    print('even:', u2.getEvens())
    print('odd:', u2.getOdds())

    pass

if __name__ == '__main__':
    main()

# 결과
even: [2, 4]
odd: [1, 3, 5]
	
even: [0, 10]
odd: [7, 23]

### 리스트 컴프리헨션으로 객체 내부에 리스트 유지 X

class selectNums:
    def __init__(self, *args):
        self.args = args

    def getEvens(self):
        return [e for e in self.args if e % 2 == 0]

    def getOdds(self):
        return [e for e in self.args if e % 2 == 1]

def main():
    u1 = selectNums(1, 2, 3, 4, 5)
    print('even:', u1.getEvens())
    print('odd:', u1.getOdds())

    print('\t')

    u2 = selectNums(0, 7, 10, 23)
    print('even:', u2.getEvens())
    print('odd:', u2.getOdds())

    pass

if __name__ == '__main__':
    main()

# 결과
even: [2, 4]
odd: [1, 3, 5]
	
even: [0, 10]
odd: [7, 23]
# 클래스 속성과 인스턴스 속성 구별
class SProperty:
    shared_ls = [] # 클래스 속성
    def __init__(self, n):
        self.shared_ls.append(n) # self로 지정해도 접근 가능

class PProperty:
    def __init__(self):
        self.private_ls = [] # 인스턴스 속성
    def put(self, n):
        self.private_ls.append(n)

def main():
    s1 = SProperty('1')
    s2 = SProperty('2')

    print('-> 클래스 속성은 모든 인스턴스에 공유')
    print('s1:', s1.shared_ls)
    print('s2:', s2.shared_ls)

    print('\t')

    p1 = PProperty()
    p1.put('3')
    p2 = PProperty()
    p2.put('4')

    print('-> 인스턴스 속성은 인스턴스별로 독립')
    print('p1:', p1.private_ls)
    print('p2:', p2.private_ls)

    pass

if __name__ == '__main__':
    main()

# 결과
-> 클래스 속성은 모든 인스턴스에 공유
s1: ['1', '2']
s2: ['1', '2']
	
-> 인스턴스 속성은 인스턴스별로 독립
p1: ['3']
p2: ['4']

 

class Book:
    book_count = 0 # keep track of how many books are created.
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
        Book.book_count += 1

    # Instance Method
    def get_description(self):
        return f"'{self.title}' by {self.author}, {self.pages}"

    # Class Method
    @classmethod
    def get_book_count(cls):
        return cls.book_count
    
    # Static Method (Utility functions)
    @staticmethod
    def is_small_book(page_count) -> bool:
        return page_count < 200

book = Book(title='The book', author='John Doe', pages=200)
print(book.get_description())
print(Book.get_book_count())
print(Book.is_small_book(150))
print(Book.is_small_book(300))

# Output:
'The book' by John Doe, 200
1
True
False

 

# 기본적인 상속의 예
class Person():
    # 생성자
    def __init__(self, name):
        self.name = name

    # get name
    def getName(self):
        return self.name

    # employee 객체인지 체크
    def isEmployee(self):
        return False

class Employee(Person):
    def isEmployee(self):
        return  True

def main():
    e1 = Person('Big Boss')
    print(e1.getName(), e1.isEmployee())

    e2 = Employee('Ant worker')
    print(e2.getName(), e2.isEmployee())

if __name__ == '__main__':
    main()
 
 #결과
Big Boss False
Ant worker True

 

# Super - Sub class 상속 관계의 예
# Super에는 멤버와 setter / getter를 두고
# Sub에 메서드를 유지

class Polygon:
    __width = None
    __heigh = None
    
    def set_value(self, w, h):
        self.__width = w
        self.__heigh = h
    
    def get_width(self):
        return self.__width
    
    def get_height(self):
        return self.__heigh
    
class Square(Polygon):
    def area(self):
        return self.get_width() * self.get_height()

class Triangle(Polygon):
    def area(self):
        return self.get_width() * self.get_height() * 1/2

s1 = Square()
s1.set_value(8, 15)
print(s1.area())

t1 = Triangle()
t1.set_value(5, 10)
print(t1.area())

# 결과
120
25.0

 

# In Python, super() built-in has two major use cases:
# - Allows us to avoid using base class explicitly
# - Working with Multiple Ingeritance

# 상속 관계일 때 super class의 메서드 직접 호출
class SuperClass:
    def __init__(self, in_msg):
        print('super class __init__ called.', in_msg)

class SubClass(SuperClass):
    def __init__(self, msg):
        print('sub class __init__ called.')
        # SuperClass.__init__(self, msg)
        super().__init__(msg)
        # 수퍼 클래스를 명시해도 되지만,
        # super() 함수를 호출해도 된다.
        # 단, 이 경우 self 객체 전달은 하지 않는다.

sub1 = SubClass("I'm SUB")

# 결과
sub class __init__ called.
super class __init__ called. I'm SUB

 

# Composition

# content is part of container
# Salary is part of Employee
# - Employee is my container and Salary is my content.
# - Container are destroyed then the content will be of no use.
# - This is why composition has strong relationship.
# - Imply that means they are both depending on each other.

# 반면에 aggregation의 경우 약한 결합이며
# 아래의 예에서 객체를 생성할 때 Salary 객체를 생성해
# 그 객체를 Employee 객체를 만들 때 넘겨주면 된다.
# 다만, 그 경계와 구분은 모호하며 상황이나 문제에 따라 사용한다.
#
# self.final_salary = Salary(pay, reward) ->
# self.final_salary = salary_object

# salary_object = Salary(1000, 100))

# class Employee:
    # def __init__(self, name, position, salary_object)

class Salary:
    def __init__(self, pay, reward):
        self.pay = pay
        self.reward = reward
    
    def annual_salary(self):
        return self.pay * 12 + self.reward

class Employee:
    def __init__(self, name, position, pay, reward):
        self.name = name
        self.position = position
        self.final_salary = Salary(pay, reward)
        # self.final_salary가 객체임에 유의

    def get_final_salary(self):
        return self.final_salary.annual_salary()

emp = Employee("mike", "Py dev", 1000, 100)
print(emp.get_final_salary())

# 결과
12100

 

### 다형성 함수의 간단한 예
def myAdd(x, y, z = 0):
    return x + y + z

def main():
    print(myAdd(1, 2))
    print(myAdd(1, 2, 3))

if __name__ == '__main__':
    main()

#결과
3
6
### 클래스 메서드의 다형성
class hero():
    def say(self):
        print("I'm hero.")

class villain():
    def say(self):
        print("I'm villain!")

def main():
    h = hero()
    v = villain()

    for e in (h, v):
        e.say()

if __name__ == '__main__':
    main()

#결과
I'm hero.
I'm villain!
### 클래스 메서드와 스태틱 메서드의 사용
"""
1. We generally use the class method to create factory methods. Factory methods return class objects ( similar to a constructor ) for different use cases.
2. We generally use static methods to create utility functions.
"""
import datetime

class Person:
    staticVar = 0
    def __init__(self, name, age):
        Person.staticVar += 1
        self.name = name
        self.age = age

    def getName(self):
        return self.name

    @staticmethod
    def getObjCount():
        return Person.staticVar

    @classmethod
    def fromBirthYear(cls, name, year):
        return cls(name, datetime.date.today().year - year)

def main():
    p1 = Person('sally', 21)
    p2 = Person.fromBirthYear('sally', 1996)
    p3 = Person('danny', 30)

    print(p1.getName(), p1.age)
    print(p2.getName(), p2.age)
    print(p3.getName(), p3.age)

    print('\t')

    print('obj count:', Person.getObjCount())

if __name__ == '__main__':
    main()

#결과
sally 21
sally 27
danny 30
	
obj count: 3

 

# class method

class Car:
    LIMITER: int = 160
    # 인스턴스 메서드가 이 클래스 멤버를 변경해도 자신의 인스턴스
    # 내의 멤버만 변경되고 다른 객체의 클래스 멤버는 변경되지 않는다.
    # 그러나, 클래스 메서드가 클래스 멤버를 변경하면 모든 인스턴스의
    # 클래스 멤버가 동시에 변경된다. (공용 변수처럼)

    def __init__(self, brand: str, max_speed: int) -> None:
        self.brand = brand
        self.max_speed = max_speed
        
    @classmethod
    def change_limit(cls, new_limit: int) -> None:
        cls.LIMITER = new_limit

def main() -> None:
    c1: Car = Car("BMW", 160)
    c2: Car = Car("Porsche", 200)

    c1.change_limit(170)
    print(f"{c1.brand} limit: {c1.LIMITER} km")
    print(f"{c2.brand} limit: {c2.LIMITER} km")

    c2.change_limit(200)
    print(f"{c1.brand} limit: {c1.LIMITER} km")
    print(f"{c2.brand} limit: {c2.LIMITER} km")

main()

# 결과
BMW limit: 170 km
Porsche limit: 170 km
BMW limit: 200 km
Porsche limit: 200 km

 

# Doc Strings
# Sphinx Markup
# 기본적인 클래스 사용 예

class User:
    """
    Base class for creating user.
    """
    
    def __init__(self, user_id: int) -> None:
        self.user_id = user_id
    
    def show_id(self) -> None:
        """Print the user_id as an int"""
        print(self.user_id)

def user_exist(user: User, database: set[User]) -> bool:
    """
    Checks if a user is inside a database.
    :param user: the user to check for
    :param database: the database to check inside
    :return: bool
    """
    return user in database


def main() -> None:
    user: User = User(0)
    user.show_id()
    
    bob: User = User(0)
    anna: User = User(1)
    
    database: set[User] = {bob, anna}
    
    if user_exist(bob, database):
        print(f"User exists in database!")
    else:
        print("No user found...")

main()

# 결과
0
User exists in database!

 

# 간단 객체 관리
# 최대한 type hinting 사용
class Person:
    _counter:int = 0
    _instances: list[any] = []

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age
        self._instances.append(self)
        Person._counter += 1

    def __repr__(self) -> str:
        return f"{self.name}={self.age} old"

    @classmethod
    def get_instances(cls) -> list[any]:
        return cls._instances

    @classmethod
    def get_counter(cls) -> int:
        return cls._counter

def main() -> None:
    Bob: Person = Person('Bob', 29)
    Sally: Person = Person('Sally', 19)
    John: Person = Person('John', 39)
    Mike: Person = Person('Mike', 49)

    person_dict: dict[str, int] = {}
    for person in Person.get_instances():
        person_dict[person.name] = person.age
    print(person_dict)
    print(f"The count of Person object: {Person.get_counter()}")

main()

# 결과
{'Bob': 29, 'Sally': 19, 'John': 39, 'Mike': 49}
The count of Person object: 4

 

# @dataclass
# In Pyhton, a data class is a class that is designed to only old data values.
# They aren't different from regular classes, but they usually
# don't have any other methods.
# They are typically used to store information that will
# be passed between different parts of a program of a system

# However, when creating classes to work only as data containers,
# writing the __init__ method repeatedly can generate a great
# amount of work and potential errors.

# The dataclasses module, a feature introduced in Python 3.7,
# provides a way to create data classes in a simpler manner
# without the need to write methods.

# Using a data class can save us a lot of trouble
# when we just want to create classes that contain data.
from dataclasses import dataclass

@dataclass
class Coin:
    name: str
    value: float
    id: str

def main():
    bitcoin1: Coin = Coin("Bitcoin", 10_000, "BTC")
    bitcoin2: Coin = Coin("Bitcoin", 10_000, "BTC")
    ripple: Coin = Coin("Ripple", 200, "XRP")
    
    print(bitcoin1)
    print(ripple)
    
    print(f"(bitcoin1 == ripple): {bitcoin1 == ripple}")
    print(f"(bitcoin1 == bitcoin2): {(bitcoin1 == bitcoin2)}")
    

main()

# 결과
Coin(name='Bitcoin', value=10000, id='BTC')
Coin(name='Ripple', value=200, id='XRP')
(bitcoin1 == ripple): False
(bitcoin1 == bitcoin2): True

 

from dataclasses import dataclass, field

@dataclass
class Fruit:
    name: str
    grams: float
    price_per_kg: float
    edible: bool = True
    related_fruits: list[str] = field(default_factory=list)

def main():
    apple: Fruit = Fruit('apple', 100, 5)
    pear: Fruit = Fruit('Pear', 250, 10, related_fruits=['Apple', 'Orange'])
    print(apple)
    print(pear)
    print(pear.related_fruits)

main()

# 결과
Fruit(name='apple', grams=100, price_per_kg=5, edible=True, related_fruits=[])
Fruit(name='Pear', grams=250, price_per_kg=10, edible=True, related_fruits=['Apple', 'Orange'])
['Apple', 'Orange']
반응형

관련글 더보기

댓글 영역