Python面向对象面试大全
1、首先简述面向对象的三大特征
封装
封装指的是把一堆数据属性与方法数据放在一个容器中,这个容器就是对象。让对象可以通过 "." 来调用对象中的数据属性与方法属性。
继承:
继承指的是子类可以继承父类的数据属性与方法属性,并可以对其进行修改或使用。
多态:
继承的另外一个好处。在python中的多态指的是让多种类若具备类似的数据属性与方法属性,都统一好命名规范,这样可以提高开发者的代码统一性,使得调用者更方便去理解。
多态的好处就是,当我们需要传入更多的子类,例如新增 Teenagers、Grownups 等时,我们只需要继承 Person 类型就可以了,而print_title()方法既可以直不重写(即使用Person的)。
也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种Person的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:
对扩展开放(Open for extension):允许子类重写方法函数
对修改封闭(Closed for modification):不重写,直接继承父类方法函数
Python 与其他语言不同点在于,当我们定义一个 class 的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样。
Python 有两个判断继承的函数:
isinstance() 用于检查实例类型; (实例,类)
issubclass() 用于检查类继承。 (子类,父类)
输出布尔
子类重写构造函数
若父类构造函数包含很多属性,子类仅需新增1、2个,会有不少冗余的代码,这边,子类可对父类的构造方法进行调用,参考如下:
主要是为了减少代码冗余。也是面向对象的特点。
多重继承
多重继承的概念应该比较好理解,比如现在需要新建一个类 baby 继承 Child , 可继承父类及父类上层类的属性及方法,优先使用层类近的方法,
会用到__mro__ 全称Method Resolution Order 指的是方法解析顺序
会用到C3算法,解决了原本基于深度优先搜索算法不满足本地优先级和单调性的问题。
本地优先级:指的是声明父类的顺序,比如C(A,B),按照声明顺序,优先查找A类,再查找B类
单调性: 如果按照C的顺序的话,他子类也必须满足这个顺序。如果不用C3的话,就凉凉了,都乱了。
列举面向对象带双下划线的魔术方法
描述符,描述符本质就是一个新式类。
__get__
__set__
__delete__
class Str: """描述符Str""" def __get__(self, instance, owner): print('Str调用') def __set__(self, instance, value): print('Str设置...') def __delete__(self, instance): print('Str删除...') class Int: """描述符Int""" def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People:
life = 1 # 类属性 name = Str() # 数据描述符 age = Int() def __init__(self, name, age): # name被Str类代理,age被Int类代理 self.name = name self.age = age # 何地?:定义成另外一个类的类属性 # 何时?:且看下列演示 p1 = People('alex', 18)
Str设置...
Int设置...
描述符的作用就是用来代理另外一个类的属性,
所以到底是发生什么事情呢?
先了解一下__dict__
https://www.cnblogs.com/alvin2010/p/9102344.html
1) 内置的数据类型没有__dict__属性, 字典,列表,字符串,毕竟是数据容器。
2) 每个类有自己的__dict__属性,就算存着继承关系,父类的__dict__ 并不会影响子类的__dict__
3) 对象也有自己的__dict__属性, 存储self.xxx 信息,父子类对象公用__dict__
然后再了解:
数据描述符
至少实现了__get__() 和 __set()__
非数据描述符
没有实现__set__()
1、描述符定义成新式类,被代理的类也应该是新式类。
2、必须把描述符定义成类属性,不能定义为构造函数中。
3、遵守优先级,由高到低
类属性、数据描述符、实例属性、非数据描述符、找不到属性触发__getattr__。
因为python是弱势语言。
所以可以使用描述符来限制功能。
比如Str 必须是浮点,都可以自定义。
class Str: def __init__(self, name, type): self.name = name self.type = type def __get__(self, instance, owner): print('get--->', instance, owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) if not instance(value, self.type): raise TypeError("Expected {}".format(self.type)) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) class People: name = Str(1, str) def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary
那么问题来了, 我们的类有很多属性,仍然采用在定义一堆类属性的方式来实现,实在是太low了。
就出现了 类的装饰器:无参
细品!!!!
class Typed: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, instance, owner): print('get--->', instance, owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) if not isinstance(value, self.expected_type): raise TypeError('Expected %s' % str(self.expected_type)) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>', kwargs) for name, expected_type in kwargs.items(): setattr(cls, name, Typed(name, expected_type)) # 设置类属性 不用上面那些过分冗余 return cls return decorate @typeassert( name=str, age=int, salary=float ) # 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) class People: def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary print(People.__dict__) # name salary age p1 = People('randy', 18, 3333.3)
所以说描述符可以实现大部分python类特征中的底层魔法,包括@classmethod @staticmethod @property 甚至是 __slots__属性
描述符是很多高级库和框架的重要之一,描述符通常是用到装饰器或者元类的大型框架中的组件。
所以我们先来个
自定义@property
这个流程值得思考。
class Lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is None: return self return self.func(instance) # 此时你应该明白,到底是谁在为你做自动传递self的事情(__get__) class Room: def __init__(self, name, width, length): self.name = name self.width = width self.length = length @Lazyproperty # area=Lazyproperty(area) 相当于定义了一个(类属性),即描述符 相当于 name = Str() # area=Lazyproperty(area) def area(self): return self.width * self.length r1 = Room('alex', 1, 1) print(r1.area)
图解
1、初始化Property类
2、初始化Room类
3、装饰器Lazyproperty
4、执行Lazyproperty类的__init__ 、area 传入func里面
5、 Room类创建实例r1,
6、__new__初始化创建,
7、__init__里卖弄添加类属性
8、r1.area的调用
9、触发了Lazyproperty的__get__
10、return self.func(instance) 意思为 Room里面的area self 为Lazyproperty 因为初始化是 self.func = func,所以self.func 相当于 area() , 所以就是为什么r1.area 相当于 r1.area()
11、调用area 输出
Property特性
property用于被装修的方法伪装成一个数据属性,在使用的时候可以不用调用实例方法,直接调用property属性(__get__)
为什么只需要一个参数,因为return self.func(instance)
class Goods: @property def price(self): print('property') @price.setter def price(self, value): print('@price setter') @price.deleter def price(self): print('@price.dellete') obj = Goods() obj.price obj.price = 123 del obj.price # property # @price setter # @price.dellete
介绍一下两种方式:
经典类
class Goods: @property def price(self): return "laowang"
新式类
自定义类方法Classmethod
class ClassMethod: def __init__(self, func): # say_hi 进来了 self.func = func def __get__(self, instance,owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身, def feedback(): print('在这里可以加功能啊...') return self.func(owner, *args, **kwargs) return feedback class People: name = 'randy' @ClassMethod # say_hi=ClassMethod(say_hi) name = Str() # say_hi = ClassMethod(say_hi) def say_hi(cls, msg): print('你好啊,帅哥 %s' % (cls.name,msg)) People.say_hi() # People.say_hi 触发了__get__的方法 # People.say_hi() 运行feddback函数,根据上面来说,直接运行func()
图解析
1、类初始化
2、类初始化
3、装饰器执行
4、__init__执行
5、People.say_hi() 执行__get__
6、feedback()
7、owner 对应 cls , cls.name
自定义@staticmethod
class StaticMethod: def __init__(self, func): self.func = func def __get__(self, instance, owner): def feedback(*args, **kwargs): print('在这里可以加功能!!!!') return self.func(*args, **kwargs) return feedback class People: @StaticMethod def say_hi(x, y, z): print('--------', x,y,z) p1 = People() p1.say_hi(4,5,6)
图解顺序
1、执行StaticMethod(say_hi)
2、执行__init__ (self, func ) say_hi 传入了 func, self 为StaticMethod object
3、执行People.say_hi(1,2,3)
4、因为People.say_hi 触动了 StaticMethod 的__get__
5、如果是People().say_hi(1,2,3), instance 为<__main__ People object at > 如果是People.say_hi(1,2,3), instance 为None
6、明显的一个回调,直接returnfeedback(),
7、因为say_hi(1,2,3) 执行了feedback函数并且参数 传入*args里面 这里可以进行定制,随便加功能。
8、return self.func(*args, **kwargs) 给say_hi
9、直接执行say_hi() x:1 y:2 z;3
10、执行say_hi里面的处理。
__setattr__: 添加/修改属性会触发它的执行
__delattr__: 删除属性的时候会触发
__getattr__: 只有在使用点调用属性且属性不存在的时候才会触发
__getattribute__: 不管是否存在,我都会执行