十八、类和对象
什么是类?
类可以比作是某种类型集合的描述
把一类相同的事物叫做类,其拥有相同的属性(其实就是变量)描述,里面封装了相同的方法。
比如,汽车是一个类,它包括价格、品牌等属性。 宝马、奔驰、兰博基尼这些牌子的车他们虽然不同,但是都可以叫做汽车。
什么是对象?
一个类的实例就是一个对象。
比如我那 82 年的兰博基尼就是一个对象(属于汽车这个类的对象)
python使用对象模型来存储数据。构造任何类型的值都是一个对象。
所有Python对象都拥有三个特性:身份、类型、值
【身份】:每个对象都有一个唯一的身份标识,任何对象的身份可以使用内建函数id()来得到
【类型】:对象的类型决定了该对象可以保存什么类型的值,可以进行什么样的操作,以及遵循什么样的规则。可以使用 type()函数查看python对象的类型。type()返回的是对象而不是简单的字符串。
【值】:对象表示的数据。
-----------------------
按我们编写 类 的习惯来说, 【属性】 + 【方法】 == 【类】
1.一个简单的对象 :
class People: 类的名字, 一般以大写开头,使用驼峰命名法
name = 'x小明' 类的 属性
sex = '男的'
age = 15
def eat(self): 类的方法
print('要吃饭')
def run(self):
print('能跑步')
def sleep(self):
print('要睡觉')
people = People() 得到一个对象 =》 一个 People 类的实例。
print(people.name) 输出对象 的属性
print(people.sex)
print(people.age)
people.eat() 调用对象的方法
people.run()
people.sleep()
输出 :
x小明
男的
15
要吃饭
能跑步
要睡觉
people = People()
print(id(People)) 2473803206168
print(id(people)) 2473834726176
people2 = People()
print(id(people2)) 2473834726568
每一个类 和 对象都有自己独立的存储空间。
2. 面向对象 Object-oriented
python 就是一门纯粹的面向对象编程的语言, 面向对象编程有以下特征 :
a、 封装 (对象封装了属性和方法把他们组合成为一个独立性很强的模块, 这样可以使得我们的信息更加安全)
比如我们的 list 列表就是一个对象, 他的 append 等方法别人不能调用, 这个就是封装。
b、 继承 (继承是子类自动共享父类之间的数据和方法 的机制)
比如我们继承了父母的相貌特征等。
如下 : MyList 继承了 list , 所以 MyList 的实例 my_list 就可以调用父类 list 的 append 方法
class MyList(list):
pass
my_list = MyList()
print(my_list) []
my_list.append(5)
print(my_list) [5]
c、 多态 (【不同对象】对【同一方法】【响应不同的行动】 ) 【感觉这个说法不对。。。。】
比如 移动 这个方法, 老虎会快速移动, 而乌龟会缓慢移动。
如下 : 我定义了两个类, 他们都有一个 fun 方法, 我不同的类的实例调用 fun 方法, 会输出不同的结果, 这就是多态。
class A:
def fun(self):
print('我是 A')
class B:
def fun(self):
print('我是 B')
a = A()
b = B()
a.fun() 我是 A
b.fun() 我是 B
3. self 是什么?
python 的 self 就相当于 C++ 中的 this 指针。
self 表示的是 类的实例本身。
如果把 设计房子的 图纸 比作类 , 那么 通过这个 图纸 建造的房子就是 实例(对象), 而 self 就相当于房子的门牌号 。
一个 self 对应一个实例。
由同一个类可以生成无数个对象, 这些对象长得都很相似, 因为他们都是来源与同一个类的属性和方法。
当一个对象的方法被调用的时候, 会将自身作为第一个参数传给 self 。 接收到 self 的时候, python 就知道你是哪一个实例(对象)
在调用方法了。
class People:
def set_name(self, name):
self.name = name
def say_name(self):
print('my name is ', self.name)
people1 = People()
people1.set_name('xiaoming')
people1.say_name() 在调用类的 say_name() 方法时, 将 people1 这个对象本身传递给 self 了,
然后在 say_name() 方法里面访问了 people1 这个对象的 name 属性。
people2 = People()
people2.set_name('xiaohong')
people2.say_name()
4. python 的魔法方法
什么是python的魔法方法 ?
魔法方法就如同它的名字一样神奇,总能在你需要的时候为你提供某种方法来让你的想法实现。
【魔法方法】是【指】【Python内部【已经包含的】】,【被双下划线所包围】的【方法】,
【这些方法】【在进行】【【特定的】操作】时【会【自动】被调用】,
它们是Python面向对象下智慧的结晶。初学者掌握Python的魔法方法也就变得尤为重要了。
为什么要使用Python魔法方法?
使用Python的魔法方法可以使Python的自由度变得更高,当不需要重写时魔法方法也可以在规定的
默认情况下生效,在需要重写时也可以让使用者根据自己的需求来重写部分方法来达到自己的期待。
而且众所周知Python是支持面向对象的语言Python的基本魔法方法就使得Python在面对对象方面做得更好。
-------------------------------------------------------------------------------------------------------
python 由以下魔法方法 :
魔法方法名 说明
基础魔法方法(较为常用)
__new__(cls[, ...]) 1.实例化对象时第一个被调用的方法
2.其参数直接传递给__init__方法处理
3.我们一般不会重写该方法
__init__(self[, ...]) 构造方法,初始化类的时候被调用
__del__(self) 析构方法,当实例化对象被彻底销毁时被调用(实例化对象的所有指针都被销毁时被调用)
__call__(self[, args...]) 允许一个类的实例像函数一样被调用:x(a, b) 调用 x.__call__(a, b)
__len__(self) 定义当被 len() 调用时的行为
__repr__(self) 定义当被 repr() 调用时的行为
__str__(self) 定义当被 str() 调用时的行为
__bytes__(self) 定义当被 bytes() 调用时的行为
__hash__(self) 定义当被 hash() 调用时的行为
__bool__(self) 定义当被 bool() 调用时的行为,应该返回 True 或 False
__format__(self, format_spec) 定义当被 format() 调用时的行为
属性相关的方法
__getattr__(self, name) 定义当用户试图获取一个不存在的属性时的行为
__getattribute__(self, name) 定义当该类的属性被访问时的行为
__setattr__(self, name, value) 定义当一个属性被设置时的行为
__delattr__(self, name) 定义当一个属性被删除时的行为
__dir__(self) 定义当 dir() 被调用时的行为
__get__(self, instance, owner) 定义当描述符的值被取得时的行为
__set__(self, instance, value) 定义当描述符的值被改变时的行为
__delete__(self, instance) 定义当描述符的值被删除时的行为
比较操作符
__lt__(self, other) 定义小于号的行为:x < y 调用 x.__lt__(y)
__le__(self, other) 定义小于等于号的行为:x <= y 调用 x.__le__(y)
__eq__(self, other) 定义等于号的行为:x == y 调用 x.__eq__(y)
__ne__(self, other) 定义不等号的行为:x != y 调用 x.__ne__(y)
__gt__(self, other) 定义大于号的行为:x > y 调用 x.__gt__(y)
__ge__(self, other) 定义大于等于号的行为:x >= y 调用 x.__ge__(y)
算数运算符
__add__(self, other) 定义加法的行为:+
__sub__(self, other) 定义减法的行为:-
__mul__(self, other) 定义乘法的行为:*
__truediv__(self, other) 定义真除法的行为:/
__floordiv__(self, other) 定义整数除法的行为://
__mod__(self, other) 定义取模算法的行为:%
__divmod__(self, other) 定义当被 divmod() 调用时的行为
__pow__(self, other[, modulo]) 定义当被 power() 调用或 ** 运算时的行为
__lshift__(self, other) 定义按位左移位的行为:<<
__rshift__(self, other) 定义按位右移位的行为:>>
__and__(self, other) 定义按位与操作的行为:&
__xor__(self, other) 定义按位异或操作的行为:^
__or__(self, other) 定义按位或操作的行为:|
反运算(类似于运算方法)
__radd__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rsub__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rmul__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rtruediv__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rfloordiv__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rmod__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rdivmod__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rpow__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rlshift__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rrshift__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rxor__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__ror__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
增量赋值运算
__iadd__(self, other) 定义赋值加法的行为:+=
__isub__(self, other) 定义赋值减法的行为:-=
__imul__(self, other) 定义赋值乘法的行为:*=
__itruediv__(self, other) 定义赋值真除法的行为:/=
__ifloordiv__(self, other) 定义赋值整数除法的行为://=
__imod__(self, other) 定义赋值取模算法的行为:%=
__ipow__(self, other[, modulo]) 定义赋值幂运算的行为:**=
__ilshift__(self, other) 定义赋值按位左移位的行为:<<=
__irshift__(self, other) 定义赋值按位右移位的行为:>>=
__iand__(self, other) 定义赋值按位与操作的行为:&=
__ixor__(self, other) 定义赋值按位异或操作的行为:^=
__ior__(self, other) 定义赋值按位或操作的行为:|=
一元操作符
__neg__(self) 定义正号的行为:+x
__pos__(self) 定义负号的行为:-x
__abs__(self) 定义当被 abs() 调用时的行为
__invert__(self) 定义按位求反的行为:~x
类型转换
__complex__(self) 定义当被 complex() 调用时的行为(需要返回恰当的值)
__int__(self) 定义当被 int() 调用时的行为(需要返回恰当的值)
__float__(self) 定义当被 float() 调用时的行为(需要返回恰当的值)
__round__(self[, n]) 定义当被 round() 调用时的行为(需要返回恰当的值)
__index__(self) 1. 当对象是被应用在切片表达式中时,实现整形强制转换
2. 如果你定义了一个可能在切片时用到的定制的数值型,你应该定义 __index__
3. 如果 __index__ 被定义,则 __int__ 也需要被定义,且返回相同的值
上下文管理(with 语句)
__enter__(self) 1. 定义当使用 with 语句时的初始化行为
2. __enter__ 的返回值被 with 语句的目标或者 as 后的名字绑定
__exit__(self, exc_type, exc_value, traceback) 1. 定义当一个代码块被执行或者终止后上下文管理器应该做什么
2. 一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作
容器类型(一般用于操作容器类)
__len__(self) 定义当被 len() 调用时的行为(一般返回容器类的长度)
__getitem__(self, key) 定义获取容器中指定元素的行为,相当于 self[key]
__setitem__(self, key, value) 定义设置容器中指定元素的行为,相当于 self[key] = value
__delitem__(self, key) 定义删除容器中指定元素的行为,相当于 del self[key]
__iter__(self) 定义当迭代容器中的元素的行为
__reversed__(self) 定义当被 reversed() 调用时的行为
__contains__(self, item) 定义当使用成员测试运算符(in 或 not in)时的行为
-------------------------------------------------------------------------------------------------
5. 最基础的一个魔法方法 : __init__(self)
我们把 __init__ 方法称之为 【构造方法】。
他的魔力体现在 : 只要实例化一个对象的时候, 这个方法就会在对象被创建的时候自动调用。(对比 C++ 里面的 构造函数)
class People:
def __init__(self, name, age=20):
self.name = name
self.age = age
print('我是 __init__ 方法,我正在被调用')
def say_name(self):
print('my name is ', self.name, self.age, 'year old')
people1 = People('xiaoming') 我是 __init__ 方法,我正在被调用
print('_'*10) __________
people1.say_name() my name is xiaoming 20 year old
6. 公有和私有
python 默认对象的属性和方法都是公有的, 可以通过 . 点操作符访问。
可以使用 __属性 这种在变量或者函数前面加上两个下划线的方式来定义私有属性。
这个技术的名字叫做 : name mangling 技术。
class People:
def __init__(self, name, age=20):
self.__name = name
self.age = age
def say_name(self):
print('my name is ', self.__name)
self.__say_age()
def __say_age(self):
print(self.age, 'year old')
people1 = People('xiaoming')
people1.say_name() my name is xiaoming
print(people1.__name) AttributeError: 'People' object has no attribute '__name'
people1.__say_age() AttributeError: 'People' object has no attribute '__say_age'
如上 : 类的实例在调用 __name 属性 和 __say_age() 时, 都抛出了异常。
都是【在类里面调用】他们却能正常运行。 这就说明他们变成私有的了。
如果我非要访问这个 “私有属性” , 有没有办法呢? 由办法的。
通过打印 print(dir(people1)) 得到:
['_People__name', '_People__say_age', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'say_name']
可以看到由两个奇怪的东西 : _People__name 和 _People__say_age, 我并没有定义他们, 我试着打印 _People__name
print(people1._People__name) 输出 : xiaoming
从 python 实现私有的技术的名字 name mangling (名字改编)可以看出,
原来 python 对私有变量的实现方式是对原来的属性的 名字 进行了 改编。
由原来的 __变量名 变成了 _类名__变量名 。
所以, 【python 没有真正的私有变量】。。python 的类是没有权限控制的。
7. 继承
子类 基类、父类或超类
class DerivedClassName(BaseCalssName):
...
如下 :
class A:
def __init__(self):
self.x = 5
def say(self):
print('hello A')
class B(A):
def say(self):
print('hello B')
class C(A):
def __init__(self):
self.y = 6
def say(self):
print('hello C')
a = A()
print(a.x) 5
a.say() hello A
b = B()
print(b.x) 5 子类继承父类, 那么就可以调用父类的属性 和 方法
b.say() hello B 子类重写了父类的方法, 那么调用的就是子类自己的方法
c = C()
c.say() hello C
print(c.x) AttributeError: 'C' object has no attribute 'x'
【【【注意】】】:子类如果自己重写了 __init__ 方法, 那么就会覆盖掉父类的 __init__ 方法, 子类对象在实例化的
时候, 就不会去调用父类的 __init__ 这个魔法函数, 所以父类在 __init__ 里面定义的属性, 子类也不可以继承到。
因此, 在这里子类调用父类的属性时就抛出了异常, 说找不到该属性。
那么子类要继承父类,就不能定义自己的属性了么??
显然不是的, 我们可以通过以下途径在子类重写了父类的 __init__ 方法后依然能够得到父类的属性:
方法一、 在子类的 __init__ 方法里以 父类名.__init__(self) 的方式将子类的实例对象传递给父类的 __init__ 方法来
调用父类的 __init__ 方法。【因为父类的 __init__ 方法被重写了, 所以正常情况下不会被执行,这里主动调用他就行了】
class A:
def __init__(self):
self.x = 5
def say(self):
print('hello A')
class C(A):
def __init__(self):
self.y = 6
A.__init__(self)
def say(self):
print('hello C')
a = A()
print(a.x) 5
c = C()
print(c.x) 5
方法二、 使用 super().__init__() 方法调用父类的 __init__ 方法,这样做比方法一好的地方在于不需要关注父类的名字。
(如果一个父类被很多子类继承,某天心血来潮, 父类的名字改了, 想想就觉得激动)
class A:
def __init__(self):
self.x = 5
def say(self):
print('hello A')
class C(A):
def __init__(self):
self.y = 6
super().__init__()
def say(self):
print('hello C')
a = A()
print(a.x) 5
c = C()
print(c.x) 5
方法三、直接用子类对象的实例作为参数调用一次父类的类方法 __init__。 然后在通过这个子类对象的实例来调用父类的属性
格式为 : 父类名.__init__(子类对象的实例)
class A:
def __init__(self):
self.x = 5
def say(self):
print('hello A')
class C(A):
def __init__(self):
self.y = 6
def say(self):
print('hello C')
a = A()
print(a.x) 5
c = C()
A.__init__(c)
print(c.x) 5
8. 多继承
子类 父类1, 父类2, 父类13
class DerivedClassName(BaseCalssName1, BaseCalssName2, BaseCalssName3):
...
一个子类可以继承多个父类, 继承的顺序为从左到右(子类在使用父类的方法或属性时, 总是从左到右在每个父类里面查找,
只要找到一个, 就立马返回给子类实例, 后面的父类属性就不在考虑范围内了。)
class A:
def __init__(self):
self.x = 5
def say(self):
print('hello A')
class B:
def __init__(self):
self.y = 10
class C(A, B):
def say(self):
print('hello C')
class D(B, A):
pass
c = C()
print(c.x) 5
print(c.y) AttributeError: 'C' object has no attribute 'y'
c.say() hello C
d = D()
print(d.x) AttributeError: 'D' object has no attribute 'x'
print(d.y) 10
如上, 我的类C继承顺序是 (A, B) , 在调用父类 A 的 x 属性时正常, 在调用父类b的y属性是就说没有这个属性。
这是因为我的类 C 在实例化的时候, 先找自己的 __init__ 方法, 没找到就去找 父类A 的 __init__ 方法,
找到了之后就调用了 父类A 的__init__ 方法,然后就不再去 父类B 里面找 __init__ 方法了。
所以我的实例c就没有 y 属性。
实例d的情况也是这样的。
------------------------------------------------------
super 插曲 :
class super(object)
| super() -> same as super(__class__, <first argument>)
| super(type) -> unbound super object
| super(type, obj) -> bound super object; requires isinstance(obj, type)
| super(type, type2) -> bound super object; requires issubclass(type2, type)
-----------------------------------------------------
9. 组合 (把一个类的实例作为另一个类的属性)
class Turtle:
def __init__(self, x):
self.num = x
class Fish:
def __init__(self, x):
self.num = x
class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x)
self.fish = Fish(y)
def print_num(self):
print('乌龟有 {} 只, 鱼有 {} 条。'.format(self.turtle.num, self.fish.num))
pool = Pool(2, 20)
pool.print_num() 水池里乌龟有 2 只, 鱼有 20 条。
10. 类 、 类对象 、 实例对象。
class A: A 在定义的时候,称之为 类
count = 10
print(A.count) A 在定义完了之后, 就可以认为是一个 【类对象】, 可以像使用对象一样使用他
输出 : 10
a = A() 对类A进行实例化,得到一个类A的 【实例对象】 a
print(a.count) 10
b = A()
print(b.count) 10
c = A()
print(c.count) 10
print('_'*50) --------------
c.count += 10 将类A的一个实例的属性 加10
print(c.count) 20 再次调用这个实例的属性, 这个属性已经发生了变化
print(a.count) 10 但是其他实例的该属性还是没有变
print(A.count) 10 类对象的这个实例也没有变
print('-'*50) --------------
A.count = 100 给类对象的这个属性重新赋值
print(A.count) 100 类对象的这个属性已经变化了
print(a.count) 100 实例a的这属性之前没有被重新赋值,也跟着变化了
print(b.count) 100
print(c.count) 20 但是刚才这个属性被重新赋值的实例的属性没有跟着变化
【贴在标签上的标签】
实例和类对象的属性跟普通的变量名一样, 在python里面也只是相当于一个 【标签】 ,在实例生成的时候,
实例的这些属性都默认贴在【类对象】对应的【属性】的【标签上面】(类对象的这个标签贴到别的地方,
这些实例的标签也会被一起贴到别的地方。)。如果我给实例的某些属性重新赋值, 就相当于把这个标签贴到了其他地方。
如果我没有重新赋值, 那么这个标签就一直贴在类对象的属性上面, 我如果修改了类对象的该属性的值,那么这些实例的值也会跟着被修改。
类C
|
类对象C
| | |
实例对象a b c
【 定义类的时候要注意 】
a、不要再一个类里面定义出所有能想到的特性和方法。应该利用继承和组合机制来进行扩展。
b、用不同的词性来命名, 比如类属性用名词, 方法名用动词。
c、一定要注意不类的属性和方法不能用同一个名字, 否则方法会把属性覆盖。
11. 绑定
python 严格要求方法需要有实例才能被调用, 这种限制其实就是python所谓的绑定概念。
【再给 类 定义方法的时候,都要接收传一个 self 参数。
class A:
def __init__(self):
self.x = 5
def say(self):
print('xxx')
def print_bay():
print('bat')
a = A()
print(a.x) 5
a.say() xxx
A.say() TypeError: say() missing 1 required positional argument: 'self'
a.print_bay() TypeError: print_bay() takes 0 positional arguments but 1 was given
使用 实例对象.__dict__ 只能输出实例的属性
print(a.__dict__) {'x': 5}
使用 类对象.__dict__ 可以输出类的属性和类的特殊属性 【可以早写接口的时候查看怎么获取 request 里面的请求方 IP 等信息】
键 表示的是属性的名字。 值表示的是属性的值。
print(A.__dict__)
{'__module__': '__main__', '__init__': <function A.__init__ at 0x00000201C7490F28>,
'say': <function A.say at 0x00000201C7490EA0>, '__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
类对象 和 实例对象 使用的内存空间是不一样的。实例对象有自己单独的存储空间。一个实例对象产生后, 类对象是否被删除跟这个实例对象没有关系。
class A:
x = 5
def say(self):
print('xxx')
a = A()
a.say() xxx
print(a.x) 5
del A
a.say() xxx
print(a.x) 5
b = A() NameError: name 'A' is not defined
12. 一些相关的BIF
a、 issubclass(class, classinfo)
判断class是否是classinfo的子类(classinfo 可以是类对象组成的元组),
如果class是classinfo其中任何一个类的子类。 则返回 True,
如果第一个参数不是对象, 则永远返回 False 。
如果第二个参数不是类或者由类对象组成的元组,会抛出一个 TypeError 异常。
class A:
pass
class B(A):
pass
print(issubclass(B, A)) True
要注意的是 : 一个类被认为是其自身的子类 。
print(issubclass(A, A)) True
b、 hasattr(object, name) attr =表示=》 attribute (属性)
判断一个对象是否拥有指定的属性。 如果有, 则返回 True
class A:
x = 5
a = A()
print(hasattr(a, 'x')) True
print(hasattr(a, 'y')) False
c、 getattr(object, name, default)
判断一个对象是否拥有指定的属性。 如果有则返回这个属性对应的值, 如果没有则返回 设置的默认值。
不设置默认值就会抛异常 : AttributeError: 'A' object has no attribute 'y'
a = A()
print(getattr(a, 'x', '没有这个属性啦')) 5
print(getattr(a, 'y', '没有这个属性啦')) 没有这个属性啦
print(getattr(a, 'y')) AttributeError: 'A' object has no attribute 'y'
d、 setattr(object, name, value)
给实例对象的某个属性赋值(如果这个属性不存在, 那么创建这个属性并赋值)
a = A()
print(a.x)
setattr(a, 'x', '没有x这个属性啦')
print(a.x)
setattr(a, 'y', '没有y这个属性啦')
print(a.y)
e、 delattr(object, name)
删除实例的一个指定的属性, 如果指定的属性不存在, 则抛出异常 : AttributeError: z
delattr(a, 'y')
print(a.y) AttributeError: 'A' object has no attribute 'y'
delattr(a, 'z') AttributeError: z
f、 property(类方法1, 类方法2, ...)
设置一个属性,这个属性的值是 property 这个方法的返回值。
当实例调用 属性 时, 会根据对该属性的行为自动判断该调用哪个方法。
比如我现在有一个类, 我要返回一个宽度, 我就会再类的所有实例里面调用返回宽度的这个函数。
但是有一天我突然想返回长度, 返回宽度的函数和函数名还是要继续保留的, 那怎么办呢?
(如果这个方法是提供给用户或者其他人的, 那你叫别人改肯定没有叫自己改得心应手。。)
我们就只要去所有调用了这个返回宽度的实例里面去逐一修改。 但是如果我设置了 property ,
那么我就只需要在 property 里面将原来的宽度方法替换成长度方法即可。
(一点类似C语言里面的宏定义。。。。。。)
class A:
def __init__(self, size=10):
self.size = size
def get_size(self):
return self.size
def set_size(self, value):
self.size = value
def del_size(self):
del self.size
x = property(get_size, set_size, del_size)
b = A()
print(b.x) 10 自动调用了 A 的 property 中的 get_size 方法
b.x = 20 自动调用了 A 的 property 中的 set_size 方法
print(b.x) 20
del b.x 自动调用了 A 的 property 中的 del_size 方法
print(b.x) AttributeError: 'A' object has no attribute 'size'
13. 构造和析构。
我们在写类定义的时候, 有时候会写 __init__ 方法, 有时候却没有,这是为什么呢??
我们的 __init__ 方法只是完成实例属性的绑定。 我不使用 __init__ 而是采用直接写死或者再实例化之后再添加属性也是可以的。
class A:
x = 5
def say_x(self):
print(self.x)
def say_y(self):
print(self.y)
def say_z(self):
print(self.z)
a = A()
print(a.x) 5
a.say_x() 5
a.y = 20
a.say_x() 20
如上 :
我如果直接调用 a.say_z() 会怎么样呢?
a.say_z() AttributeError: 'A' object has no attribute 'z'
他会说 : A 没有 z 这个属性。 那为什么 我调用 a.say_y() 不报错呢? 因为我在调用 a.say_y() 之前, 已经给实例 a 设置了 y属性。
也就是说, 如果我类的方法再调用类的属性时, 如果这个属性不存在, 那么就会报错。 我即使像设置属性 x 一样给所有被方法用到的属性
设置默认值, 然后在得到一个类的实例之后再来按我们的需求修改初始值, 那也是很蠢的方法(万一我们忘记修改了呢?)
而使用 __init__ 方法, 我们只需要再实例化这个类的时候把初始值传进去, 就可以保证后面的方法正常调用了。
a、 __init__
__init__ 函数默认且必须返回 None 对象, 否则就会报错 TypeError: __init__() should return None, not 'int'
其实我们在实例化一个对象的时候,第一个被调用的并不是 __init__ 函数, 而是 __new__(cls[, ...])
b、 __new__
__new__(cls[, ...]) 方法 : 接收一个 class ,如果 cls 后面有参数, 这些参数会原封不动的传给 __init__ 方法。
__new__ 方法需要一个实例对象作为返回值(这个是必须的,通常是返回 class 这个类的实例对象,
当然也可以返回其他类的实例对象。 返回的就是传给 __init__ 方法的 self 参数)。
这个 __new__ 方法平时是极少去重写他的(面试除外。。。。),直接使用python默认的方案去执行他就行了。
但是在我们继承一个不可变类, 又需要进行修改的时候, 重写这个 __new__ 就非常有必要了。
class CapStr(str):
def __new__(cls, string): 定义类的 __new__ 方法, 接收一个 固定的 cls 和一些类的初始
属性(这些属性到时候要传给 __init__ 方法。
string = string.upper() 对接收到的 初始属性进行修改
return str.__new__(cls, string) 返回一个类的__new__ 方法, 将 接收到的 cls 和修改过的参数一起传进去。
# reture string 为什么我返回string 也能正常运行, 并且得到正常的结果呢?
str1 = CapStr('helli') 实例化一个字符串
print(str1) 得到一个字母全部为大写的字符串。
如上, 我们不可能对一个 str 进行修改。 所以我们应该在 __new__ 的时候(或者说调用 str 的 __new__ 之前)就把他替换了。
然后再用替换过的 string 去调用 str 的__new__方法,将我们主动调用的 __new__ 方法的返回值作为我们自己重写了的 __new__ 方法的返回值。
【注意】 : 如果最后一步不返回 str 这个类的 __new__ 方法, 那么基类的 __new__ 方法就不会被执行。就会报错。
c、 __del__
当一个实例再被删除(引用计数为零被系统自动收回或者被调用 del 方法删除)的时候, 这个魔法方法会被自动调用。
class A(str):
def __new__(cls, string):
print('我是构造函数的爸爸, 我正在生成实例给构造函数。。')
return str.__new__(cls, string)
def __init__(self, string):
self.string = string
print('我是构造函数, 我正在构造实例对象')
return super().__init__()
def __del__(self):
print('我是析构函数,我的实例正在被删除。。。')
a = A('hehe')
del a
print('-'*50)
b = A('haha')
输出如下 :
我是构造函数的爸爸, 我正在生成实例给构造函数。。
我是构造函数, 我正在构造实例对象
我是析构函数,我的实例正在被删除。。。
--------------------------------------------------
我是构造函数的爸爸, 我正在生成实例给构造函数。。
我是构造函数, 我正在构造实例对象
我是析构函数,我的实例正在被删除。。。
14. 算术运算符
a = int(5)
b = int(10)
print(type(a)) <class 'int'>
print(type(b)) <class 'int'>
print(type(int)) <class 'type'>
print(a + b) 15
如上, int 是一个 type (类), 所以 a 和 b 也是一个类。那么 a + b 的本质就是【两个类相加】。
他们相加实际上是调用了 int 类的 __add__ 方法, 如下 ,我重写这个 __add__ 方法,就会发现问题
class int(int):
def __add__(self, other):
print('啊,你居然要我做加法, 不会自己算啊,屎靓仔 。。。。')
return self
a = int(5)
b = int(10)
print(a + b)
print(b + a)
输出如下 : 我们并没有像上面那样得到两者相加的结果。 这也证明了两个数字相加确实是通过 int 的 __add__ 魔法方法完成的。
自然, 我们也可以通过给我们自己的类定义 __add__ 这个魔法方法, 让他可以做加法操作。
啊,你居然要我做加法, 不会自己算啊,屎靓仔 。。。。
5
啊,你居然要我做加法, 不会自己算啊,屎靓仔 。。。。
10
像我这么牛逼的算术运算魔法方法还有23个。。
算数运算符
__add__(self, other) 定义加法的行为:+
__sub__(self, other) 定义减法的行为:-
__mul__(self, other) 定义乘法的行为:*
__truediv__(self, other) 定义真除法的行为:/
__floordiv__(self, other) 定义整数除法的行为://
__mod__(self, other) 定义取模算法的行为:%
__divmod__(self, other) 定义当被 divmod() 调用时的行为
__pow__(self, other[, modulo]) 定义当被 power() 调用或 ** 运算时的行为
__lshift__(self, other) 定义按位左移位的行为:<<
__rshift__(self, other) 定义按位右移位的行为:>>
__and__(self, other) 定义按位与操作的行为:&
__xor__(self, other) 定义按位异或操作的行为:^
__or__(self, other) 定义按位或操作的行为:|
反运算(类似于运算方法)
__radd__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rsub__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rmul__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rtruediv__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rfloordiv__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rmod__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rdivmod__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rpow__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rlshift__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rrshift__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__rxor__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
__ror__(self, other) 当被运算对象(左边的操作对象)不支持该运算时被调用
需要注意的是, 当我们重写这些方法的时候,比如下面的加法, 我传入的是 self 和 other ,然后 返回 self + other 的话,
其实 self 和 other 都是实例, 就又会重写调用 __add__ 方法。 为了避免陷入这种死循环, 我们应该返回 int(self) + int(other)
class MyInt(int):
def __add__(self, other):
return self + other 死循环
return int(self) + int(other) 正常返回
a = MyInt(5)
b = MyInt(10)
print(a + b)
14. 一个简单的定时器 :
import time
class MyTimer:
def __init__(self):
self.unit = ['年', '月', '天', '小时', '分钟', '秒']
self.lasted = []
self.begin = 0
self.end = 0
self.prompt = "未开始计时"
def __str__(self):
return self.prompt
__repr__ = __str__
def __add__(self, other):
prompt = "总共运行了"
result = []
for index in range(6):
result.append(self.lasted[index] + other.lasted[index])
if result[index]:
prompt += (str(result[index]) + self.unit[index])
return prompt
def start(self):
self.begin = time.localtime()
self.prompt = "提示:请先调用 stop 停止计时"
print('开始计时。。。')
def stop(self):
if not self.begin:
print('请先调用 start 方法开始计时')
else:
self.end = time.localtime()
self._cals_()
print('计时结束')
def _cals_(self):
self.lasted = []
self.prompt = "总共运行了"
for index in range(6):
self.lasted.append(self.end[index] - self.begin[index])
if self.lasted[index]:
self.prompt += (str(self.lasted[index]) + self.unit[index])
self.begin = 0
self.end = 0
my_time = MyTimer()
my_time.start()
time.sleep(3)
my_time.stop()
print(my_time.prompt)
t2 = MyTimer()
t2.start()
time.sleep(1)
t2.stop()
print(t2.prompt)
print(my_time + t2)
输出 :
开始计时。。。
计时结束
总共运行了3秒
开始计时。。。
计时结束
总共运行了1秒
总共运行了4秒
15. 属性的访问
属性相关的方法
__getattr__(self, name) 定义当用户试图获取一个不存在的属性时的行为
__getattribute__(self, name) 定义当该类的属性被访问时的行为
__setattr__(self, name, value) 定义当一个属性被设置时的行为
__delattr__(self, name) 定义当一个属性被删除时的行为
__dir__(self) 定义当 dir() 被调用时的行为
案例一、
class A:
def __getattr__(self, item):
print('没有这个属性哦, __getattr__ ')
def __getattribute__(self, name):
print('访问我干嘛啊', name)
return super().__getattribute__(name)
def __setattr__(self, key, value):
print('你又在设置别的属性。。。')
return super().__setattr__(key, value)
def __delattr__(self, item):
print('删除我干嘛??')
super().__delattr__(item)
a = A()
a.x
访问我干嘛啊 x
没有这个属性哦, __getattr__
a.y = 10
你又在设置别的属性。。。
print(a.y)
访问我干嘛啊 y
10
del a.
删除我干嘛??
案例二、
class Rectangle:
def __init__(self, width=0, height=0):
self.width = width
self.height = height
def __setattr__(self, key, value):
if key == "square":
self.width = value
self.height = value
else:
self.__dict__[key] = value
def __getattribute__(self, item):
print('调用我干嘛啦,屎靓仔。。', item)
return super().__getattribute__(item)
def get_area(self):
return self.width * self.height
rectangle = Rectangle()
print("-"*50)
rectangle.square = 50
print("-"*50)
print(rectangle.get_area())
print("-"*50)
rectangle2 = Rectangle()
print("-"*50)
rectangle2.name = "矩形"
print("-"*50)
print(rectangle2.name)
输出 :
调用我干嘛啦,屎靓仔。。 __dict__
调用我干嘛啦,屎靓仔。。 __dict__
--------------------------------------------------
调用我干嘛啦,屎靓仔。。 __dict__
调用我干嘛啦,屎靓仔。。 __dict__
--------------------------------------------------
调用我干嘛啦,屎靓仔。。 get_area
调用我干嘛啦,屎靓仔。。 width
调用我干嘛啦,屎靓仔。。 height
2500
--------------------------------------------------
调用我干嘛啦,屎靓仔。。 __dict__
调用我干嘛啦,屎靓仔。。 __dict__
--------------------------------------------------
调用我干嘛啦,屎靓仔。。 __dict__
--------------------------------------------------
调用我干嘛啦,屎靓仔。。 name
矩形