git merge conflict - fast-forward와 3-way-merge

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)


문제상황

지금까지 git을 통한 협업 시 merge conflict(병합충돌)은 같은 파일의 같은 라인을 수정하면 무조건 일어나는 것이라고 생각했다. 그런데 병합 충돌이 일어나지 않는 경우를 발견했다.

merge를 하는 방식에는 fast-forward3-way-merge 두 가지가 있다.

fast-forward

우선 fast-forward방식은 우선 아래의 그림에서부터 출발해본다.

여기서 C는 커밋(Commit)을 의미한다. 작업을 진행하며 위와같은 커밋이 진행되었고 현재 C2에 master가 존재하고 있다.

이때 이슈가 생겨 iss53이라는 브랜치를 생성하면 위와 같은 그림처럼 형성이 된다. 당연하게 iss53 브랜치에서 하는 행위 그 어떠한 것도 master에는 영향을 주지 않는다.

iss53 브랜치에서 이슈를 다 해결하고 커밋을 날리면 위와 같이 커밋 히스토리가 있을 것이다. 즉, master는 C2에 있지만 iss53은 C3를 가리키고 있다.

중요한 포인트가 뭐냐하면, C3의 커밋에는 C2까지의 내용이 모두 담겨있다는 사실이다. 이 상태에서 master로 merge를 하는 것은 단순히 master의 포인터를 최신 커밋을 가리키고 있는 C3로 옮기는 것을 의미한다.

현 브랜치가 iss53 브랜치 일 것이기에,

git ckeckout master
Switched to branch "iss53"

git merge iss53

Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

이렇게 merge 메시지에서 fast-forward가 보일 것이다.

C3 커밋이 C2커밋에 기반한 브랜치이기 때문에 브랜치 포인터는 merge 과정없이 그저 최신 커밋으로 이동을 한다. 이런 방식이 fast-forward 방식이다.

  • A와 B 브랜치가 있다.
  • A->B를 머지하려고 한다.
  • B가 A 이후의 커밋을 가리키고 있다. (B의 조상커밋이 A이다.)
  • A는 단순히 B와 동일한 커밋을 가리키도록 이동한다.

즉, 단순히 포인터를 최신 커밋으로 옮기는 것을 fast-forward 방식이라고 한다.

3-way-merge

이제 git에서 협업시 가장 많이 이루어지는 3-way-merge를 살펴보겠다.

다시 master 브랜치에서 이슈가 발생해 iss53 브랜치를 생성해 이슈를 해결하고 커밋을 날린 상황으로 돌아가 보겠다.

그런데 이때 master 브랜치에서 급하게 해결해야 될 핫 이슈가 발생했다.

그래서 master에서 hotfix 브랜치를 생성해서 급히 생긴 이슈를 해결하고 commit을 날린 후 master에서 merge를 했다.

즉, 이 상황은 아래와 같다.(iss53 시점에서부터 시작하겠다.)

git ckeckout master # iss53브랜치에서 master로 이동

git branch hotfix # hotfix브랜치 생성
git checkout hotfix # hotfix 브랜치로 이동

# 이슈 해결 뒤

git add -A
git commit -m 'hotfix 해결'

git ckeckout master # master 브랜치로 이동

git merge hotfix

그러면 이제 위 그림과 같이 master는 C4에 있게 되고 현재 포인터가 C4를 가리키게 된다.

이제 hotfix를 해결했으니 해당 브랜치는 삭제한다.

git branch -d hotfix

이제 다시 우리가 개발하던 iss53으로 돌아가 작업을 완료하고 커밋을 날려 C5라는 커밋 히스토리를 남겨보자.

이때 fast-forward와 다른 점이 보일 것이다.

현재 가리키는 커밋(C4-master)가 merge할 브랜치(C5-iss53)의 조상(C3)가 아니기 때문에 fast-forward 로 머지를 하지 않는다.

git checkout master

git merge iss53

Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

즉, 3-way-merge는 각 브랜치가 가리키는 커밋 2개(C4, C5)와 공통의 조상 1개(C2)를 사용하는 것을 의미한다.

3-way-merge의 최종결과는 위와 같다.

3-way-merge의 결과를 별도의 커밋으로 만들고 해당 브랜치가 그 커밋을 가리키도록 이동시킨다. 그 커밋은 C6이고 해당 커밋의 부모는 여러개가 되어있는 것을 볼 수 있다.

이때, 3-way-merge에서 merge-conflict가 발생할 수 있는 것이다. merge하는 두 브랜치에서 같은 파일의 같은 부분을 동시에 수정하게 되면 git은 해당 부분을 merge를 하지 못한다.

일반적으로 git은 자동적으로 merge를 하지 못하기 때문에 새로운 커밋또한 생성되지 않는다. 변경사항의 충돌은 개발자가 해결해주지 않는 한 merge 과정을 진행할 수도 없다.

그동안 병합충돌은 어느 방식에서든 같은 파일의 같은 곳을 수정하면 무조건적으로 일어나는 것이라고 생각했었다. 근데 해당 충돌이 일어나는 방식은 3-way-merge에서만 발생하는 것이란걸 알게되었다..

사실 지금도 매우 놀랍고 이해가 안가는 부분도 있지만.. 일단 그렇다고 한다. 만약 제가 이해한 부분이 틀렸다면 언제든지 댓글을 남겨주세요.

python reverse, reversed 함수의 차이

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


reverse()

  • list의 자료형 함수 = list 타입에서 제공하는 함수
  • list 요소를 역순으로 정렬해준다.
l = [1,2,3]  #[3,2,1]
t = (1,2,3)  # AttributeError: 'tuple' object has no attribute 'reverse'
d = {'a':1, 'b':2, 'c':3}  # AttributeError: 'dict' object has no attribute 'reverse'
s = '123'  # AttributeError: 'str' object has no attribute 'reverse'
li = [1,2,3]
li_reverse = li.reverse()
print(li_reverse)  # None, 값을 반환해주지 않고 단순히 해당 list를 뒤섞어준다.
print(li)  # [3,2,1]

reversed()

  • 파이썬의 내장함수 = list에서 제공해주는 함수가 아니다.
  • iterator의 요소를 역순으로 리턴
  • 원본을 변경하지는 않는다.
l = [1,2,3]  # <listreverseiterator object at 0x101053c10>
t = (1,2,3)  # <reversed object at 0x101053b50>
d = {'a':1, 'b':2, 'c':3}  # TypeError: argument to reversed() must be a sequence
s = '123'  # <reversed object at 0x101053c10>

이렇게 딕셔너리는 시퀀스 타입이 아니기에 지원해주지 않으며, 인덱스로 순차적인 접근이 가능하지 않은 애들한테는 reversed()는 지원되지 않는다. 그리고 reversed함수는 reversed객체를 반환한다. 근데 list에만 listreverseiterator를 반환하고 있다. (아직 이 유의미한 차이에 대해서는 잘 모르겠다…)

여튼, reversed객체는 list나 tuple로 사용하고 싶다면 아래와 같이 한다.

li = [ i for i in range(5) ]
print(list(reversed(li)))  # [4,3,2,1,0]
print(li)  # [0,1,2,3,4]

staticmethod와 classmethod 한번 더 정리해보기

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)


클래스의 인스턴스를 통하지 않고 클래스에서 바로 메서드를 호출하는 방법에는 정적 매서드와 클래스 메서드가 있다.

Staticmethod

우선 정적 메서드는 메서드 위에 @staticmethod을 붙이며 매개변수로 self를 지정하지 않는다.

class 클래스이름:
  @staticmethod
  def 메서드(매개변수1, 매개변수2)
    코드
class Calc:

  @staticmethod
  def add(a,b):
    print(a+b)

  @staticmethod
  def multi(a,b):
    print(a*b)

Calc.add(10,20) # 30
Calc.multi(3,5) # 15

Calc 클래스에서 @staticmethod 를 붙여서 add와 multi 메서드를 만들었고, 정적 메서드를 호출할 때는 클래스에서 바로 메서드를 호출하면 된다. 정적 메서드는 self 를 매개변수로 받지 않으며 그렇기 때문에 인스턴스 속성에도 접근이 불가능하다. 그래서 보통 정적메서드는 인스턴스 속성, 인스턴스 메서드가 필요 없을 때 사용한다.

즉, 여기서 만든 Calc 클래스에 들어가있는 add, multi 메서드는 숫자 두개를 더하거나 곱할 뿐 인스턴스의 속성은 필요하지 않음을 의미한다. 이러한 정적메서드는 메서드의 실행이 외부 상태에 영향을 끼치지 않는 순수함수(pure function)을 만들때 사용한다. 따라서 저적 메서드는 인스턴스의 상태를 변화시키지 않는 메서드를 만들때 사용한다.

Classmethod

클래스메서드 또한 메서드 위에 @classmethod 를 붙이고 첫번째 매개변수로 cls를 지정한다.

class 클래스이름:  
  @classmethod
  def 매서드(cls, 매개변수1, 매개변수2)
    코드
class Person:
  count = 0

  def __init__(self):
    Person.count += 1 # 인스턴스가 만들어질때 클래스 속성 count에 1을 더함


  @classmethod
  def print_count(cls):
    print(f'{cls.count}명 생성 되었습니다.') # cls로 클래스 속성에 접근


jane = Person()
mike = Person()

Person.print_count() # 2명 생성되었습니다.

코드를 풀이해보면

  • 인스턴스가 만들어질때마다 숫제를 세어주는 count += 1 을 만든다.
  • 이때 클래스 속성에 접근하는 것을 명확히 하기 위해 Person.count로 만들어준다.
  • 그리고 @classmethod를 통해 클래스 메서드를 생성한다.
  • 이때 클래스 메서드는 첫번째 매개변수를 cls로 받음으로써 현재 클래스를 받는다.
  • 따라서 cls를 통해 count 속성에 접근할 수 있게 된다.

  • 우리는 아래서 jane, mike 두개의 Person 인스턴스를 만들었고
  • print_count 호출하게 되면 생성된 두개의 인스턴스를 출력해주는 것을 볼 수 있다.
  • print_count는 클래스 메서드이기때문에 Person.print_count()로 호출된다.

클래스 메서드는 정적 메서드처럼 인스턴스 없이 호출된다는 점은 같다. 그러나

  • 클래스 메서드는 메서드 안에서 클래스 속성, 클래스 메서드에 접근해야할 때 사용한다.

특히 cls를 사용하면 메서드 안에서 현재 클래스의 인스턴스를 만들 수도 있다. cls는 클래스이기때문에 cls()는 Person()과 같다

클래스와 객체, 그리고 인스턴스는 무엇일까?

|

개인적인 연습 내용을 정리한 글입니다.
잘못된 내용이 있다면 편하게 댓글 남겨주세요!


프로그래밍을 공부하다보면, 프로그래밍 용어가 참 어려울때가 많다. 어떤 때는 인스턴스 어떤 때는 객체, 처음 공부를 시작할떄 제대로 머리에 정리를 안하고 지나가니 계속해서 발목을 잡는것 같아 이제서라도 하나하나 정리해보려고 한다.

그리고 덧붙여 나는 이 개념들이 자바를 공부하면서 좀 더 확실하게 알게 된 것 같다.

클래스?

  • 개념
    • 객체를 만들어내기 위한 설계도 혹은 틀 (ex. 붕어빵 틀)
    • 연관되어있는 변수(속성)과 메서드의 집합

객체?

  • 개념
    • 클래스에 선언된 모양 그대로 생성된 실체
  • 특징
    • ‘클래스의 인스턴스’
    • 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖는다.
    • oop 관점에서 클래스의 타입으로 선언되면 그것을 객체로 부른다.

인스턴스?

  • 개념
    • 설계도를 바탕으로 구현된 구체적인 실체
    • 즉, 객체를 소프트웨어에 실체화하면 그것을 인스턴스라고 부른다
    • 실체화된 인스턴스는 메모리에 할당된다.
  • 특징
    • 인스턴스는 객체에 포함됨
    • oop 관점에서 객체가 메모리에 할당되어 실제 사용될때 인스턴스라고 부른다
## 막간 지식으로 oop란 무엇인가?

OOP: Object Oriented Programming(객체지향프로그래밍)
객체지향 프로그래밍(이하 OOP)는 컴퓨터 프로그램을 객체들의 모임으로 파악하고자하는 프로그래밍의 패러다임 중 하나이다. OOP의 장점은 프로그램을 유연하고 변경이 용이하게 만들며 개발과 보수를 간편하게 만든다.

- 강한 응집력
- 약한 결합력

프로그램의 한 요소가 특정 목적을 위해 밀접하게 연관된 기능들로 모여 구현되어있고 그 요소요소가 다른 요소들과 관계를 크게 맺고있지 않은 것을 볼 수 있다. OOP의 경우 클래스 하나에 문제 해결을 위한 데이터를 모아놓은 객체를 활용함으로써 응집력을 강화하면서도 클래스 간 독립적인 디자인을 함으로써 결합력을 약하게 하고 있다.

클래스와 객체

클래스는 설계도, 객체는 설계도로 구현한 모든 대상을 의미

객체와 인스턴스

  • 클래스 타입으로 선언되었을 때 객체라고 부르며, 그 객체가 메모리에 할당되어 실제 사용될때 인스턴스라고 함
  • 객체는 현실 세계에 가깝고, 인스턴스는 소프트웨어 세계에 가깝다.
  • 객체는 ‘실제’, 인스턴스는 ‘관계’에 초점을 맞춤

참고>

클래스 속성과 인스턴스 속성 알아보기

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)


우선적으로 이야기 하자면

  • 클래스 속성 : 모든 인스턴스가 공유. 인스턴스 자체가 사용해야 하는 값을 저장할 때 사용
  • 인스턴스 속성: 인스턴스 별로 독립되어 있음. 각 인스턴스가 값을 따로 저장해야 할 때 사용

클래스 속성 사용하기

class 클래스이름:
  속성 = 값
class Person:
  bag = []


  def put_bag(self, stuff):
    self.bag.append(stuff)


jane = Person()
jane.put_bag('책')

mike = Person()
mike.put_bag('안경')

print(jane.bag)
print(mike.bag)
['책', '열쇠']
['책', '열쇠']

우리는 jane과 mike 각각의 인스턴스를 만들고자 put_bag 메서드에 물건을 넣었는데, 출력을 해보면 넣었던 물건들이 합쳐져서 나오는 것을 볼 수 있다. 즉, 클래스 속성은 클래스에 속해 있으며 모든 인스턴스에서 공유하고 있다.

그래서 우리가 put_bag 메서드에서 클래스 속성 bag 에 접근하기 위해 self를 사용했는데, 사실 self는 현재의 인스턴스를 뜻하는 것이기 때문에 클래스 속성을 지칭하기에는 조금 모호한 표현이다. 그래서 아래와 같이 클래스 속성에 접근할 때는 클래스 이름으로 접근하면 좀 더 코드가 명확해진다.

def put_bag(self, stuff):
  Person.bag.append(stuff)


print(Person.bag)

일반적으로 파이썬에서는 속성과 메서드 이름을 찾을 때 인스턴스 > 클래스 순으로 찾는다. 그래서 인스턴스 속성이 없으면 클래스 속성을 찾게 되므로 jane.bag혹은 mike.bag을 해도 문제없이 동작은 한다. 겉보기에는 인스턴스 속성을 사용하는 것 같지만 실제로는 클래스 속성이다.

인스턴스 속성 사용하기

class Person:
  def __init__(self):
    self.bag = []

  def put_bag(self, stuff):
    self.bag.appennd(stuff)


jane = Person()
jane.put_bag('책')

mike = Person()
mike.put_bag('안경')

print(jane.bag)
print(mike.bag)
['책']
['열쇠']

즉, 인스턴스 속성은 인스턴스별로 독립되어 있어 서로 영향을 주지 않는 것으로 볼 수 있다.

비공개 클래스 속성 사용하기

클래스 속성도 비공개 속성을 만들 수 있는데, 그 방법은 클래스 속성을 만들때 아래와 같이 만들면 된다. 이 속성은 클래스 안에서만 접근할 수 있고 클래스 바깥에서는 접근이 불가능하다.

class 클래스이름:  
  __속성 = 값

즉, 클래스에서 공개하고 싶지 않은 속성이 있다면 비공개 속성을 사용하면 된다.

class Knight:
  __item_limit = 10 # 비공개 클래스 속성

  def print_item_limit(self):
    print(Knight.__item_limit)  # 클래스 안에서는 접근 가능


x = Knight()
x.print_item_limit()

print(Knight.__item_limit) # 클래스 바깥에서는 접근 불가능
10
Traceback (most recent call last):
  File "C:\project\class_private_class_attribute_error.py ", line 11, in <module>
    print(Knight.__item_limit)
AttributeError: type object 'Knight' has no attribute '__item_limit'

실행을 하면 클래스 Knight의 비공개 클래스 속성 __item_limit는 클래스 안의 print_item_limit 메서드에만 접근할 수 있고 클래스 바깥에서 접근하면 에러가 발생한다. 아이켐의 보유 제한은 10개인데, 이 클래스를 사용하는 사람이 마음대로 수정하면 곤란하니 그럴때 사용하면 된다. 즉, 비공개 클래스 속성은 클래스 바깥으로 드러내고 싶지 않은 값에 사용한다