面试之 Python 基础之面向对象
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
面向对象三大特性
面对对象是一种编程思想以类的眼光来来看待事物的一种方式。
-
封装将共同的属性和方法封装到同一个类下面。
- 第一层面创建类和对象会分别创建二者的名称空间我们只能用
类名.
或者obj.
的方式去访问里面的名字这本身就是一种封装。 - 第二层面类中把某些属性和方法隐藏起来(或者说定义成私有的)只在类的内部使用、外部无法访问或者留下少量接口函数供外部访问。
- 第一层面创建类和对象会分别创建二者的名称空间我们只能用
-
继承将多个类的共同属性和方法封装到一个父类下面然后在用这些类来继承这个类的属性和方法。
-
多态Python天生是支持多态的。指的是基类的同一个方法在不同的派生类中有着不同的功能。
Python 面向对象中的继承有什么特点
继承概念的实现方式主要有2类实现继承、接口继承。
- 实现继承是指使用基类的属性和方法而无需额外编码的能力。
- 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构爹类方法)。
Python 经典类和新式类
python 有两种类经典类和新式类。
-
python3都是新式类默认继承 object。
class Animal(object): 等于 class Animal:
-
python2经典类和新式类并存。
class Animal
经典类
class Animal(object)
新式类
继承分为单继承和多继承Python 是支持多继承的。
如果没有指定基类python3的类会默认继承object类object是所有python类的基类它提供了一些常见方法如__str__
的实现。
对象可以调用自己本类和父类的所有方法和属性先调用自己的自己没有才调父类的。谁对象调用方法方法中的self就指向谁。
class Foo:
def __init__(self):
self.func()
def func(self):
print('Foo.func')
class Son(Foo):
def func(self):
print('Son.func')
s = Son() # Son.func
# ========================================================
class A:
def get(self):
self.say()
def say(self):
print('AAAAA')
class B(A):
def say(self):
print('BBBBB')
b = B()
b.get() # 输出结果为BBBBB
面向对象深度优先和广度优先是什么
Python的类可以继承多个类Python的类如果继承了多个类那么其寻找方法的方式有两种
- 当类是经典类时多继承情况下会按照深度优先方式查找。
- 当类是新式类时多继承情况下会按照广度优先方式查找。
简单点说就是经典类是纵向查找新式类是横向查找。
什么是面向对象的 mro
mro 就是方法解析顺序。
- 在没有多重继承的情况下对象执行一个方法如果对象没有对应的方法那么向上父类搜索的顺序是非常清晰的。如果向上追溯到
object
类所有类的父类都没有找到对应的方法那么将会引发AttributeError
异常。 - 有多重继承尤其是出现菱形继承钻石继承的时候向上追溯到底应该找到那个方法就得依赖MRO。
- Python3 中的类以及 Python2 中的新式类使用C3算法来确定MRO它是一种类似于广度优先搜索的方法。
- Python2 中的旧式类经典类使用深度优先搜索来确定MRO。
可以使用类的mro()
方法或__mro__
属性来获得类的MRO列表。
阅读下面的代码说出运行结果。
class A:
def who(self):
print('A', end='')
class B(A):
def who(self):
super(B, self).who()
print('B', end='')
class C(A):
def who(self):
super(C, self).who()
print('C', end='')
class D(B, C):
def who(self):
super(D, self).who()
print('D', end='')
item = D()
item.who() # ACBD
上面B
代码中的super(B, self).who()
表示以B类为起点向上搜索self
D类对象的who
方法所以会找到C
类中的who
方法因为D
类对象的MRO列表是D --> B --> C --> A --> object
。
面向对象中super的作用
在使用super
函数时可以通过super(类型, 对象)
来指定对哪个对象以哪个类为起点向上搜索父类方法。
# 用于子类继承基类的方法
class FooParent(object):
def __init__(self):
self.parent = 'I\'m the parent.'
print('Parent')
print('1111')
def bar(self, message):
print("%s from Parent" % message)
class FooChild(FooParent):
def __init__(self):
# super(FooChild,self) 首先找到 FooChild 的父类就是类 FooParent然后把类FooChild的对象转换为类 FooParent 的对象
super(FooChild, self).__init__()
print('Child')
# def bar(self, message):
# # super(FooChild, self).bar(message)
# print('Child bar fuction')
# print(self.parent)
if __name__ == '__main__':
fooChild = FooChild()
fooChild.bar('HelloWorld')
如何判断是函数还是方法
-
看他的调用者是谁
- 如果是类就需要传入一个参数self的值这时他就是一个函数。
- 如果调用者是对象就不需要给self传入参数值这时他就是一个方法。
-
使用
isinstance
方法判断。
from types import FunctionType, MethodType
class Foo(object):
def __init__(self):
self.name = 'lcg'
def func(self):
print(self.name)
obj = Foo()
print(obj.func) # <bound method Foo.func of <__main__.Foo object at 0x000001ABC0F15F98>>
print(Foo.func) # <function Foo.func at 0x000001ABC1F45BF8>
print(isinstance(obj.func, FunctionType)) # False
print(isinstance(obj.func, MethodType)) # True
print(isinstance(Foo.func, FunctionType)) # True
print(isinstance(Foo.func, MethodType)) # False
静态方法staticmethod和类方法classmethod区别和应用场景 ★★★★★
-
类方法
- 类对象所拥有的方法用修饰器
@classmethod
来标识其为类方法对于类方法第一个参数必须是类对象一般以cls作为第一个参数当然可以用其他名称的变量作为其第一个参数但是大部分人都习惯以’cls’作为第一个参数的名字就最好用’cls’不需要实例化就可以使用。 - 应用场景当一个方法中只涉及到静态属性的时候可以使用类方法(类方法用来修改类属性)。
- https://blog.51cto.com/u_15688254/5391548
- 类对象所拥有的方法用修饰器
-
静态方法
- 通过修饰器
@staticmethod
来进行修饰静态方法不需要多定义参数可以通过对象和类来访问是类中的一个独立的普通函数或者说方法类或者实例化的对象都可以直接使用它。 - 应用场景静态方法主要用于获取一些固定的值如获取时间、获取一些配置文件但是不会对其进行频繁的更改调用时直接
类.静态方法名
就好了。就是整个项目中就可以直接调用静态方法不需要实例化本身用类就可以调用。
- 通过修饰器
class Num:
# 普通方法能用Num调用而不能用实例化对象调用
def one():
print('1')
# 实例方法能用实例化对象调用而不能用Num调用
def two(self):
print('2')
# 静态方法能用Num和实例化对象调用
@staticmethod
def three():
print('3')
# 类方法第一个参数cls长什么样不重要都是指Num类本身调用时将Num类作为对象隐式地传入方法
@classmethod
def go(cls):
cls.three()
Num.one() # 1
#Num.two() # TypeError: two() missing 1 required positional argument: 'self'
Num.three() # 3
Num.go() # 3
i=Num()
#i.one() # TypeError: one() takes 0 positional arguments but 1 was given
i.two() # 2
i.three() # 3
i.go() # 3
metaclass 作用以及应用场景
metaclass 用来指定类是由谁创建的。如下面创建 Foo 类的示例时调用 MyType()()
类的 metaclass 默认是 type。我们也可以指定类的 metaclass 值。在 Python3 中
class MyType(type):
def __call__(self, *args, **kwargs):
return 'MyType'
class Foo(object, metaclass=MyType):
def __init__(self):
return 'init'
def __new__(cls, *args, **kwargs):
return cls.__init__(cls)
def __call__(self, *args, **kwargs):
return 'call'
obj = Foo()
print(obj) # MyType
用尽量多的方法实现单例模式
单例模式是一种常用的软件设计模式。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问从而方便对实例个数的控制并节约系统资源。
如果希望在系统中某个类的对象只能存在一个单例模式是最好的解决方案。
# 1、使用__new__方法
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls) # 其实就是object
cls._instance = orig.__new__(cls, *args, **kwargs)
return cls._instance
class MyClass(Singleton):
a = 1
# 2、共享属性
# 创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法.
class Borg(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Borg, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass2(Borg):
a = 1
# 3、装饰器版本
def singleton(cls, *args, **kw):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return getinstance
@singleton
class MyClass:
...
# 4、import方法作为python的模块是天然的单例模式
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
# to use
# from mysingleton import my_singleton
my_singleton.foo()
单例模式应用场景。通常一个对象的状态是被其他对象共享的就可以将其设计为单例例如项目中使用的数据库连接池对象和配置对象通常都是单例这样才能保证所有地方获取到的数据库连接和配置信息是完全一致的而且由于对象只有唯一的实例因此从根本上避免了重复创建对象造成的时间和空间上的开销也避免了对资源的多重占用。再举个例子项目中的日志操作通常也会使用单例模式这是因为共享的日志文件一直处于打开状态只能有一个实例去操作它否则在写入日志的时候会产生混乱。
property
property()
函数的作用是在新式类中返回属性值。可以对应于某个方法,希望能够像调用属性一样来调用方法此时可以将一个方法加上property。
定义property属性共有两种方式分别是【装饰器】和【类属性】而【装饰器】方式针对经典类和新式类又有所不同。下面分别展示
# 类属性方式
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
# 如果 c 是 C 的实例化, c.x 将触发 getter,c.x = value 将触发 setter del c.x 触发 deleter。
c = C()
c.x = 1
print(c.x) # 1
# 属性方式
# property 属性的定义和调用要注意定义时在实例方法的基础上添加 @property 装饰器仅有一个self参数调用时无需括号。
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
Python 中为什么没有函数重载
C++、Java、C#等诸多编程语言都支持函数重载所谓函数重载指的是在同一个作用域中有多个同名函数它们拥有不同的参数列表参数个数不同或参数类型不同或二者皆不同可以相互区分。重载也是一种多态性因为通常是在编译时通过参数的个数和类型来确定到底调用哪个重载函数所以也被称为编译时多态性或者叫前绑定。
这个问题的潜台词其实是问面试者是否有其他编程语言的经验是否理解Python是动态类型语言是否知道Python中函数的可变参数、关键字参数这些概念。
首先Python是解释型语言函数重载现象通常出现在编译型语言中。其次Python是动态类型语言函数的参数没有类型约束也就无法根据参数类型来区分重载。再者Python中函数的参数可以有默认值可以使用可变参数和关键字参数因此即便没有函数重载也要可以让一个函数根据调用者传入的参数产生不同的行为。
魔法方法魔法方法|双下划线方法 ★★★★★
列举面向对象中带双下划线的特殊方法如__new__
、__init__
__iter__用于迭代器之所以列表、字典、元组可以进行for循环是因为类型内部定义了 __iter__。
__next__: 用于迭代器。
__doc__表示类的描述信息。
__new__生成实例
__init__生成实例的属性构造方法通过类创建对象时自动触发执行。
__del__析构方法当对象在内存中被释放时自动触发执行。如当 del obj 或者应用程序运行完毕时执行该方法里边的内容。
__call__实例对象加( )会执行def __call__:... 方法里边的内容。
__enter__和__exit__出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量with中代码块执行完毕时执行__exit__里边的内容。
__module__表示当前操作的对象在那个模块 obj.__module__
__class__ 表示当前操作的对象的类是什么 obj.__class____doc__类的描述信息该描述信息无法被继承
__str__改变对象的字符串显示 print函数 --->obj.__str__()如果一个类中定义了__str__方法那么在打印对象时默认输出该方法的返回值。
__repr__改变对象的字符串显示交互式解释器 --->obj.__repr__()
__format__自定制格式化字符串__slots__:一个类变量 用来限制实例可以添加的属性的数量和类型
__dict__类或对象中的所有成员。
__setitem__,__getitem,__delitem__:用于索引操作如字典。以上分别表示获取、设置、删除数据。
class Foo:
def __init__(self,name):
self.name=name
def __getitem__(self, item):
print(self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key]=value
def __delitem__(self, key):
print('del obj[key]时,我执行')
self.__dict__.pop(key)
def __delattr__(self, item):
print('del obj.key时,我执行')
self.__dict__.pop(item)
f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)
__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发
__init__()
和 __new__()
方法有什么区别
Python 中调用构造器创建对象属于两阶段构造过程
-
首先执行
__new__(cls, *args, **kwargs)
方法获得对象所需的内存空间第一个参数cls是当前正在实例化的类。如果要得到当前类的实例应当在当前类中的
new()
方法语句中调用当前类的父类的new()
方法。如果当前类是直接继承自object那当前类的new()方法返回的对象应该为def __new__(cls, *args, **kwargs): ... return object.__new__(cls)
- 如果新式类中没有重写new()方法即在定义新式类时没有重新定义new()时Python默认是调用该类的直接父类的new()方法来构造该类的实例如果该类的父类也没有重写new()那么将一直按此规矩追溯至object的new()方法因为object是所有新式类的基类。
- 如果新式类中重写了new()方法那么你可以自由选择任意一个的其他的新式类必定要是新式类只有新式类必定都有new()因为所有新式类都是object的后代而经典类则没有new() 方法的new()方法来制造实例包括这个新式类的所有前代类和后代类只要它们不会造成递归死循环。
-
再通过
__init__()
执行对内存空间数据的填充对象属性的初始化。
__new__
方法的返回值是创建好的Python对象的引用而__init__
方法的第一个参数就是这个对象的引用所以在__init__
中可以完成对对象的初始化操作。
注意__new__
是类方法它的第一个参数是类__init__
是对象方法它的第一个参数是对象。
运行下面的代码是否会报错如果报错请说明哪里有什么样的错如果不报错请说出代码的执行结果。
class A:
def __init__(self, value):
self.__value = value
@property
def value(self):
return self.__value
obj = A(1)
obj.__value = 2
print(obj.value)
print(obj.__value)
点评这道题有两个考察点一个考察点是对_
和__
开头的对象属性访问权限以及@property
装饰器的了解另外一个考察的点是对动态语言的理解不需要过多的解释。
扩展如果不希望代码运行时动态的给对象添加新属性可以在定义类时使用__slots__
魔法。例如我们可以在上面的A
中添加一行__slots__ = ('__value', )
再次运行上面的代码将会在原来的第10行处产生AttributeError
错误。