类和面向对象
- 类:抽象的集合,可以看做模板,类中定义属性和方法
- 对象:基于类的具体实例
类及类属性
class Person:
"""
class doc
"""
classVar = 100
def showMe(self):
return __class__.__name__
print(Person)
print(type(Person))
print(Person.__name__)
print(Person.__doc__)
print(Person.showMe)
- class后接标识符定义类
- 每个class实际上也是一个对象,是type类的对象
- classVar是类变量,所有实例和类共享
- showMe方法的本质是一个对象
实例化
class Person:
"""
class doc
"""
classVar = 100
def showMe(self):
return __class__.__name__
p = Person()
print(p)
-
__main__是文件模块名,Person object表示是Person类的对象,0x到结尾表示对象在内存中的对象地址
-
每一次实例化都是生成不同的对象,及时实例中属性和方法相同
-
实例化的本质是调用类的__init__方法,__init__方法不能有返回值
-
上面Person类没有定义__init__方法,是因为调用object类的__init__方法,python3没有定义继承哪个类,默认继承object
-
初始化对象可以看做两步,首先调用__new__方法(继承object)在内存开辟内存存放实例对象等操作,__init__用来填充实例的数据等操作,两步完成后才算真正的实例化
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
- __init__方法的第一个参数self指的是第一个步骤__new__方法创建出的对象,可以不是self标识符,self.name和self.age是实例属性(实例变量),分别接受形参的name和age
class Person:
age = 3
def __init__(self, name):
self.name = name
tom = Person('Tom')
jerry = Person('Jerry')
print(tom.name, tom.age)
print(jerry.name, tom.age)
print(Person.age)
Person.age = 30
print(Person.age, tom.age, jerry.age)
- 每个实例变量值是自己独有的,类变量共享
特殊属性
| 特殊属性 | 含义 |
|---|---|
| _name_ | 名称或模块名 |
| _class_ | 实例class信息 |
| _dict_ | 类或实例的属性字典 |
| _qualname_ | 简单来说是属性的完整路径 |
class Person:
age = 3
def __init__(self, name):
self.name = name
self.inner = self.__class__.Inner()
class Inner:
var = 100
def test(self):
pass
tom = Person('Tom')
jerry = Person('Jerry')
print(tom.__dict__)
print(tom.__class__)
print(tom.__class__.__name__)
print(__name__)
print(Person.__dict__)
print(Person.Inner.test.__qualname__)
- 实例.__dict__主要输出__init__方法下定义的赋值语句,类.__dict__输出类的信息
- 只用class.__name__输出类名,单独执行print(_name_)输出的是模块名,如果该文件被其他文件引入,模块名就是定义的文件名
- __class__输出实例的类信息,每个类输出的是type类信息
- __qualname__就是对象的完整路径,比如test方法的是Person.Inner.test
属性访问规则
class Person:
age = 3
height = 170
def __init__(self, name, age=18):
self.name = name
self.age = age
tom = Person("Tom") # 实例化、初始化
jerry = Person('Jerry', 20)
Person.age = 30
print(1, Person.age, tom.age, jerry.age) # 输出什么结果
print(2, Person.height, tom.height, jerry.height) # 输出什么结果
jerry.height = 175
print(3, Person.height, tom.height, jerry.height) # 输出什么结果
tom.height += 10
print(4, Person.height, tom.height, jerry.height) # 输出什么结果
Person.height += 15
print(5, Person.height, tom.height, jerry.height) # 输出什么结果
Person.weight = 70
print(6, Person.weight, tom.weight, jerry.weight) # 输出什么结果
print(7, tom.__dict__['height']) # 可以吗
print(8, tom.__dict__['weight']) # 可以吗
- 1输出30 18 20,没啥好说的,Person.age = 30改变类变量的值,tom使用默认18,jerry传入20
- 2输出170 170 170,虽然实例变量没有height变量,但是公用了类变量height=170
- 3输出170 170 175,经过jerry.height = 175动态给jerry动态赋予height属性,就不会用类变量的height属性,相当于在jerry.__dict__添加了height=175
- 4输出170 180 175,可以理解为tom实例拿类变量height+=10了,然后添加到tom.__dict__中
- 5输出185 180 175,对类变量+=15,对实例变量没有影响
- 6输出70 70 70,动态给Person类添加weight属性,实例没有weight属性用类的变量
- 7输出180,tom._dict_['height']等于tom.height
- 8就会报错,这不是面向对象报错,是字典的报错,找不到weight的key,而tom.weight由python内部帮你找到类的变量,所以tom._dict_['weight']这种方式尽量不要用
类方法和静态方法
- 普通方法
class Person:
def normal_function():
print('普通的函数')
def method(self):
print('方法')
# 调用
Person.normal_function()
print(Person().normal_function)
print(Person().normal_function())
print(Person.__dict__)
-
Person().normal_function()会报错,因为这是因为normal_function()并没有把实例传入,Person().normal_function可以当做实例没有找到类的属性normal_function
-
类方法
class Person:
@classmethod
def class_method(cls):
print('类方法')
print("{0.__name__}'s name = {0.__name__}".format(cls))
cls.HEIGHT = 170
# 调用
Person.class_method()
Person().class_method()
print(Person.__dict__)
print(Person.HEIGHT)
print(Person().HEIGHT)
-
@classmethod表示是类方法,类和实例都可以访问,cls表示类,cls.HEIGHT = 170相当于创建类变量
-
静态方法
class Person:
HEIGHT = 180
@staticmethod
def static_method():
print('静态方法')
print(Person.HEIGHT)
# 调用
Person.static_method()
Person().static_method()
print(Person.__dict__)
- @staticmethod表示是静态方法,比类方法少了传参的步骤,类和实例都可以访问
私有变量
- 通过"__"定义私有变量
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age
p = Person("tom", 25)
print(p.__name)
print(p.__age)
-
上面p.__name和p.__age都会报错,实质是因为python将__name实例变量变成_Person__name和_Person__age。p.__dict__可以看到
-
通过"_"定义保护成员
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
p = Person("tom", 25)
print(p._name)
print(p._age)
- 实际上没有保护效果,还是可以访问,只是一个约定
属性装饰器
- 一般会将属性设置成私有变量,然后设置方法进行取值和传值、
class Person:
def __init__(self, name):
self._name = name
def name(self):
return self._name
def set_name(self, value):
self._name = value
- Python提供了property装饰器,简化调用
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def set_name(self, value):
self._name = value
- property装饰器必须在前,setter、deleter装饰器在后
- property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果
继承
- 继承:继承其他类的属性和方法,也可以有自己的属性和方法,减少代码冗余
class Animal:
def __init__(self, name):
self._name = name
def shout(self):
print(f"{self._name} shout")
@property
def name(self):
return self._name
@name.setter
def set_name(self, value):
self._name = value
a = Animal("tom")
a.shout()
class Cat(Animal):
pass
c = Cat("miao")
c.shout()
class Dog(Animal):
pass
d = Dog("wang")
d.shout()
-
Animal类是Cat和Dog类的父类,Cat和Dog类是Animal的子类
-
特殊属性
| 特殊属性和方法 | 含义 |
|---|---|
| _bases_ | 类的父类们 |
| _base_ | __bases__元祖的第一个 |
| _mro_ | 方法的查找顺序 |
| mro() | 同上 |
| _subclasses_() | 类的子类列表 |
class A:
pass
print(A.__base__)
print(A.__bases__)
print(A.mro())
print(A.__mro__)
print(int.__subclasses__())
print(bool.mro())
- 继承中的访问控制
class Animal:
__a = 10
_b = 20
c = 30
def __init__(self):
print("self init")
self.__d = 40
self._e = 50
self.f = 60
self.__a += 1
def showa(self):
print(self.__a)
print(self.__class__.__a)
def __showb(self):
print(self._b)
print(self.__a)
print(self.__class__.__a)
class Cat(Animal):
__a = 100
_b = 200
c = Cat()
c.showa()
c._Animal__showb()
print(c.c)
print(c._Animal__d)
print(c._e, c.f, c._Animal__a)
print(c.__dict__)
print(c.__class__.__dict__.keys())
-
c.showa():c调用父类showa方法,需要注意的是Cat实例化时用的是Animal的__init__方法,self.__a+=1实际上是self._Animal__a+=1,也就是c._dict__会有_Animal__a=11的原因,self._class.__a__会在Cat.__dict__查找,但是没有找到,最终在父类Animal.__dict__找到。方法或实例的调用顺序:实例._dict_->实例类._dict_->父类._dict_
-
有了查找顺序,后面输出就很简单了。总结一下:实例.__dict__可以理解为__init__方法下的赋值,如果子类没有重写覆盖父类的__init__方法,而父类在自己的__init__方法下有self.__a的赋值,那么实例上的赋值是self._父类名__a。类.__dict__可以理解为类中包含的属性(不在__init__下)和方法集合。
-
方法重写
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
# 覆盖了父类方法
def shout(self):
print('miao')
def shout(self):
print('miao2')
a = Animal()
a.shout()
c = Cat()
c.shout()
print(Animal.__dict__)
print(Cat.__dict__)
-
Cat类的shout方法覆盖Animal类的shout方法,然后被自己类的shout方法覆盖,本质是改变Cat.__dict__中shout变量指向的方法地址
-
对父类方法增强
class Animal:
def shout(self):
print('Animal shouts')
class Cat(Animal):
def shout(self):
super(Cat, self).shout()
super().shout()
print('miao2')
a = Animal()
a.shout()
c = Cat()
c.shout()
print(Animal.__dict__)
print(Cat.__dict__)
- 场景:子类想继承父类属性,但也想有自己的属性
class A:
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, b, c):
super(B, self).__init__(b + c)
self.b = b
self.c = c
b = B(2, 3)
print(b.a)
print(b.b)
print(b.c)
-
从上面例子得知子类的__init__方法会覆盖父类的__init__方法,所以要想子类有父类的a属性,需要调用父类的__init__方法
-
多继承
- mixin
class Doc:
def __init__(self, content):
self.content = content
# 抽象方法,需要子类重写该方法,要不然报错
def print(self):
raise NotImplementedError()
class Printable:
def printContent(self):
print(self.content)
class Pdf(Printable,Doc):
pass
p = Pdf("pdf content")
print(Pdf.mro())
p.printContent()
魔术方法
_new_:实例化对象,主要是继承object类的__new__方法,可以理解为分配内存存放对象,__init__方法主要是为对象分配属性。一般情况用不到这个魔术方法,但涉及到设计模式点情况,需要用到。参考:juejin.cn/post/684490…
class SingleObject:
def __new__(cls, *args, **kwargs):
if not hasattr(SingleObject, "_instance"):
SingleObject._instance = object.__new__(cls)
return SingleObject._instance
def __init__(self):
pass
print(SingleObject.__dict__.keys())
a = SingleObject()
print(SingleObject.__dict__.keys())
b = SingleObject()
print(id(a))
print(id(b))
- 可以看到a=SingleObject()后类字典多了一个_instance属性
_str_:str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。如果没 有定义,就去调用 repr 方法返回字符串表达,如果 repr 没有定义,就 直接返回对象的内存地址信息
class Test:
def __str__(self):
# 必须return str,__repr__同理
return "str method called"
print(Test())
print("Test:{}".format(Test()))
print(str(Test()))
print([Test()])
- 当在容器中使用实例对象时,找的是__repr__魔术方法,开发过程中只重写__repr__魔术方法即可
_repr_:和__str__魔术方法类似,return str,开发中重写这个魔术方法就好
class Test:
def __repr__(self):
return "repr method called"
print(Test())
print("Test:{}".format(Test()))
print(str(Test()))
print({Test()})
_bytes_:返回字节对象
def __bytes__(self):
#return "{} is {}".format(self.name, self.age).encode()
import json
return json.dumps(self.__dict__).encode()
_bool_:内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。 没有定义 bool (),就找 len ()返回长度,非0为真。如果 len ()也没有定义,那么所有实例都返回真
class A:pass
print(bool(A()))
print(bool(A))
class B:
def __bool__(self):
return False
print(bool(B))
print(bool(B()))
class C:
def __len__(self):
return 0
print(bool(C()))
print(bool(C))
class D:
def __bool__(self):
return False
def __len__(self):
return 1
print(bool(D))
print(bool(D()))
运算法重载
class A:
def __add__(self, other):
return A(self.data + other.data)
def __init__(self, data):
self.data = data
def __lt__(self, other):
return self.data < other.data
def __ge__(self, other):
return self.data == other.data
def __gt__(self, other):
return self.data > other.data
def __iadd__(self, other):
return A(self.data + other.data)
def __repr__(self):
return f'A:[data={self.data}]'
a = A(2)
b = A(2)
print(id(a))
if a > b:
print("a>b")
elif a == b:
print("a==b")
else:
print("a<b")
print(a + b)
print(id(a))
print(a)
a += b
print(id(a))
print(a)
- 实例默认是独一无二,a实例不等于b实例,需要重写__eq__方法
- __add__返回点值不会修改调用对象,__iadd__会修改原有对象,也就是说a指向点实例发生改变
上下文管理
- 对类实例的增强,当一个对象同时实现了 _enter_ ()和 _exit_ ()方法,它就属于上下文管理的对象
class Point:
def __init__(self):
print("init")
def __enter__(self):
print("enter")
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
with Point() as p:
print("with")
- 从上图中可以看到with Point()时会执行__init__方法
- 在执行with块中代码时,会先执行__enter__方法
- 执行完with块中代码后会执行__exit__方法
- 即使with块中抛出异常还是会执行__exit__方法。前提是__exit__方法return True,关于上下文的用法:www.cnblogs.com/wongbingmin…
- 上文中as p,就像操作文件那样是一个别名,前提是__enter__方法返回self,那么p就是Point()对象的别名,这也说明open函数返回的文件对象的__enter__方法返回self了。
class Point:
def __init__(self):
print("init")
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("exit")
with Point() as p:
print("with")
with p as f:
print(p)
print(f)
print(p == f)
print(p is f)
f = open("timeDemo.py", "r")
print(f.__enter__())
with f as f1:
print(f)
print(f1)
print(f == f1)
print(f is f1)
生成器函数
- 在一个函数定义中,出现了yield语句,此函数就是生成器函数
def foo():
a = 0
while True:
print("yield before")
yield a
print("yield after")
a += 1
f = foo()
print(next(f))
print("~~~~~~")
print(next(f))
print("=====")
print(next(f))
- 从上面代码执行来看,通过next()调用时,当代码执行到yield这行时,返回yield后到值,下一次从yield后到代码执行
- 打印上面f对象,发现是一个迭代器,也就是每一次next()才返回值。参考:Python 的关键字 yield 有哪些用法和用途? www.zhihu.com/question/34…
contextlib.contextmanager
- contextlib.contextmanager它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现 _enter_ 和 _exit_ 方法。函数的要求:必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
import contextlib
@contextlib.contextmanager
def foo(): #
print('enter')
try:
yield 5 # yield 5,yield的值只能有一个,作为__enter__方法的返回值 finally:
print('exit')
except:
print("error")
finally:
print("quit")
with foo() as f:
raise Exception("error")
print(f)
- 实际应用
import contextlib
import datetime
import time
@contextlib.contextmanager
def timeit():
print('enter')
start = datetime.datetime.now()
try:
yield
finally:
print('exit')
delta = (datetime.datetime.now() - start).total_seconds()
print('delta = {}'.format(delta))
def add(x, y):
time.sleep(2)
return x + y
with timeit():
add(4, 5)