Python 클래스의 내부를 살펴보겠습니다.
python 동영상 튜토리얼 칼럼에서는 Python 클래스의 내부를 소개합니다.
이 기사에서는 Python 3.8의 클래스와 객체 뒤에 있는 몇 가지 개념과 구현 원칙에 대해 설명합니다. 주로 Python 클래스 및 객체 속성의 저장, 함수 및 메서드, 설명자 및 객체 메모리 사용을 설명하려고 합니다. . 최적화 지원은 물론 상속 및 속성 조회와 같은 관련 문제도 지원됩니다.
간단한 예부터 시작해 보겠습니다.
class Employee: outsource = False def __init__(self, department, name): self.department = department self.name = name @property def inservice(self): return self.department is not None def __repr__(self): return f"<Employee: {self.department}-{self.name}>"employee = Employee('IT', 'bobo')复制代码
employee
개체는 Employee
클래스의 인스턴스이며 department
와 department
라는 두 가지 속성을 갖습니다. 값이 이 인스턴스에 속하는 이름
입니다. outsource
는 클래스 속성이고 소유자는 클래스입니다. 클래스의 모든 인스턴스 객체는 이 속성의 값을 공유하며 이는 다른 객체 지향 언어와 일치합니다. employee
对象是 Employee
类的一个实例,它有两个属性 department
和 name
,其值属于该实例。outsource
是类属性,所有者是类,该类的所有实例对象共享此属性值,这跟其他面向对象语言一致。
更改类变量会影响到该类的所有实例对象:
>>> e1 = Employee('IT', 'bobo')>>> e2 = Employee('HR', 'cici')>>> e1.outsource, e2.outsource (False, False)>>> Employee.outsource = True>>> e1.outsource, e2.outsource>>> (True, True)复制代码
这仅限于从类更改,当我们从实例更改类变量时:
>>> e1 = Employee('IT', 'bobo')>>> e2 = Employee('HR', 'cici')>>> e1.outsource, e2.outsource (False, False)>>> e1.outsource = True>>> e1.outsource, e2.outsource (True, False)复制代码
是的,当你试图从实例对象修改类变量时,Python 不会更改该类的类变量值,而是创建一个同名的实例属性,这是非常正确且安全的。在搜索属性值时,实例变量会优先于类变量,这将在继承与属性查找一节中详细解释。
值得特别注意的是,当类变量的类型是可变类型时,你是从实例对象中更改的它们的:
>>> class S:... L = [1, 2] ...>>> s1, s2 = S(), S()>>> s1.L, s2.L ([1, 2], [1, 2])>>> t1.L.append(3)>>> t1.L, s2.L ([1, 2, 3], [1, 2, 3])复制代码
好的实践方式是应当尽量的避免这样的设计。
属性的存储
本小节我们一起来看看 Python 中的类属性、方法及实例属性是如何关联存储的。
实例属性
在 Python 中,所有实例属性都存储在 __dict__
字典中,这就是一个常规的 dict
,对于实例属性的维护即是从该字典中获取和修改,它对开发者是完全开放的。
>>> e = Employee('IT', 'bobo')>>> e.__dict__ {'department': 'IT', 'name': 'bobo'}>>> type(e.__dict__)dict>>> e.name is e.__dict__['name']True>>> e.__dict__['department'] = 'HR'>>> e.department'HR'复制代码
正因为实例属性是采用字典来存储,所以任何时候我们都可以方便的给对象添加或删除字段:
>>> e.age = 30 # 并没有定义 age 属性>>> e.age30>>> e.__dict__ {'department': 'IT', 'name': 'bobo', 'age': 30}>>> del e.age>>> e.__dict__ {'department': 'IT', 'name': 'd'}复制代码
我们也可以从字典中实例化一个对象,或者通过保存实例的 __dict__
来恢复实例。
>>> def new_employee_from(d):... instance = object.__new__(Employee)... instance.__dict__.update(d)... return instance ...>>> e1 = new_employee_from({'department': 'IT', 'name': 'bobo'})>>> e1 <Employee: IT-bobo>>>> state = e1.__dict__.copy()>>> del e1>>> e2 = new_employee_from(state)>>> e2>>> <Employee: IT-bobo>复制代码
因为 __dict__
的完全开放,所以我们可以向其中添加任何 hashable 的 immutable key,比如数字:
>>> e.__dict__[1] = 1>>> e.__dict__ {'department': 'IT', 'name': 'bobo', 1: 1}复制代码
这些非字符串的字段是我们无法通过实例对象访问的,为了确保不会出现这样的情况,除非必要的情况下,一般最好不要直接对 __dict__
进行写操作,甚至不要直接操作 __dict__
。
所以有一种说法是 Python is a "consenting adults language"。
这种动态的实现使得我们的代码非常灵活,很多时候非常的便利,但这也付出了存储和性能上的开销。所以 Python 也提供了另外一种机制(__slots__
)来放弃使用 __dict__
,以节约内存,提高性能,详见 __slots__ 一节。
类属性
同样的,类属性也在存储在类的 __dict__
字典中:
>>> Employee.__dict__ mappingproxy({'__module__': '__main__', 'outsource': True, '__init__': <function __main__.Employee.__init__(self, department, name)>, 'inservice': <property at 0x108419ea0>, '__repr__': <function __main__.Employee.__repr__(self)>, '__str__': <function __main__.Employee.__str__(self)>, '__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of 'Employee' objects>, '__doc__': None}>>> type(Employee.__dict__) mappingproxy复制代码
与实例字典的『开放』不同,类属性使用的字典是一个 MappingProxyType
对象,它是一个不能 setattr
的字典。这意味着它对开发者是只读的,其目的正是为了保证类属性的键都是字符串,以简化和加快新型类属性的查找和 __mro__
的搜索逻辑。
>>> Employee.__dict__['outsource'] = FalseTypeError: 'mappingproxy' object does not support item assignment复制代码
因为所有的方法都归属于一个类,所以它们也存储在类的字典中,从上面的例子中可以看到已有的 __init__
和 __repr__
方法。我们可以再添加几个来验证:
class Employee: # ... @staticmethod def soo(): pass @classmethod def coo(cls): pass def foo(self): pass复制代码
>>> Employee.__dict__ mappingproxy({'__module__': '__main__', 'outsource': False, '__init__': <function __main__.Employee.__init__(self, department, name)>, '__repr__': <function __main__.Employee.__repr__(self)>, 'inservice': <property at 0x108419ea0>, 'soo': <staticmethod at 0x1066ce588>, 'coo': <classmethod at 0x1066ce828>, 'foo': <function __main__.Employee.foo(self)>, '__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of 'Employee' objects>, '__doc__': None})复制代码
继承与属性查找
目前为止,我们已经知道,所有的属性和方法都存储在两个 __dict__
字典中,现在我们来看看 Python 是如何进行属性查找的。
Python 3 中,所有类都隐式的继承自 object
,所以总会有一个继承关系,而且 Python 是支持多继承的:
>>> class A:... pass...>>> class B:... pass...>>> class C(B):... pass...>>> class D(A, C):... pass...>>> D.mro() [<class '__main__.D'>, <class '__main__.A'>, <class '__main__.C'>, <class '__main__.B'>, <class 'object'>]复制代码
mro()
是一个特殊的方法,它返回类的线性解析顺序。
属性访问的默认行为是从对象的字典中获取、设置或删除属性,例如对于 e.f
def get_attribute(obj, name): class_definition = obj.__class__ descriptor = None for cls in class_definition.mro(): if name in cls.__dict__: descriptor = cls.__dict__[name] break if hasattr(descriptor, '__set__'): return descriptor, 'data descriptor' if name in obj.__dict__: return obj.__dict__[name], 'instance attribute' if descriptor is not None: return descriptor, 'non-data descriptor' else: raise AttributeError复制代码
>>> e = Employee('IT', 'bobo')>>> get_attribute(e, 'outsource') (False, 'non-data descriptor')>>> e.outsource = True>>> get_attribute(e, 'outsource') (True, 'instance attribute')>>> get_attribute(e, 'name') ('bobo', 'instance attribute')>>> get_attribute(e, 'inservice') (<property at 0x10c966d10>, 'data descriptor')>>> get_attribute(e, 'foo') (<function __main__.Employee.foo(self)>, 'non-data descriptor')复制代码
>>> class Manager(Employee):... def __init__(self, *arg):... self.inservice = True... super().__init__(*arg) ...>>> m = Manager("HR", "cici") AttributeError: can't set attribute复制代码
속성 저장
🎜이 섹션에서는 Python의 클래스 속성, 메서드 및 인스턴스 속성이 어떻게 연결되고 저장되는지 살펴보겠습니다. 🎜인스턴스 속성🎜🎜Python에서 모든 인스턴스 속성은 일반 dict
인 __dict__
사전에 저장됩니다. 인스턴스 속성의 유지 관리는 개발자에게 완전히 공개된 이 사전에서 속성을 얻고 수정하는 것입니다. 🎜class Function:
def __get__(self, obj, objtype=None):
if obj is None: return self return types.MethodType(self, obj) # 将函数绑定为方法复制代码
로그인 후 복사로그인 후 복사🎜인스턴스 속성은 사전에 저장되므로 언제든지 객체에 필드를 쉽게 추가하거나 삭제할 수 있습니다. 🎜>>> Employee.coo
<bound method Employee.coo of <class '__main__.Employee'>>
>>> Employee.foo<function __main__.Employee.foo(self)>
>>> e = Employee('IT', 'bobo')
>>> e.foo<bound method Employee.foo of <Employee: IT-bobo>>复制代码
로그인 후 복사로그인 후 복사🎜또한 사전에서 객체를 인스턴스화하거나 인스턴스의 __dict__
를 저장할 수도 있습니다. > 인스턴스를 복원합니다. 🎜>>> e.foo.__self__
<Employee: IT-bobo>>>> e.foo.__self__.__class__
__main__.Employee复制代码
로그인 후 복사로그인 후 복사🎜__dict__
는 완전히 개방되어 있으므로 해싱 가능한 불변 키를 여기에 추가할 수 있습니다. 예를 들어 숫자는 다음과 같습니다. 🎜>>> def f1(self):... if isinstance(self, type):... return self.outsource... return self.name
...>>> bound_f1 = f1.__get__(e, Employee) # or bound_f1 = f1.__get__(e)>>> bound_f1
<bound method f1 of <Employee: IT-bobo>>>>> bound_f1.__self__
<Employee: IT-bobo>>>> bound_f1()'bobo'复制代码
로그인 후 복사로그인 후 복사🎜이것은 그렇지 않습니다. 이러한 상황이 발생하지 않도록 하려면 일반적으로 필요한 경우가 아니면 __dict__
에 직접 쓰지 않거나 __dict__
. 🎜🎜그래서 파이썬은 "성인의 동의를 받는 언어"라는 말이 있습니다. 🎜
🎜이 동적 구현은 많은 경우 코드를 매우 유연하고 편리하게 만들지 만 저장 및 성능이 저하됩니다. 따라서 Python은 메모리를 절약하고 성능을 향상시키기 위해 __dict__
사용을 포기하는 또 다른 메커니즘(__slots__
)도 제공합니다. 🎜클래스 속성🎜🎜마찬가지로 클래스 속성도 클래스의 __dict__
사전에 저장됩니다. 🎜class Test:
__slots__ = ('a', 'b') def __init__(self, a, b):
self.a = a
self.b = b复制代码
로그인 후 복사로그인 후 복사🎜"open"과 다릅니다. 인스턴스 사전의 클래스 특성에서 사용되는 사전은 setattr
일 수 없는 사전인 MappingProxyType
개체입니다. 이는 개발자를 위한 읽기 전용임을 의미하며, 그 목적은 클래스 속성의 키가 모두 문자열인지 확인하여 새 클래스 속성의 조회와 __mro__
의 검색 논리를 단순화하고 속도를 높이는 것입니다. . 🎜>>> t = Test(1, 2)>>> t.__dict__
AttributeError: 'Test' object has no attribute '__dict__'>>> Test.__dict__
mappingproxy({'__module__': '__main__', '__slots__': ('a', 'b'), '__init__': <function __main__.Test.__init__(self, a, b)>, 'a': <member 'a' of 'Test' objects>, 'b': <member 'b' of 'Test' objects>, '__doc__': None})复制代码
로그인 후 복사로그인 후 복사🎜모든 메소드는 클래스에 속하기 때문에 클래스 사전에도 저장되어 있습니다. 위의 예를 보면 기존 __init__
및 __repr__
메소드를 볼 수 있습니다. 확인을 위해 몇 가지를 더 추가할 수 있습니다: 🎜def __getattribute__(self, key):
v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(self) return v复制代码
로그인 후 복사로그인 후 복사
def calltracker(func): @wraps(func)
def wrapper(*args, **kwargs):
wrapper.calls += 1
return func(*args, **kwargs)
wrapper.calls = 0
return wrapper@calltrackerdef f():
return 'f called'复制代码
로그인 후 복사로그인 후 복사
상속 및 속성 조회
🎜지금까지 우리는 모든 속성과 메서드가 두 개의 에 저장되어 있다는 것을 이미 알고 있습니다. __dict__ 사전을 사용하여 이제 Python이 속성 조회를 수행하는 방법을 살펴보겠습니다. 🎜🎜Python 3에서는 모든 클래스가 암시적으로 객체
에서 상속되므로 항상 상속 관계가 있고 Python은 다중 상속을 지원합니다. 🎜>>> f.calls0>>> f()'f called'>>> f.calls1复制代码
로그인 후 복사로그인 후 복사🎜mro() code>는 특수한 상속입니다. 클래스의 선형 구문 분석 순서를 반환하는 메서드입니다. 🎜🎜속성 액세스의 기본 동작은 개체 사전에서 속성을 가져오거나 설정하거나 삭제하는 것입니다. 예를 들어 <code>e.f
찾기에 대한 간단한 설명은 다음과 같습니다.e.f
的查找顺序会从 e.__dict__['f']
开始,然后是 type(e).__dict__['f']
,接下来依次查找 type(e)
的基类(__mro__
顺序,不包括元类)。 如果找到的值是定义了某个描述器方法的对象,则 Python 可能会重载默认行为并转而发起调用描述器方法。这具体发生在优先级链的哪个环节则要根据所定义的描述器方法及其被调用的方式来决定。
class Function: def __get__(self, obj, objtype=None): if obj is None: return self return types.MethodType(self, obj) # 将函数绑定为方法复制代码
>>> Employee.coo <bound method Employee.coo of <class '__main__.Employee'>> >>> Employee.foo<function __main__.Employee.foo(self)> >>> e = Employee('IT', 'bobo') >>> e.foo<bound method Employee.foo of <Employee: IT-bobo>>复制代码
>>> e.foo.__self__ <Employee: IT-bobo>>>> e.foo.__self__.__class__ __main__.Employee复制代码
>>> def f1(self):... if isinstance(self, type):... return self.outsource... return self.name ...>>> bound_f1 = f1.__get__(e, Employee) # or bound_f1 = f1.__get__(e)>>> bound_f1 <bound method f1 of <Employee: IT-bobo>>>>> bound_f1.__self__ <Employee: IT-bobo>>>> bound_f1()'bobo'复制代码
__dict__
사전에 저장됩니다. 🎜class Test: __slots__ = ('a', 'b') def __init__(self, a, b): self.a = a self.b = b复制代码
setattr
일 수 없는 사전인 MappingProxyType
개체입니다. 이는 개발자를 위한 읽기 전용임을 의미하며, 그 목적은 클래스 속성의 키가 모두 문자열인지 확인하여 새 클래스 속성의 조회와 __mro__
의 검색 논리를 단순화하고 속도를 높이는 것입니다. . 🎜>>> t = Test(1, 2)>>> t.__dict__ AttributeError: 'Test' object has no attribute '__dict__'>>> Test.__dict__ mappingproxy({'__module__': '__main__', '__slots__': ('a', 'b'), '__init__': <function __main__.Test.__init__(self, a, b)>, 'a': <member 'a' of 'Test' objects>, 'b': <member 'b' of 'Test' objects>, '__doc__': None})复制代码
__init__
및 __repr__
메소드를 볼 수 있습니다. 확인을 위해 몇 가지를 더 추가할 수 있습니다: 🎜def __getattribute__(self, key): v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(self) return v复制代码
def calltracker(func): @wraps(func) def wrapper(*args, **kwargs): wrapper.calls += 1 return func(*args, **kwargs) wrapper.calls = 0 return wrapper@calltrackerdef f(): return 'f called'复制代码
상속 및 속성 조회
🎜지금까지 우리는 모든 속성과 메서드가 두 개의 에 저장되어 있다는 것을 이미 알고 있습니다. __dict__ 사전을 사용하여 이제 Python이 속성 조회를 수행하는 방법을 살펴보겠습니다. 🎜🎜Python 3에서는 모든 클래스가 암시적으로객체
에서 상속되므로 항상 상속 관계가 있고 Python은 다중 상속을 지원합니다. 🎜>>> f.calls0>>> f()'f called'>>> f.calls1复制代码
mro() code>는 특수한 상속입니다. 클래스의 선형 구문 분석 순서를 반환하는 메서드입니다. 🎜🎜속성 액세스의 기본 동작은 개체 사전에서 속성을 가져오거나 설정하거나 삭제하는 것입니다. 예를 들어 <code>e.f
찾기에 대한 간단한 설명은 다음과 같습니다.
e.f
的查找顺序会从e.__dict__['f']
开始,然后是type(e).__dict__['f']
,接下来依次查找type(e)
的基类(__mro__
顺序,不包括元类)。 如果找到的值是定义了某个描述器方法的对象,则 Python 可能会重载默认行为并转而发起调用描述器方法。这具体发生在优先级链的哪个环节则要根据所定义的描述器方法及其被调用的方式来决定。
所以,要理解查找的顺序,你必须要先了解描述器协议。
简单总结,有两种描述器类型:数据描述器和和非数据描述器。
如果一个对象除了定义
__get__()
之外还定义了__set__()
或__delete__()
,则它会被视为数据描述器。仅定义了__get__()
的描述器称为非数据描述器(它们通常被用于方法,但也可以有其他用途)
由于函数只实现 __get__
,所以它们是非数据描述器。
Python 的对象属性查找顺序如下:
- 类和父类字典的数据描述器
- 实例字典
- 类和父类字典中的非数据描述器
请记住,无论你的类有多少个继承级别,该类对象的实例字典总是存储了所有的实例变量,这也是 super
的意义之一。
下面我们尝试用伪代码来描述查找顺序:
def get_attribute(obj, name): class_definition = obj.__class__ descriptor = None for cls in class_definition.mro(): if name in cls.__dict__: descriptor = cls.__dict__[name] break if hasattr(descriptor, '__set__'): return descriptor, 'data descriptor' if name in obj.__dict__: return obj.__dict__[name], 'instance attribute' if descriptor is not None: return descriptor, 'non-data descriptor' else: raise AttributeError复制代码
>>> e = Employee('IT', 'bobo')>>> get_attribute(e, 'outsource') (False, 'non-data descriptor')>>> e.outsource = True>>> get_attribute(e, 'outsource') (True, 'instance attribute')>>> get_attribute(e, 'name') ('bobo', 'instance attribute')>>> get_attribute(e, 'inservice') (<property at 0x10c966d10>, 'data descriptor')>>> get_attribute(e, 'foo') (<function __main__.Employee.foo(self)>, 'non-data descriptor')复制代码
由于这样的优先级顺序,所以实例是不能重载类的数据描述器属性的,比如 property
属性:
>>> class Manager(Employee):... def __init__(self, *arg):... self.inservice = True... super().__init__(*arg) ...>>> m = Manager("HR", "cici") AttributeError: can't set attribute复制代码
发起描述器调用
上面讲到,在查找属性时,如果找到的值是定义了某个描述器方法的对象,则 Python 可能会重载默认行为并转而发起描述器方法调用。
描述器的作用就是绑定对象属性,我们假设 a
是一个实现了描述器协议的对象,对 e.a
发起描述器调用有以下几种情况:
- 直接调用:用户级的代码直接调用
e.__get__(a)
,不常用 - 实例绑定:绑定到一个实例,
e.a
会被转换为调用:type(e).__dict__['a'].__get__(e, type(e))
- 类绑定:绑定到一个类,
E.a
会被转换为调用:E.__dict__['a'].__get__(None, E)
在继承关系中进行绑定时,会根据以上情况和 __mro__
顺序来发起链式调用。
函数与方法
我们知道方法是属于特定类的函数,唯一的不同(如果可以算是不同的话)是方法的第一个参数往往是为类或实例对象保留的,在 Python 中,我们约定为 cls
或 self
, 当然你也可以取任何名字如 this
(只是最好不要这样做)。
上一节我们知道,函数实现了 __get__()
方法的对象,所以它们是非数据描述器。在 Python 访问(调用)方法支持中正是通过调用 __get__()
将调用的函数绑定成方法的。
在纯 Python 中,它的工作方式如下(示例来自描述器使用指南):
class Function: def __get__(self, obj, objtype=None): if obj is None: return self return types.MethodType(self, obj) # 将函数绑定为方法复制代码
在 Python 2 中,有两种方法: unbound method 和 bound method,在 Python 3 中只有后者。
bound method 与它们绑定的类或实例数据相关联:
>>> Employee.coo <bound method Employee.coo of <class '__main__.Employee'>> >>> Employee.foo<function __main__.Employee.foo(self)> >>> e = Employee('IT', 'bobo') >>> e.foo<bound method Employee.foo of <Employee: IT-bobo>>复制代码
我们可以从方法来访问实例与类:
>>> e.foo.__self__ <Employee: IT-bobo>>>> e.foo.__self__.__class__ __main__.Employee复制代码
借助描述符协议,我们可以在类的外部作用域手动绑定一个函数到方法,以访问类或实例中的数据,我将以这个示例来解释当你的对象访问(调用)类字典中存储的函数时将其绑定成方法(执行)的过程:
现有以下函数:
>>> def f1(self):... if isinstance(self, type):... return self.outsource... return self.name ...>>> bound_f1 = f1.__get__(e, Employee) # or bound_f1 = f1.__get__(e)>>> bound_f1 <bound method f1 of <Employee: IT-bobo>>>>> bound_f1.__self__ <Employee: IT-bobo>>>> bound_f1()'bobo'复制代码
总结一下:当我们调用 e.foo()
时,首先从 Employee.__dict__['foo']
中得到 foo
函数,在调用该函数的 foo
方法 foo.__get__(e)
将其转换成方法,然后执行 foo()
获得结果。这就完成了 e.foo()
-> f(e)
的过程。
如果你对我的解释感到疑惑,我建议你可以阅读官方的描述器使用指南以进一步了解描述器协议,在该文的函数和方法和静态方法和类方法一节中详细了解函数绑定为方法的过程。同时在 Python 类一文的方法对象一节中也有相关的解释。
__slots__
Python 的对象属性值都是采用字典存储的,当我们处理数成千上万甚至更多的实例时,内存消耗可能是一个问题,因为字典哈希表的实现,总是为每个实例创建了大量的内存。所以 Python 提供了一种 __slots__ 的方式来禁用实例使用 __dict__
,以优化此问题。
通过 __slots__
来指定属性后,会将属性的存储从实例的 __dict__
改为类的 __dict__
中:
class Test: __slots__ = ('a', 'b') def __init__(self, a, b): self.a = a self.b = b复制代码
>>> t = Test(1, 2)>>> t.__dict__ AttributeError: 'Test' object has no attribute '__dict__'>>> Test.__dict__ mappingproxy({'__module__': '__main__', '__slots__': ('a', 'b'), '__init__': <function __main__.Test.__init__(self, a, b)>, 'a': <member 'a' of 'Test' objects>, 'b': <member 'b' of 'Test' objects>, '__doc__': None})复制代码
关于 __slots__ 我之前专门写过一篇文章分享过,感兴趣的同学请移步理解 Python 类属性 __slots__ 一文。
补充
__getattribute__ 和 __getattr__
也许你还有疑问,那函数的 __get__
方法是怎么被调用的呢,这中间过程是什么样的?
在 Python 中 一切皆对象,所有对象都有一个默认的方法 __getattribute__(self, name)
。
该方法会在我们使用 .
访问 obj
的属性时会自动调用,为了防止递归调用,它总是实现为从基类 object
中获取 object.__getattribute__(self, name)
, 该方法大部分情况下会默认从 self
的 __dict__
字典中查找 name
(除了特殊方法的查找)。
话外:如果该类还实现了
__getattr__
,则只有__getattribute__
显式地调用或是引发了AttributeError
异常后才会被调用。__getattr__
由开发者自己实现,应当返回属性值或引发AttributeError
异常。
而描述器正是由 __getattribute__()
方法调用,其大致逻辑为:
def __getattribute__(self, key): v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(self) return v复制代码
请注意:重写
__getattribute__()
会阻止描述器的自动调用。
函数属性
函数也是 Python function
对象,所以一样,它也具有任意属性,这有时候是有用的,比如实现一个简单的函数调用跟踪装饰器:
def calltracker(func): @wraps(func) def wrapper(*args, **kwargs): wrapper.calls += 1 return func(*args, **kwargs) wrapper.calls = 0 return wrapper@calltrackerdef f(): return 'f called'复制代码
>>> f.calls0>>> f()'f called'>>> f.calls1复制代码
相关免费学习推荐:python视频教程
위 내용은 Python 클래스의 내부를 살펴보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

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

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

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

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

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

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

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

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

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

뜨거운 주제











PHP와 Python은 고유 한 장점과 단점이 있으며 선택은 프로젝트 요구와 개인 선호도에 달려 있습니다. 1.PHP는 대규모 웹 애플리케이션의 빠른 개발 및 유지 보수에 적합합니다. 2. Python은 데이터 과학 및 기계 학습 분야를 지배합니다.

Python과 JavaScript는 커뮤니티, 라이브러리 및 리소스 측면에서 고유 한 장점과 단점이 있습니다. 1) Python 커뮤니티는 친절하고 초보자에게 적합하지만 프론트 엔드 개발 리소스는 JavaScript만큼 풍부하지 않습니다. 2) Python은 데이터 과학 및 기계 학습 라이브러리에서 강력하며 JavaScript는 프론트 엔드 개발 라이브러리 및 프레임 워크에서 더 좋습니다. 3) 둘 다 풍부한 학습 리소스를 가지고 있지만 Python은 공식 문서로 시작하는 데 적합하지만 JavaScript는 MDNWebDocs에서 더 좋습니다. 선택은 프로젝트 요구와 개인적인 이익을 기반으로해야합니다.

Docker는 Linux 커널 기능을 사용하여 효율적이고 고립 된 응용 프로그램 실행 환경을 제공합니다. 작동 원리는 다음과 같습니다. 1. 거울은 읽기 전용 템플릿으로 사용되며, 여기에는 응용 프로그램을 실행하는 데 필요한 모든 것을 포함합니다. 2. Union 파일 시스템 (Unionfs)은 여러 파일 시스템을 스택하고 차이점 만 저장하고 공간을 절약하고 속도를 높입니다. 3. 데몬은 거울과 컨테이너를 관리하고 클라이언트는 상호 작용을 위해 사용합니다. 4. 네임 스페이스 및 CGroup은 컨테이너 격리 및 자원 제한을 구현합니다. 5. 다중 네트워크 모드는 컨테이너 상호 연결을 지원합니다. 이러한 핵심 개념을 이해 함으로써만 Docker를 더 잘 활용할 수 있습니다.

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

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

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

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

파이썬은 자동화, 스크립팅 및 작업 관리가 탁월합니다. 1) 자동화 : 파일 백업은 OS 및 Shutil과 같은 표준 라이브러리를 통해 실현됩니다. 2) 스크립트 쓰기 : PSUTIL 라이브러리를 사용하여 시스템 리소스를 모니터링합니다. 3) 작업 관리 : 일정 라이브러리를 사용하여 작업을 예약하십시오. Python의 사용 편의성과 풍부한 라이브러리 지원으로 인해 이러한 영역에서 선호하는 도구가됩니다.
