상속의 MRO 및 Super에 대한 자세한 설명
Python Advanced - 상속의 MRO 및 Super
앞서 작성
별도의 언급이 없는 한 다음은 Python3을 기반으로 합니다
Abstract
이 문서에서는 Python
에서 상속 관계를 전달하는 방법을 설명합니다. > super()
는 "상위 클래스" 메서드를 호출하고, super(Type, CurrentClass)
는 CurrentClass
의 MRO
를 반환합니다. Type 및 Python
클래스가 올바르게 초기화되도록 설계하는 방법. Python
继承关系中如何通过super()
调用“父类”方法,super(Type, CurrentClass)
返回CurrentClass
的MRO
中Type
的下一个类的代理;以及如何设计Python
类以便正确初始化。
1. 单继承中父类方法调用
在继承中,调用父类方法是很有必要的。调用父类方法的场景有很多:
比如必须调用父类的构造方法
__init__
才能正确初始化父类实例属性,使得子类实例对象能够继承到父类实例对象的实例属性;再如需要重写父类方法时,有时候没有必要完全摒弃父类实现,只是在父类实现前后加一些实现,最终还是要调用父类方法
单继承是最简单的继承关系,多继承过于复杂,而且使用起来容易出错。因此一些高级语言完全摒弃了多继承,只支持单继承;一些高级语言虽然支持多继承,但也不推荐使用多继承。Python
也是一样,在不能完全掌握多继承时,最好不好使用,单继承能满足绝大部分的需求。
1.1 非绑定方式调用
绑定方法与非绑定方法的区别与联系参见:Python基础-类
如有以下继承关系两个类:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C') D.test(self)
现在要求在子类C
的test
函数中调用父类D
的test
实现。我们能想到最直接的方法恐怕是直接引用类对象D
的函数成员test
了:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')
尝试测试一下:
c = C() c.test()
output:
test in C test in D
看来非绑定的方式确实满足了当前调用父类方法的需求。
1.2 builtin 函数 super
参考Python tutorial关于super的描述: super([type[, object-or-type]])
Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped.
super
函数返回委托类type
的父类或者兄弟类方法调用的代理对象。super
用来调用已经在子类中重写了的父类方法。方法的搜索顺序与getattr()
函数相同,只是参数类type
本身被忽略。
1.3 绑定方式调用
使用绑定方式调用父类方法,自然不能显式传入参数当前对象(self
)。现在super
函数能够范围对父类的代理,因为在单继承中子类有且仅有一个父类,所以父类是明确的,我们完全清楚调用的父类方法是哪个:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')super().test() # super(C, self).test()的省略形式
2. 深入super
事实上,super
函数返回的代理对象是一个bultin class super
,正如它的名字所指,类super
代理了子类的父类。在单继承关系中,super
代理的类很容易找到吗,就是子类的唯一父类;但是在多继承关系中,super
除了能代理子类的父类外,还有可能代理子类的兄弟类。
2.1 复杂的多继承
在多继承关系中,继承关系可能会相当复杂。
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')class B(D): def test(self):print('test in B')class A(B, C):pass
类A
继承层次结构如下:
object | D / \ B C \ / A
类A
的继承关系中存在菱形结构,即可以通过多条路径从类A
到达某个父类,这里是D
。
如果现在要求在类A
中调用“父类”的test
方法,需要一种对test
方法的搜索解析顺序,来决定到底是调用B,C或D
的test
方法。
2.2 方法解析顺序(MRO)
上面提出的对test
的方法的搜索顺序,就是方法解析顺序了。
深度优先Python
旧式类中,方法解析顺序是深度优先,多个父类从左到右。
广度优先Python
新式类中,方法解析顺序是广度优先,多个父类从左到右。
所以上面的解析顺序是:A -> B -> C -> D -> object
。
Python
中,类的__mro__
属性展示了方法搜索顺序,可以调用mro()
方法或者直接引用__mro__
- 🎜예를 들어, 상위 클래스의 생성자 메서드
__init__
를 호출하여 올바르게 초기화해야 합니다. 상위 클래스 인스턴스 속성을 사용하면 하위 클래스 인스턴스 객체가 상위 클래스 인스턴스 객체의 인스턴스 속성을 상속할 수 있습니다. 🎜 - 🎜상위 클래스 메서드를 재정의해야 하는 경우 때로는 완전히 버릴 필요가 없습니다. 부모 클래스 구현, 그냥 부모 클래스에 구현하기 전후에 구현을 추가한 후에도 여전히 부모 클래스 메서드를 호출해야 합니다🎜
Python
도 마찬가지입니다. 다중 상속을 완전히 익힐 수 없으면 단일 상속을 사용하지 않는 것이 가장 좋습니다. 🎜1.1 비바인딩 모드에서 호출
🎜바운드 메서드와 비바인딩 메서드의 차이점과 연결은 Python 기본 - 클래스를 참조하세요.🎜🎜다음과 같은 상속 관계를 갖는 두 클래스가 있는 경우 :🎜🎜print(A.mro())print(A.__mro__)
test
함수에서 상위 클래스 D
의 test
구현을 호출해야 합니다. >C. 우리가 생각할 수 있는 가장 직접적인 방법은 아마도 클래스 객체 D
의 함수 멤버 test
를 직접 참조하는 것입니다: 🎜🎜[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>] (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
a = A() a.test() # output: test in B
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')class B(D): def test(self):print('test in B')
1.2 내장 함수 super
🎜super에 대한 Python 튜토리얼의 설명을 참조하세요:super([type[, object-or-type]])
🎜🎜🎜프록시 객체 반환 이는 유형의 상위 또는 형제 클래스에 메소드 호출을 위임합니다. 이는 클래스에서 재정의된 상속된 메소드에 액세스하는 데 유용합니다. 검색 순서는 유형 자체를 건너뛴다는 점을 제외하면 getattr()에서 사용하는 것과 동일합니다.🎜 🎜🎜 super
함수는 위임 클래스 type
의 상위 클래스 또는 형제 클래스 메서드가 호출한 프록시 객체를 반환합니다. super
는 하위 클래스에서 재정의된 상위 클래스 메서드를 호출하는 데 사용됩니다. 메서드 검색 순서는 매개변수 클래스 type
자체가 무시된다는 점을 제외하면 getattr()
함수와 동일합니다. 🎜1.3 바인딩 메서드 호출
🎜바인딩 메서드를 사용하여 상위 클래스 메서드를 호출하면 당연히 매개변수 현재 개체(self
)를 명시적으로 전달할 수 없습니다. 이제 super
함수는 상위 클래스의 프록시 범위를 지정할 수 있습니다. 단일 상속에서 하위 클래스에는 상위 클래스가 하나만 있고 상위 클래스가 하나만 있으므로 상위 클래스가 명확하고 어떤 상위 클래스 메서드가 무엇인지 완전히 알 수 있기 때문입니다. :🎜🎜object | D / \ C B
super
함수에 의해 반환된 프록시 객체는 이름에서 알 수 있듯이 bultin 클래스 super
입니다. 에서 < code>super 클래스는 하위 클래스의 상위 클래스를 나타냅니다. 단일 상속 관계에서는 super
가 나타내는 클래스를 쉽게 찾을 수 있나요? 이 클래스는 하위 클래스의 유일한 상위 클래스이지만 다중 상속 관계에서는 super
가 해당 클래스를 찾을 수 있습니다. 프록시 하위 클래스뿐만 아니라 상위 클래스 외에도 하위 클래스의 형제 클래스도 프록시할 수 있습니다. 🎜2.1 복잡한 다중 상속
🎜다중 상속 관계에서 상속 관계는 상당히 복잡할 수 있습니다. 🎜🎜class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')super().test()class B(D): def test(self):print('test in B')super().test()class A(B, C):passb = B() b.test()print('==========') a = A() a.test()
A
클래스의 상속 계층은 다음과 같습니다. 🎜test in B test in D==========test in B test in C test in D
A
클래스의 상속 관계에는 다이아몬드 구조가 있습니다. A
클래스의 여러 경로가 특정 상위 클래스(이 경우 D
)에 도달합니다. 🎜🎜이제 A
클래스에서 "🎜Parent Class🎜"의 test
메서드를 호출해야 하는 경우 code>test< /code> 메소드의 검색 및 파싱 순서에 따라 <code>B, C, D
의 test
메소드 호출 여부를 결정합니다. 🎜2.2 Method Resolution Order(MRO)
🎜위에서 제안한test
의 메소드 검색 순서는 Method Resolution Order입니다. 🎜🎜🎜깊이 우선🎜🎜Python
이전 스타일 클래스에서 메서드 구문 분석 순서는 깊이 우선이며 여러 상위 클래스는 왼쪽에서 오른쪽입니다. 🎜🎜너비 우선🎜🎜Python
새 스타일 클래스에서 메서드 확인 순서는 너비 우선이고 여러 상위 클래스는 왼쪽에서 오른쪽입니다. 🎜🎜위의 구문 분석 순서는 A -> B -> C -> D -> object입니다. 🎜🎜Python
에서 클래스의 __mro__
속성은 메서드 검색 순서를 보여줍니다. mro()
메서드를 호출하거나 < code>__mro__< /code>검색 순서 가져오기: 🎜🎜def super(cls, inst):
mro = inst.__class__.mro() # Always the most derived classreturn mro[mro.index(cls) + 1]
로그인 후 복사로그인 후 복사🎜output:
[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>]
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
로그인 후 복사로그인 후 복사所以
a = A()
a.test() # output: test in B
로그인 후 복사로그인 후 복사变化的MRO
即使是同一个类,在不同的MRO中位置的前后关系都是不同的。如以下类:
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')class B(D): def test(self):print('test in B')
로그인 후 복사로그인 후 복사类B
的继承层次结构为:
object
|
D
/ \
C B
로그인 후 복사로그인 후 복사类B
的MRO:B -> D -> object
对比类A
的MRO:A -> B -> C -> D -> object
同样的类B
,在两个不同的MRO中位置关系也是不同的。可以说,在已有的继承关系中加入新的子类,会在MRO中引入新的类,并且改变解析顺序。
那么可以想象,同样在类B
的test中通过super
调用父类方法,在不同的MRO中实际调用的方法是不同的。
如下:
class D(object): def test(self):print('test in D')class C(D): def test(self):print('test in C')super().test()class B(D): def test(self):print('test in B')super().test()class A(B, C):passb = B()
b.test()print('==========')
a = A()
a.test()
로그인 후 복사로그인 후 복사output:
test in B
test in D==========test in B
test in C
test in D
로그인 후 복사로그인 후 복사因为在原有的类关系中加入B
和C
的子类A
,使得在B
的test
方法中调用super
的test
方法发生了改变,原来调用的是其父类D
的test
方法,现在调用的是其兄弟类C
的test
方法。
从这里可以看出super
不总是代理子类的父类,还有可能代理其兄弟类。
因此在设计多继承关系的类体系时,要特别注意这一点。
2.3 再看super方法
方法super([type[, object-or-type]])
,返回的是对type
的父类或兄弟类的代理。
如果第二个参数省略,返回的super
对象是未绑定到确定的MRO
上的:
如果第二个参数是对象,那么isinstance(obj, type)
必须为True
;
如果第二个参数是类型,那么issubclass(type2, type)
必须为True
,即第二个参数类型是第一个参数类型的子类。
在super
函数的第二个参数存在时,其实现大概如以下:
def super(cls, inst):
mro = inst.__class__.mro() # Always the most derived classreturn mro[mro.index(cls) + 1]
로그인 후 복사로그인 후 복사很明显,super
返回在第二个参数对应类的MRO
列表中,第一个参数type
的下一个类的代理。因此,要求第一个参数type
存在于第二个参数类的MRO
是必要的,只有第一个参数类是第二个参数所对应类的父类,才能保证。
super()
super
函数是要求有参数的,不存在无参的super
函数。在类定义中以super()
方式调用,是一种省略写法,由解释器填充必要参数。填充的第一个参数是当前类,第二个参数是self
:
super() => super(current_class, self)
로그인 후 복사所以,super()
这种写法注定只能在类定义中使用。
现在再来看上面的继承关系:
class D(object):def test(self):print('test in D')class C(D):def test(self):print('test in C')# super().test() # 与下面的写法等价super(C, self).test() # 返回self对应类的MRO中,类C的下一个类的代理class B(D):def test(self):print('test in B')# super().test() # 与下面的写法等价super(B, self).test() # 返回self对应类的MRO中,类B的下一个类的代理class A(B, C):pass
로그인 후 복사因此:
b = B()
b.test() # 基于类B的MRO(B->D->object),类B中的super()代理Dprint('==========')
a = A()
a.test() # 基于类A的MRO(A->B->C->D->object),类B中的super()代理C
로그인 후 복사以上就是在继承关系中引入新类,改变方法解析顺序的实例。
super([type[, object-or-type]])
的第二个参数,对象和类还有一点区别:使用对象返回的是代理使用绑定方法,使用类返回的代理使用非绑定方法。
如:
b = B()super(B, b).test()super(B, B).test(b)
로그인 후 복사这两种方式得到的结果是相同的,区别在于非绑定调用与绑定调用。
3. 最佳实践
3.1 不可预测的调用
普通的函数或者方法调用中,调用者肯定事先知道被调用者所需的参数,然后可以轻松的组织参数调用。但是在多继承关系中,情况有些尴尬,使用super
代理调用方法,编写类的作者并不知道最终会调用哪个类的方法,这个类都可能尚未存在。
如现在一作者编写了以下类:
class D(object):def test(self):print('test in D')
class B(D):def test(self):print('test in B')super().test()
로그인 후 복사在定义类D
时,作者完全不可能知道test
方法中的super().test()
最终会调用到哪个类。
因为如果后来有人在这个类体系的基础上,引入了如下类:
class C(D):def test(self):print('test in C')super().test()
class A(B, C):passa = A()
a.test()
로그인 후 복사此时会发现类B
的test
方法中super().test()
调用了非原作者编写的类的方法。
这里test
方法的参数都是确定的,但是在实际生产中,可能各个类的test
方法都是不同的,如果新引入的类C
需要不同的参数:
class C(D):def test(self, param_c):print('test in C, param is', param_c)super().test()
class A(B, C):passa = A()
a.test()
로그인 후 복사类B
的调用方式调用类C
的test
方法肯定会失败,因为没有提供任何参数。类C
的作者是不可能去修改类B
的实现。那么,如何适应这种参数变换的需求,是在设计Python
类中需要考虑的问题。
3.2 实践建议
事实上,这种参数的变换在构造方法上能体现得淋漓尽致,如果子类没有正确初始化父类,那么子类甚至不能从父类继承到需要的实例属性。
所以,Python
的类必须设计友好,才能拓展,有以下三条指导原则:
通过super()
调用的方法必须存在;
调用者和被调用者参数必须匹配;
所有对父类方法的调用都必须使用super()
3.3 参数匹配
super()
代理的类是不可预测的,需要匹配调用者和可能未知的调用者的参数。
固定参数
一种方法是使用位置参数固定函数签名。就像以上使用的test()
一样,其签名是固定的,只要要传递固定的参数,总是不会出错。
关键字参数
每个类的构造方法可能需要不同的参数,这时固定参数满足不了这种需求了。幸好,Python
中的关键字参数可以满足不定参数的需求。设计函数参数时,参数由关键字参数和关键字参数字典组成,在调用链中,每一个函数获取其所需的关键字参数,保留不需要的参数到**kwargs
中,传递到调用链的下一个函数,最终**kwargs
为空时,调用调用链中的最后一个函数。
示例:
class Shape(object):def __init__(self, shapename, **kwargs):self.shapename = shapenamesuper().__init__(**kwargs)class ColoredShape(Shape):def __init__(self, color, **kwargs):self.color = colorsuper().__init__(**kwargs)
cs = ColoredShape(color='red', shapename='circle')
로그인 후 복사参数的剥落步骤为:
使用cs = ColoredShape(color='red', shapename='circle')
初始化ColoredShape
;
ColoredShape
的__init__
方法获取其需要的关键字参数color
,此时的kwargs
为{shapename:'circle'}
;
调用调用链中Shape
的__init__
方法,该方法获取所需关键字参数shapename
,此时kwargs
为{}
;
最后调用调用链末端objet.__init__
,此时因为kwargs
已经为空。
初始化子类传递的关键字参数尤为重要,如果少传或多传,都会导致初始化不成功。只有MRO
中每个类的方法都是用super()
来调用“父类”方法时,才能保证super()
调用链不会断掉。
3.4 保证方法存在
上面的例子中,由于顶层父类object
总是存在__init__
方法,在任何MRO
链中也总是最后一个,因此任意的super().__init__
调用总能保证是object.__init__
结束。
但是其他自定义的方法得不到这样的保证。这时需要手动创建类似object
的顶层父类:
class Root:def draw(self):# the delegation chain stops hereassert not hasattr(super(), 'draw')class Shape(Root):def __init__(self, shapename, **kwds):self.shapename = shapenamesuper().__init__(**kwds)def draw(self):print('Drawing. Setting shape to:', self.shapename)super().draw()class ColoredShape(Shape):def __init__(self, color, **kwds):self.color = colorsuper().__init__(**kwds)def draw(self):print('Drawing. Setting color to:', self.color)super().draw()
cs = ColoredShape(color='blue', shapename='square')
cs.draw()
로그인 후 복사如果有新的类要加入到这个MRO
体系,新的子类也要继承Root
,这样,所有的对draw()
的调用都会经过Root
,而不会到达没有draw
方法的object
了。这种对于子类的扩展要求,应当详细注明在文档中,便于使用者阅读。这种限制与Python
所有异常都必须继承自BaseException
一样。
3.5 组合不友好的类
对于那些不友好的类:
class Moveable:def __init__(self, x, y):self.x = xself.y = ydef draw(self):print('Drawing at position:', self.x, self.y)
로그인 후 복사如果希望使用它的功能,直接将其加入到我们友好的继承体系中,会破坏原有类的友好性。
除了通过继承获得第三方功能外,还有一种称之为组合的方式,即把第三方类作为组件的方式揉入类中,使得类具有第三方的功能:
class MoveableAdapter(Root):def __init__(self, x, y, **kwds):self.movable = Moveable(x, y)super().__init__(**kwds)def draw(self):self.movable.draw()super().draw()
로그인 후 복사Moveable
被作为组件整合到适配类MoveableAdapter
中,适配类拥有了Moveable
的功能,而且是友好实现的。完全可以通过继承适配类的方式,将Moveable
的功能加入到友好的继承体系中:
class MovableColoredShape(ColoredShape, MoveableAdapter):passMovableColoredShape(color='red', shapename='triangle',
x=10, y=20).draw()
로그인 후 복사
参考
Python’s super() considered super!
Python tutorial#super
위 내용은 상속의 MRO 및 Super에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











PHP는 주로 절차 적 프로그래밍이지만 객체 지향 프로그래밍 (OOP)도 지원합니다. Python은 OOP, 기능 및 절차 프로그래밍을 포함한 다양한 패러다임을 지원합니다. PHP는 웹 개발에 적합하며 Python은 데이터 분석 및 기계 학습과 같은 다양한 응용 프로그램에 적합합니다.

PHP는 웹 개발 및 빠른 프로토 타이핑에 적합하며 Python은 데이터 과학 및 기계 학습에 적합합니다. 1.PHP는 간단한 구문과 함께 동적 웹 개발에 사용되며 빠른 개발에 적합합니다. 2. Python은 간결한 구문을 가지고 있으며 여러 분야에 적합하며 강력한 라이브러리 생태계가 있습니다.

Python은 부드러운 학습 곡선과 간결한 구문으로 초보자에게 더 적합합니다. JavaScript는 가파른 학습 곡선과 유연한 구문으로 프론트 엔드 개발에 적합합니다. 1. Python Syntax는 직관적이며 데이터 과학 및 백엔드 개발에 적합합니다. 2. JavaScript는 유연하며 프론트 엔드 및 서버 측 프로그래밍에서 널리 사용됩니다.

VS 코드는 파이썬을 작성하는 데 사용될 수 있으며 파이썬 애플리케이션을 개발하기에 이상적인 도구가되는 많은 기능을 제공합니다. 사용자는 다음을 수행 할 수 있습니다. Python 확장 기능을 설치하여 코드 완료, 구문 강조 및 디버깅과 같은 기능을 얻습니다. 디버거를 사용하여 코드를 단계별로 추적하고 오류를 찾아 수정하십시오. 버전 제어를 위해 git을 통합합니다. 코드 서식 도구를 사용하여 코드 일관성을 유지하십시오. 라인 도구를 사용하여 잠재적 인 문제를 미리 발견하십시오.

PHP는 1994 년에 시작되었으며 Rasmuslerdorf에 의해 개발되었습니다. 원래 웹 사이트 방문자를 추적하는 데 사용되었으며 점차 서버 측 스크립팅 언어로 진화했으며 웹 개발에 널리 사용되었습니다. Python은 1980 년대 후반 Guidovan Rossum에 의해 개발되었으며 1991 년에 처음 출시되었습니다. 코드 가독성과 단순성을 강조하며 과학 컴퓨팅, 데이터 분석 및 기타 분야에 적합합니다.

vs 코드에서는 다음 단계를 통해 터미널에서 프로그램을 실행할 수 있습니다. 코드를 준비하고 통합 터미널을 열어 코드 디렉토리가 터미널 작업 디렉토리와 일치하는지 확인하십시오. 프로그래밍 언어 (예 : Python의 Python Your_file_name.py)에 따라 실행 명령을 선택하여 성공적으로 실행되는지 여부를 확인하고 오류를 해결하십시오. 디버거를 사용하여 디버깅 효율을 향상시킵니다.

VS 코드는 Windows 8에서 실행될 수 있지만 경험은 크지 않을 수 있습니다. 먼저 시스템이 최신 패치로 업데이트되었는지 확인한 다음 시스템 아키텍처와 일치하는 VS 코드 설치 패키지를 다운로드하여 프롬프트대로 설치하십시오. 설치 후 일부 확장은 Windows 8과 호환되지 않을 수 있으며 대체 확장을 찾거나 가상 시스템에서 새로운 Windows 시스템을 사용해야합니다. 필요한 연장을 설치하여 제대로 작동하는지 확인하십시오. Windows 8에서는 VS 코드가 가능하지만 더 나은 개발 경험과 보안을 위해 새로운 Windows 시스템으로 업그레이드하는 것이 좋습니다.

VS 코드 확장은 악의적 인 코드 숨기기, 취약성 악용 및 합법적 인 확장으로 자위하는 등 악성 위험을 초래합니다. 악의적 인 확장을 식별하는 방법에는 게시자 확인, 주석 읽기, 코드 확인 및주의해서 설치가 포함됩니다. 보안 조치에는 보안 인식, 좋은 습관, 정기적 인 업데이트 및 바이러스 백신 소프트웨어도 포함됩니다.
