admin管理员组文章数量:1598869
封装、继承、多态
当谈到封装、继承和多态时,通常是在面向对象编程 (OOP) 的上下文中讨论的。
封装 (Encapsulation) 示例:
class Person:
def __init__(self, name, age):
self.__name = name # 使用双下划线前缀将属性变为私有
self.__age = age
def get_name(self):
return self.__name
def set_name(self, new_name):
if len(new_name) > 0:
self.__name = new_name
def get_age(self):
return self.__age
def set_age(self, new_age):
if new_age >= 0:
self.__age = new_age
# 创建一个Person对象并访问属性
person = Person("Alice", 30)
print(person.get_name()) # 输出:Alice
person.set_age(32) # 设置年龄
print(person.get_age()) # 输出:32
在这个示例中,我们通过将属性标记为私有(使用双下划线前缀)来封装数据,并提供公共方法(get_name
、set_name
、get_age
、set_age
)来访问和修改这些属性。这样,我们可以控制属性的访问和修改,以确保数据的安全性。
使用面向对象来开发,示例代码如下:
- 类:理解为模板
- 对象:理解为实物
- 在使用面向过程编程时,当需要对数据处理时,需要考虑用哪个模板中哪个函数来进行操作,但是当用面向对象编程时,因为已经将数据存储到了这个独立的空间中,这个独立的空间(即对象)中通过一个特殊的变量(
__class__
)能够获取到类(模板),而且这个类中的方法是有一定数量的,与此类无关的将不会出现在本类中,因此需要对数据处理时,可以很快速的定位到需要的方法是谁 这样更方便 - 全局变量是只能有1份的,多个函数需要多个备份时,往往需要利用其它的变量来进行储存;而通过封装 会将用来存储数据的这个变量 变为了对象中的一个“全局”变量,只要对象不一样那么这个变量就可以再有1份,所以这样更方便
- 代码划分更清晰
继承 (Inheritance) 示例:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return f"{self.name} 说:汪汪汪!"
class Cat(Animal):
def speak(self):
return f"{self.name} 说:喵喵喵!"
# 创建一个Dog对象和一个Cat对象,并调用speak方法
dog = Dog("旺财")
cat = Cat("小花")
print(dog.speak()) # 输出:旺财 说:汪汪汪!
print(cat.speak()) # 输出:小花 说:喵喵喵!
在这个示例中,我们定义了一个基类 Animal
,然后创建了两个派生类 Dog
和 Cat
,它们继承了 Animal
的属性和方法。每个派生类都可以覆盖基类的方法(例如 speak
),以实现自己的行为。
- 能够提升代码的重用率,即开发一个类,可以在多个子功能中直接使用
- 继承能够有效的进行代码的管理,当某个类有问题只要修改这个类就行,而其继承这个类的子类往往不需要就修改
多态 (Polymorphism) 示例:
class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
# 创建一个Shape的列表,包含不同类型的图形对象
shapes = [Circle(5), Rectangle(4, 6), Circle(3)]
# 计算并输出每个图形的面积
for shape in shapes:
print(f"面积:{shape.area()}")
在这个示例中,我们定义了一个基类 Shape
,以及两个派生类 Circle
和 Rectangle
,它们都具有一个名为 area
的方法。通过多态,我们可以在一个列表中存储不同类型的图形对象,然后通过统一的方法调用来计算它们的面积。这就是多态的核心思想,不同的对象可以对相同的方法做出不同的响应。
class MiniOS(object):
"""MiniOS 操作系统类 """
def __init__(self, name):
self.name = name
self.apps = [] # 安装的应用程序名称列表
def __str__(self):
return "%s 安装的软件列表为 %s" % (self.name, str(self.apps))
def install_app(self, app):
# 判断是否已经安装了软件
if app.name in self.apps:
print("已经安装了 %s,无需再次安装" % app.name)
else:
app.install()
self.apps.append(app.name)
class App(object):
def __init__(self, name, version, desc):
self.name = name
self.version = version
self.desc = desc
def __str__(self):
return "%s 的当前版本是 %s - %s" % (self.name, self.version, self.desc)
def install(self):
print("将 %s [%s] 的执行程序复制到程序目录..." % (self.name, self.version))
class PyCharm(App):
pass
class Chrome(App):
def install(self):
print("正在解压缩安装程序...")
super().install()
linux = MiniOS("Linux")
print(linux)
pycharm = PyCharm("PyCharm", "1.0", "python 开发的 IDE 环境")
chrome = Chrome("Chrome", "2.0", "谷歌浏览器")
chrome2 = Chrome("Chrome2", "3.0", "古哥浏览器")
linux.install_app(pycharm)
linux.install_app(chrome)
linux.install_app(chrome)
linux.install_app(chrome2)
print(linux)
Linux 安装的软件列表为 []
将 PyCharm [1.0] 的执行程序复制到程序目录...
正在解压缩安装程序...
将 Chrome [2.0] 的执行程序复制到程序目录...
已经安装了 Chrome,无需再次安装
正在解压缩安装程序...
将 Chrome2 [3.0] 的执行程序复制到程序目录...
Linux 安装的软件列表为 ['PyCharm', 'Chrome', 'Chrome2']
多态需要用到继承,重写,调用某个方法时,要看是父类创建的实例,还是子类创建的实例,实例不同调用的方法不同
关键点:
- 定义了一个基类App,它具有name、version、desc属性,以及一个install方法用于安装。
- 定义了两个子类PyCharm和Chrome,继承自App。
- Chrome类对install方法进行了重写,添加了自定义的安装逻辑。
- 在MainOS类中,通过install_app方法安装不同的App实例对象,调用其install方法。
- 对于同一个install_app方法,传入不同的类的实例,会调用各自类中不同的install实现。这就是多态。
- 多态允许我们基于同一接口编写可扩展和可维护的代码。子类可以改变父类的行为,同时保持接口一致。
所以多态的关键是:
- 有继承关系
- 子类覆盖父类方法
- 对不同子类实例调用相同的方法
这使得调用者可以一致地调用方法,而具体执行代码依赖于运行时对象的类型。这就是多态
-
面向过程开发,简单、开发前期快速,越往后越复杂,适合小工程
-
面向对象开发,复杂、开发前期较慢,越往后开发越方便,适合大工程
没有最好的开发模式,只有经过多多练习,见的多了,感受多了,自然也就能够在不同的任务、不同的工程,使用合适的方式进行开发
静态方法和类方法
类属性、实例属性
它们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
-
实例属性属于对象
-
类属性属于类
class Province(object):
# 类属性
country = 'china'
def __init__(self, name):
# 实例属性
self.name = name
# 创建一个实例对象
obj = Province('shangdong')
# 直接访问实例属性
print(obj.name)
# 直接访问类属性
Province.country
由上述代码可以看出【实例属性需要通过对象来访问】【类属性通过类访问】,在使用上可以看出实例属性和类属性的归属是不同的。
其在内容的存储方式类似如下图:
由上图看出:
-
类属性在内存中只保存一份
-
实例属性在每个对象中都要保存一份
-
通过类创建实例对象时,如果每个对象需要具有相同名字的属性,那么就使用类属性,用一份既可
实例方法、静态方法和类方法
方法包括:实例方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
-
实例方法:由对象调用;至少一个self参数;执行实例方法时,自动将调用该方法的对象赋值给self;
-
类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类赋值给cls;
-
静态方法:由类调用;无默认参数;
class Foo(object):
def __init__(self, name):
self.name = name
def ord_func(self):
""" 定义实例方法,至少有一个self参数 """
# print(self.name)
print('实例方法')
@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """
print('类方法')
@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""
print('静态方法')
f = Foo("china")
# 调用实例方法
f.ord_func()
# 调用类方法
Foo.class_func()
# 调用静态方法
Foo.static_func()
实例方法
类方法
静态方法
- 相同点:对于所有的方法而言,均属于类,所以 在内存中也只保存一份
- 不同点:方法调用者不同、调用方法时自动传入的参数不同。
# 实例方法
# self参数表示当前对象的实例
class Person:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f'Hello, my name is {self.name}')
p = Person('John')
p.say_hello() # 调用实例方法,自动传递当前实例p作为self参数
# 静态方法
# 使用@staticmethod装饰器声明,不需要默认的self参数
class Person:
def __init__(self, name):
self.name = name
@staticmethod
def hello():
print('Hello!')
Person.hello() # 直接使用类名调用静态方法,不需要实例化
# 类方法
# 使用@classmethod装饰器声明,默认参数cls代表当前类本身
class Person:
count = 0 # 类属性
def __init__(self, name):
self.name = name
Person.count += 1
@classmethod
def print_count(cls):
print(f'Number of people: {cls.count}')
Person.print_count() # 使用类名调用,将类本身作为参数传递
person=Person('')
person.print_count()
Hello, my name is John
Hello!
Number of people: 0
Number of people: 1
# 实例方法需要实例化对象后才能调用,自动传递self参数
# 静态方法不需要实例化,通过类名直接调用
# 类方法也不需要实例化,会将类本身作为第一个参数传递
多继承以及MRO顺序
1. 多继承中调用父类方式不同结果不同
2.1 单独调用父类的方法
# coding=utf-8
print("******多继承使用类名.__init__ 发生的状态******")
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
Parent.__init__(self, name)
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender):
print('Son2的init开始被调用')
self.gender = gender
Parent.__init__(self, name)
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
Son1.__init__(self, name, age) # 单独调用父类的初始化方法
Son2.__init__(self, name, gender)
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用类名.__init__ 发生的状态******\n\n")
复制Error复制成功...
运行结果:
******多继承使用类名.__init__ 发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用类名.__init__ 发生的状态******复制Error复制成功...
2.2 多继承中super调用被重写的父类方法
print("******多继承使用super().__init__ 发生的状态******")
class Parent(object):
def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init开始被调用')
self.age = age
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init开始被调用')
self.gender = gender
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
# super(Grandson, self).__init__(name, age, gender)
super().__init__(name, age, gender)
print('Grandson的init结束被调用')
print(Grandson.__mro__)
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
print("******多继承使用super().__init__ 发生的状态******\n\n")复制Error复制成功...
运行结果:
******多继承使用super().__init__ 发生的状态******
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
******多继承使用super().__init__ 发生的状态******复制Error复制成功...
2.3 注意
以上2个代码执行的结果不同
-
如果2个子类中都继承了父类,当在子类中通过父类名调用时,parent被执行了2次
-
如果2个子类中都继承了父类,当在子类中通过super调用时,parent被执行了1次
2. 单继承中super
print("******单继承使用super().__init__ 发生的状态******")
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
def p(self): #继承父类的方法
print('父类')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
super().__init__(name) # 单继承不能提供全部参数
print('Son1的init结束被调用')
class Grandson(Son1):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
super().__init__(name, age) # 单继承不能提供全部参数
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('年龄:', gs.p())
# print('性别:', gs.gender)
print("******单继承使用super().__init__ 发生的状态******\n\n")
******单继承使用super().__init__ 发生的状态******
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
父类
年龄: None
******单继承使用super().__init__ 发生的状态******
总结
-
super().__init__
相对于类名.__init__
,在单继承上用法基本无差 -
但在多继承上有区别,
super
方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果 -
多继承时,使用
super
方法,对父类的传参数,由于super
的算法导致的原因,必须把参数全部传递,否则会报错 -
单继承时,使用
super
方法,则不能全部传递,只能传父类方法所需的参数,否则会报错 -
多继承时,相对于使用类名.init方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
小试牛刀(以下为面试题)
以下的代码的输出将是什么? 说出你的答案并解释。
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)
复制Error复制成功...
答案, 以上代码的输出是:
1 1 1
1 2 1
3 2 3
使你困惑或是惊奇的是关于最后一行的输出是 3 2 3 而不是 3 2 1。为什么改变了 Parent.x 的值还会改变 Child2.x 的值,但是同时 Child1.x 值却没有改变?
这个答案的关键是,在 Python 中,类变量在内部是作为字典处理的。如果一个变量的名字没有在当前类的字典中发现,将搜索祖先类(比如父类)直到被引用的变量名被找到(如果这个被引用的变量名既没有在自己所在的类又没有在祖先类中找到,会引发一个 AttributeError 异常 )。
因此,在父类中设置 x = 1 会使得类变量 x 在引用该类和其任何子类中的值为 1。这就是因为第一个 print 语句的输出是 1 1 1。
随后,如果任何它的子类重写了该值(例如,我们执行语句 Child1.x = 2),然后,该值仅仅在子类中被改变。这就是为什么第二个 print 语句的输出是 1 2 1。
最后,如果该值在父类中被改变(例如,我们执行语句 Parent.x = 3),这个改变会影响到任何未重写该值的子类当中的值(在这个示例中被影响的子类是 Child2)。这就是为什么第三个 print 输出是 3 2 3。
多重继承例子
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
d = D()
d.method()
输出:
D
B
C
A
这个例子展示了在多重继承中super()如何工作。它按照方法解析顺序(MRO)调用父类的方法
C3线性化算法: 这个算法确保:
- 子类在父类之前被检查
- 保持定义时的顺序(所以B在C之前)
- 确保每个类只出现一次在MRO中
这个输出不是DBAC,而是DBCA,原因如下:
1. 方法解析顺序 (MRO):
Python使用C3线性化算法来确定方法解析顺序。在这个例子中,如果我们打印`D.__mro__`,会得到:
```
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
```
这就是Python在查找方法时会遵循的顺序。
2. 调用过程:
- 首先调用D的method,打印"D"
- D使用super().method(),这会调用MRO中下一个类(B)的method
- B的method打印"B",然后调用super().method()
- 根据MRO,B的下一个类是C,所以C的method被调用
- C的method打印"C",然后调用super().method()
- 最后,A的method被调用,打印"A"
3. 为什么不是DBAC:
- 如果按照继承的视觉层次来看,可能会认为应该是DBAC
- 但是,Python的MRO保证了一个类在其父类之前被检查
- 在这个例子中,C是D的一个父类,所以它必须在A之前被检查
4. C3线性化算法:
这个算法确保:
- 子类在父类之前被检查
- 保持定义时的顺序(所以B在C之前)
- 确保每个类只出现一次在MRO中
所以,输出DBCA是Python的MRO工作的结果,它保证了一致的方法解析顺序,避免了菱形继承等问题可能带来的歧义。
这个行为可能初看起来有点反直觉,但它实际上提供了一个一致和可预测的方法调用顺序,这在复杂的继承结构中非常重要。
Python中的`super()`函数主要用于在子类中调用父类的方法。它提供了一种更灵活和可维护的方式来扩展或修改父类的行为。让我给你举几个例子来说明`super()`的作用:
1. 基本用法:
```python
class Parent:
def greet(self):
print("Hello from Parent")
class Child(Parent):
def greet(self):
super().greet() # 调用父类的greet方法
print("Hello from Child")
child = Child()
child.greet()
```
输出:
```
Hello from Parent
Hello from Child
```
在这个例子中,`Child`类扩展了`Parent`类的`greet`方法,同时保留了父类的行为。
2. 多重继承:
```python
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
d = D()
d.method()
```
输出:
```
D
B
C
A
```
这个例子展示了在多重继承中`super()`如何工作。它按照方法解析顺序(MRO)调用父类的方法。
3. 构造函数中的使用:
```python
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
class Square(Rectangle):
def __init__(self, side_length):
super().__init__(side_length, side_length)
square = Square(5)
print(square.length, square.width)
```
输出:
```
5 5
```
在这个例子中,`Square`类使用`super()`调用`Rectangle`的构造函数,避免了代码重复。
4. 带参数的`super()`:
```python
class Parent:
def __init__(self, name):
self.name = name
class Child(Parent):
def __init__(self, name, age):
super().__init__(name)
self.age = age
def introduce(self):
print(f"I'm {self.name} and I'm {self.age} years old")
child = Child("Alice", 10)
child.introduce()
```
输出:
```
I'm Alice and I'm 10 years old
```
这个例子展示了如何在子类的构造函数中使用`super()`来初始化父类的属性。
这些例子展示了`super()`在不同场景下的使用。它允许你在扩展或修改父类行为的同时,保持代码的清晰和可维护性。
内建属性
1. 内建属性
内建属性,往往是指我们,在使用类时,可以直接使用的那些功能,例如__new__
、__init__
等
1.1 新式类
Python3中定义的类都是新式类的,无论是否写明一个类继承object
,都会间接或直接继承object
teachclass.py
class Person(object):
pass
Python3
中类的内建属性和方法
常用内建属性
常用专有属性 | 说明 | 触发方式 |
---|---|---|
__init__ |
构造初始化函数 | 创建实例后,赋值时使用,在__new__ 后自动调用 |
__new__ |
生成实例所需属性 | 创建实例时 |
__class__ |
实例所在的类 | 实例.__class__ |
__str__ |
实例字符串表示,可读性 | print(类实例) 如没实现,使用repr结果 |
__repr__ |
实例字符串表示,准确性 | 类实例 回车 或者 print(repr(类实例)) |
__del__ |
析构(对象被删除前做清理工作) | del 实例 后,如果对象引用计数为0,则自动调用 |
__dict__ |
实例自定义属性 | vars(实例.__dict__) |
__doc__ |
类文档,子类不继承 | help(类或实例) |
__getattribute__ |
属性访问拦截器 | 访问实例属性时 |
__bases__ |
类的所有父类构成元素 | 类名.__bases__ |
# 示例和解释Python特殊方法和属性
# __init__: 用于初始化对象的构造器方法。
# 参数 self: 表示实例本身。
# 参数 args: 表示传递给构造器的参数,可变数量。
class ExampleClass:
def __init__(self, *args):
self.args = args
# __new__: 用于创建并返回类的新实例,通常用于元类或控制实例创建的高级用途。
# 参数 cls: 表示类本身。
# 参数 args: 表示传递给构造器的参数,可变数量。
class ExampleClassNew:
def __new__(cls, *args):
instance = super(ExampleClassNew, cls).__new__(cls)
return instance
# __class__: 特殊属性,用于获取对象所属的类。
example = ExampleClass()
example_class = example.__class__
print('example_class',example_class)
# __str__: 定义对象的“非正式”或可打印字符串表示。
# 参数 self: 表示实例本身。
class ExampleStr:
def __str__(self):
return "Example of __str__"
e=ExampleStr()
print('e.__str__()',e.__str__())
# __repr__: 定义对象的“官方”字符串表示,通常用于调试。
# 参数 self: 表示实例本身。
class ExampleRepr:
def __repr__(self):
return "Example of __repr__"
# __del__: 析构器方法,在对象被销毁前调用。
# 参数 self: 表示实例本身。
class ExampleDel:
def __del__(self):
print("Example of __del__")
# __dict__: 特殊属性,包含了对象的属性字典。
example_dict = example.__dict__
print('example_dict',example_dict)
# __doc__: 特殊属性,包含了类的文档字符串。
example_doc = ExampleClass.__doc__
print('example_doc',example_doc)
# __getattribute__: 用于获取属性的特殊方法。
# 参数 self: 表示实例本身。
# 参数 name: 属性名称。
class ExampleGetattribute:
def __getattribute__(self, name):
return f"Attribute {name} accessed"
# __bases__: 特殊属性,包含了类的基类元组。
example_bases = ExampleClass.__bases__
print('example_bases',example_bases)
下面是一些Python特殊方法和属性的例子,以及每个方法和属性的中文解释和参数意义:
1. `__init__`: 这是一个构造器方法,用于初始化对象。
- `self`: 表示实例本身。
- `*args`: 表示传递给构造器的参数,数量可变。
2. `__new__`: 用于创建并返回类的新实例,通常用于元类或控制实例创建的高级用途。
- `cls`: 表示类本身。
- `*args`: 表示传递给构造器的参数,数量可变。
3. `__class__`: 这是一个特殊属性,用于获取对象所属的类。
4. `__str__`: 定义对象的“非正式”或可打印字符串表示。
- `self`: 表示实例本身。
5. `__repr__`: 定义对象的“官方”字符串表示,通常用于调试。
- `self`: 表示实例本身。
6. `__del__`: 这是一个析构器方法,在对象被销毁前调用。
- `self`: 表示实例本身。
7. `__dict__`: 这是一个特殊属性,包含了对象的属性字典。
8. `__doc__`: 这是一个特殊属性,包含了类的文档字符串。
9. `__getattribute__`: 用于获取属性的特殊方法。
- `self`: 表示实例本身。
- `name`: 属性名称。
10. `__bases__`: 这是一个特殊属性,包含了类的基类元组。
3. __getattribute__
属性
__getattribute__
功能很强大:能够完成属性访问时进行拦截
例子:
class Itcast(object):
def __init__(self,subject1):
self.subject1 = subject1
self.subject2 = 'cpp'
# 属性访问时拦截器,打log
def __getattribute__(self,obj):
if obj == 'subject1':
print('log subject1')
return 'redirect python'
else: # 测试时注释掉这2行,将找不到subject2
return object.__getattribute__(self,obj)
def show(self):
print('this is Itcast')
s = Itcast("python")
print(s.subject1)
print(s.subject2)
运行结果:
log subject1
redirect python
cpp
getattribute的坑
class Person(object):
def __getattribute__(self,obj):
print("---test---")
if obj.startswith("a"):
return "hahha"
else:
return self.test
def test(self):
print("heihei")
t.Person()
t.a # 返回hahha
t.b
# 会让程序死掉
# 原因是:当t.b执行时,会调用Person类中定义的__getattribute__方法,但是在这个方法的执行过程中
# if条件不满足,所以 程序执行else里面的代码,即return self.test 问题就在这,因为return 需要把
# self.test的值返回,那么首先要获取self.test的值,因为self此时就是t这个对象,所以self.test就是
# t.test 此时要获取t这个对象的test属性,那么就会跳转到__getattribute__方法去执行,即此时产
# 生了递归调用,由于这个递归过程中 没有判断什么时候推出,所以这个程序会永无休止的运行下去,又因为
# 每次调用函数,就需要保存一些数据,那么随着调用的次数越来越多,最终内存吃光,所以程序 崩溃
#
# 注意:以后不要在__getattribute__方法中调用self.xxxx
魔法属性
无论人或事物往往都有不按套路出牌的情况,Python的类属性也是如此,存在着一些具有特殊含义的属性,详情如下:
1. doc
-
表示类的描述信息
class Foo:
""" 描述类信息,这是用于看片的神奇 """
def func(self):
pass
print(Foo.__doc__)
#输出:类的描述信息
描述类信息,这是用于看片的神奇
2. module 和 class
-
module 表示当前操作的对象在那个模块
-
class 表示当前操作的对象的类是什么
test.py
class Person(object):
def __init__(self):
self.name = 'laowang'
main.py
from test import Person
obj = Person()
print(obj.__module__) # 输出 test 即:输出模块
print(obj.__class__) # 输出 test.Person 即:输出类
3. init
-
初始化方法,通过类创建对象时,自动触发执行
class Person:
def __init__(self, name):
self.name = name
self.age = 18
obj = Person('laowang') # 自动执行类中的 __init__ 方法
4. del
-
当对象在内存中被释放之前,自动触发执行
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,del的调用是由解释器在进行垃圾回收前 自动触发,执行一些完善工作
深度学习除外
class Foo:
def __del__(self):
pass
5. call
-
对象后面加括号,触发执行。
注:init方法的执行是由创建对象触发的,即:对象 = 类名()
;而对于 call 方法的执行是由对象后加括号触发的,即:对象()
或者 类()()
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
6. dict
-
类或对象中的所有属性
类的实例属性属于对象;类中的类属性和方法等属于类,即:
class Province(object):
country = 'China'
def __init__(self, name, count):
self.name = name
self.count = count
def func(self, *args, **kwargs):
print('func')
# 获取类的属性,即:类属性、方法、
print(Province.__dict__)
# 输出:{'__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', 'country': 'China', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Province' objects>, 'func': <function Province.func at 0x101897950>, '__init__': <function Province.__init__ at 0x1018978c8>}
obj1 = Province('山东', 10000)
print(obj1.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 10000, 'name': '山东'}
obj2 = Province('山西', 20000)
print(obj2.__dict__)
# 获取 对象obj1 的属性
# 输出:{'count': 20000, 'name': '山西'}
7. str
-
如果一个类中定义了str方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo:
def __str__(self):
return 'laowang'
obj = Foo()
print(obj)
# 输出:laowang
8、getitem、setitem、delitem
-
用于索引操作,如字典。以上分别表示获取、设置、删除数据
# -*- coding:utf-8 -*-
class Foo(object):
def __getitem__(self, key):
print('__getitem__', key)
def __setitem__(self, key, value):
print('__setitem__', key, value)
def __delitem__(self, key):
print('__delitem__', key)
obj = Foo()
result = obj'k1'] # 自动触发执行 __getitem__
obj['k2'] = 'laowang' # 自动触发执行 __setitem__
del obj'k1'] # 自动触发执行 __delitem__
版权声明:本文标题:面向对象 元类 gil log 协程 垃圾回收 描述符 property 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1728313911a1153348.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论