欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

python粗谈面向对象(二) 百日筑基(九)

程序员文章站 2022-05-11 10:45:53
super,类方法,双下方法,property,type简单介绍。 ......

浅谈super()

super并不是一个函数,是一个类名,形如super(b, self)事实上调用了super类的初始化函数,产生了一个super对象;python的多继承类是通过mro的方式来保证各个父类的函数被逐一调用,而且保证每个父类函数只调用一次(如果每个类都使用super),并且按照mro序列一次调用。下面是一个小练习:

class a:
    def fun(self):
        print('in a')
class b(a):
    def fun(self):
        super().fun()
        print('in b')
class c(a):
    def fun(self):
        print('in c')
class d(b,c):
    def fun(self):
        super().fun()
        print('in d')
print(d.mro())  # 打印mro序列
obj = d()
obj.fun()

# 打印内容如下
[<class '__main__.d'>, <class '__main__.b'>, <class '__main__.c'>, <class '__main__.a'>, <class 'object'>]
in c
in b
in d

从打印结果我们可以知道super并不是简单的按照调用父类那么简单。它是按照mro序列的排序方式调用的。在d类fun中super指向b类,在b类的fun方法中super指向下一个mro序列也就是c类。所以最终打印顺序是c类的fun b类的fun最后是d类的fun。

在看一个简单的示例:

class a:
    def fun(self):
        print('in a')
class b(a):
    def fun(self):
        super().fun()
        print('in b')
class c(a):
    def fun(self):
        print('in c')
class d(b,c):
    def fun(self):
        super(b,self).fun()  # 跳过b类,使用下一个mro序列
        print('in d')
print(d.mro())  # 打印mro序列
obj = d()
obj.fun()

# 打印内容如下
[<class '__main__.d'>, <class '__main__.b'>, <class '__main__.c'>, <class '__main__.a'>, <class 'object'>]
in c
in d

super(b,self).fun()函数是表示跳过b类。使用下一个mro序列也就是c类。

面向对象之类成员

类的成员分为两种形式:

  • 公有成员:在任何地方都可以访问。
  • 私有成员:只能在类的内部访问。

类属性

类的静态字段(静态属性):

  • 公有静态字段:类可以访问,类内部可以访问,派生类中可以访问。
  • 私有静态字段:仅类内部可以访问。

访问类的公有字段:

class a:
    name = "公有静态字段"
    def func(self):
        print(a.name)
class b(a):
    def show(self):
        print(b.name)
print(a.name)   # 类访问
obj_a = a()
obj_a.func()     # 类内部访问
obj_b = b()
obj_b.show() # 派生类中访问

访问类的私有字段:

class a:
    __name = "私有静态字段"
    def func(self):
        print(a.__name)
class b(a):
    def show(self):
        print(a.__name)
# print(a.__name)     # 不可以在类外访问
obj_a = a()
obj_a.func()     # 可以在类内部访问
# obj_b = b()
# obj_b.show() # 不可以在派生类中访问

对象属性

  • 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
  • 私有普通字段:仅类内部可以访问;

访问对象公有字段:

class a:
    def __init__(self):
        self.foo = "对象公有字段"
    def func(self):
        print(self.foo) # 类内部访问
class b(a):
    def show(self):
        print(self.foo) # 子类中访问父类对象的公有字段
obj = a()
print(obj.foo)    # 通过对象访问
obj.func()  # 类内部访问

obj_b = b()
obj_b.show()  # 子类中访问父类对象的公有字段

# 打印内容如下
对象公有字段
对象公有字段
对象公有字段

访问对象私有属性:

class a:
    def __init__(self):
        self.__foo = "对象私有字段"
    def func(self):
        print(self.__foo) # 类内部访问
obj = a()
obj.func()  # 类内部访问

#打印内容如下
对象私有字段

类方法

  • 公有方法:对象可以访问,类内部可以访问,派生类中可以访问。
  • 私有方法:仅类内部可以访问。

访问公有方法:

class a:
    def fun(self):
        print("公有方法 a  fun")
class b(a):
    def show(self):
        print("公有方法 b show")
    def func(self):
        self.show()
obj = b()
obj.show()  # 通过对象访问
obj.func()  # 类内部访问
obj.fun()  # 子类中访问父类方法

# 打印内容如下
公有方法 b show
公有方法 b show
公有方法 a  fun

在类中访问私有方法:

class a:
    def __show(self):
        print("私有方法 a show")
    def func(self):
        self.__show()
obj = a()
obj.func()  # 类内部访问

# 打印内容如下
私有方法 a show

总结:

对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用。

如果非要访问私有成员的话,我们可以通过类名.__dict__查看类的所有属性和方法。如下图所示:

python粗谈面向对象(二) 百日筑基(九)

由上图我们可以看出私有方法只不过是python在前面加了_类名__方法的方式进行了简单的加密。所以虽然是私有方法或者私有属性,我们还是可以用对象或者类在类的外部进行调用。但既然我们把它定义成私有属性,就表示我们只想在类的内部调用而不打算在类的外部调用。所以没有必要定义了私有属性又在外部调用。

关于类的方法从类型上分为以下几种:

实例方法:从名字上看就可以知道主要是给实例对象调用的,第一个参数必须是实例对象,这也应该没什么异议毕竟是给实例使用的,参数名一般约定俗成为“self”,如果你看它不顺眼也可以改成自己喜欢的。通过它来传递实例的属性和方法。主要由实例对象调用,虽然类也可以调用,但一般不建议。

类方法: 从名字上也可以看出它主要是给类使用的,使用装饰器@classmethod。第一个参数必须是当前类,该参数名一般约定为“cls”,一样如果你不习惯cls可以改成自己喜欢的,通过它来传递类的属性和方法,主要由类调用,虽然实例对象也可以调用,但一般不建议。

静态方法:这是一个特殊的方法,它除了在类空间内创建了一个函数外,和类没有任何关系,使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数这些俗套的东东,如果想要在静态方法中调用类的成员或者对象的成员需要将类或者对象传递给静态方法。实例对象和类对象都可以调用。

双下方法:这也是个特殊方法,他是解释器提供的由双下划线加方法名加双下划线 __方法名__的具有特殊意义的方法,双下方法主要是python源码程序员使用的,我们在开发中尽量不要使用双下方法,但是深入研究双下方法,有益于我们阅读源码。

实例方法:

class a:
    name = "xiao ming"
    def fun(self):     # 实例方法
        self.__fun2()  # 调用私有实例方法
    def __fun2(self):  # 私有实例方法
        print("我是私有的实例方法")
    def fun3(self):
        print("我是公有的实例方法")
obj = a()
obj.fun()
a.fun3("我必须传个参数")
a.fun("我必须传个参数")

# 打印内容如下
我是私有的实例方法
我是公有的实例方法
attributeerror: 'str' object has no attribute '_a__fun2'

类虽然可以调用实例方法,但是必须要传递个参数给实例方法,如果实例方法中在调用其它的实例方法,无论调用的是公有实例方法还是私有实例方法都会出现问题,因为这些方法需要参数,通过类的方式无法传递参数所以会报错。下面是图片应该看的更清晰些,所以说实例方法就是给实例用的,类就不要增加存在感了。如果有特殊情况需要类参与那就使用类方法。不要和实例方法混在一起。

实例对象在调用方法时就不需要传递参数,这是因为python为我们隐式的把实例对象空间地址传给了实例方法,所以实例对象在调用实例方法时不会报错,因为python已经为我们将参数隐式的传递给了实例方法。只是我们没看到,所以说眼睛看到的未必就是真实的。

python粗谈面向对象(二) 百日筑基(九)

类方法:

class a:
    name = "xiao ming"
    @classmethod
    def fun(cls):     # 类方法
        cls.__fun2()  # 调用私有类方法
        print(cls)    # 打印cls内存地址
    @classmethod
    def __fun2(cls):  # 类私有方法
        print("我是类的私有方法")
    @classmethod
    def fun3(cls):
        print("我是公有的实例方法")
obj = a()
obj.fun()  # 对象调用类方法
a.fun3()   # 类调用类方法
a.fun()    # 类调用类方法
print(a)

# 打印内容如下
我是类的私有方法
<class '__main__.a'>  # 通过实例对象调用的类方法
我是公有的实例方法
我是类的私有方法
<class '__main__.a'>
<class '__main__.a'>

从打印结果我们可以知道,类的实例对象也可以正常调用类方法,并且python为我们将类a隐式的传递给了类方法,而不是将实例对象空间传递给了类方法。所以我们不能在类方法中使用对象的属性和方法,除非我们将实例对象空间传递给类方法,这就需要在定义类方法时,给类方法在加个形参,然后使用实例对象显式的将对象空间传递给类方法。所以说既然实例对象有自己的实例方法就不要和类方法凑热闹了,这就是不建议用实例对象调用类方法的原因。

小示例:统计创建实例对象的个数。

class a:
    count = 0
    def __init__(self):
        a.obj_count()  # 统计创建了多少个实例对象
    @classmethod
    def obj_count(cls):
        cls.count += 1
obj_1 = a()
obj_2 = a()
obj_3 = a()
print(a.count)

# 打印内容如下
3

静态方法:

class a:
   @staticmethod
   def fun():
       print("我是静态函数")
obj = a()
obj.fun()
a.fun()

关于类的静态方法没什么好说的,就是在类空间内创建了一个与类不发生任何关系的函数,也不能说一点关系没有,毕竟是在类空间创建的。类和实例化对象都可以正常调用。

双下方法:

我们知道在python中一切皆是对象,而我们又知道对象是类实例化出来的,所以python中的对象必然都是通过某个具体类实例化出来的。例如:

python粗谈面向对象(二) 百日筑基(九)

我们可以知道str_1是str类的实例化对象,所以str_1可以使用str类中的所有方法,而str类继承object类所以str_1也可以使用object类中的方法。如果我们想要获取字符串的长度可以直接使用len(字符串),这是为什么呢?,那么len又属于哪个类的方法呢?我们做个简单的示例:

class a:
    def __len__(self):
        print("计算长度")
obj = a()
len(obj)

下面是图片,应该更好理解:

python粗谈面向对象(二) 百日筑基(九)

好的既然不能解释,那么我们就让它能解释。

class a:
    def __len__(self):
        return 4
obj = a()
print(len(obj))

# 打印内容如下
4

我们可以发现这回没有报错了,那如果我在类a中在定义一个用于统计字符串长度的函数__len__,是不是类对象在统计属性长度时就可以调用本类中len功能了呢?答案是理论上可以,你可以单独创建个数据类型然后不继承object类,继承你写的类这样就可以调用你写的len了。

关于双下划线方法我们要知道几个主要的如下:

__new__:在实例化对象时为对象开辟内存空间。

class a:
    def __init__(self):
        self.x = 1
        print('in __init__')
obj = a()
print(obj.x)

 # 打印内容如下
in __init__
1

下面演示一个没开辟空间的实例化对象:

class a:
    def __init__(self):
        self.x = 1  # 为实例对象封装属性
        print('in __init__')
    def __new__(cls, *args, **kwargs):
        print("in __new__")
obj = a()
obj.name = "hello world"  # 为实例对象封装属性
print(obj.x)

 

打印如下图所示:

python粗谈面向对象(二) 百日筑基(九)

触发了__new__后并没有执行__init__函数,所以也就没有给obj对象封装x这个属性,当调用obj.x这个属性时,找不到也是自然,但是给对象封装name属性时也失败,究其原因就是实例对象在内存中没有空间,所以无法为其封装属性。

下面我们在类a中的__new__中调用object的__new__为对象开辟内存。

class a:
    def __init__(self):
        self.x = 1  # 为实例对象封装属性
        print('in __init__')
    def __new__(cls, *args, **kwargs):
        print("in __new__")
        return object.__new__(cls)  # 调用object的__new__为对象开辟空间
obj = a()
obj.name = "hello world"  # 为实例对象封装属性
print(obj.x)

 

打印内容如下:

python粗谈面向对象(二) 百日筑基(九)

下面我们来演示个单实例的代码:什么是单实例?单实例就是类创建n个对象,但是这n个对象都使用一块内存空间。

class a:
    instance_flag = none  # 如果有创建一个实例对象就将空间赋值给instance_flag
    def __new__(cls, *args, **kwargs):
        if not cls.instance_flag:  
            cls.instance_flag = object.__new__(cls)
        return cls.instance_flag
obj_1 = a()
obj_2 = a()
obj_3 = a()
print(obj_1)
print(obj_2)
print(obj_3)

# 打印内容如下
<__main__.a object at 0x0000000002868588>
<__main__.a object at 0x0000000002868588>
<__main__.a object at 0x0000000002868588>
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
【采用单例模式动机、原因】
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或id(序号)生成器。如在windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。
【单例模式优缺点】
【优点】
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。
【缺点】
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
__call__:实例化对象()  或者 类名()()会触发

class a:
    def __init__(self):
        pass
    def __call__(self, *args, **kwargs):
        print('__call__')

obj = a()  # 实例化对象
obj()    # 执行 __call__
a()()  # 执行 __call__
__call__
__call__
__item__:当以字典的形式操作实例对象时会触发。
class a:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        print("添加属性,修改属性时,激活我")
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]删除值时,我执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('删除属性时,我执行')
        self.__dict__.pop(item)

obj_1=a('小明')
obj_1['age']=18         # 新增属性
obj_1['age1']=19        # 修改属性
del obj_1.age1          # 删除属性
del obj_1['age']        # 删除字典的值 
print(obj_1.__dict__)
添加属性,修改属性时,激活我
添加属性,修改属性时,激活我
删除属性时,我执行
del obj[key]删除值时,我执行
{'name': '小明'}
好了双下方法就到这里吧。

property

将一个类的函数定义成属性,对象再去使用的时候,可以直接使用对象.属性的方式来执行这个函数,从表面无法判断是属性还是方法。

class a:
    @property
    def fun(self):
        print("我是被分装成属性的函数")
obj = a()
obj.fun   # 调用属性

# 打印内容如下
我是被分装成属性的函数
如果单从调用fun来看根本看不出来fun到底是函数还是一个真正的属性。
那么我们对property都有哪些操作呢?可以获取property,设置property和删除property三种操作。这三种操作有两种实现方式,如下:

class a:
    @property
    def aaa(self):
        print('get的时候运行我啊')
    @aaa.setter
    def aaa(self,value):
        print('set的时候运行我啊')
    @aaa.deleter
    def aaa(self):
        print('delete的时候运行我啊')
#只有在属性aaa定义property后才能定义aaa.setter,aaa.deleter
f1=a()
f1.aaa        # 获取属性
f1.aaa='aaa'  # 设置属性
del f1.aaa    # 删除属性

# 打印内如下
get的时候运行我啊
set的时候运行我啊
delete的时候运行我啊
第二种方式

class b:
    def get_aaa(self):  # 获取属性时
        print('get的时候运行我啊')
    def set_aaa(self,value):  # 设置属性
        print('set的时候运行我啊')
    def delete_aaa(self):   # 删除属性
        print('delete的时候运行我啊')
    aaa=property(get_aaa,set_aaa,delete_aaa) #内置property三个参数与get,set,delete一一对应

f1=b()
f1.aaa       # 获取属性
f1.aaa='aaa' # 设置属性
del f1.aaa   # 删除属性

# 打印内容如下
get的时候运行我啊
set的时候运行我啊
delete的时候运行我啊
下面是一个商品实例的应用:
class goods(object):
    def __init__(self):
        self.price_1 = 100     # 原价
        self.discount_1 = 0.8  # 折扣
    @property
    def price(self):
        new_price = self.price_1 * self.discount_1  # 实际价格 = 原价 * 折扣
        return new_price
    @price.setter
    def price(self, value):
        self.original_price = value  # 重新设置价格
    @price.deleter
    def price(self):  # 删除价格
        del self.original_price
obj = goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
del obj.price     # 删除商品原价

isinstance和issubclass的区别

isinstance(a,b):判断a是否是b类(或者b类的派生类)实例化的对象

如下代码:

class a:
    pass
class b(a):
    pass
obj = b()
print(isinstance(obj,b))
print(isinstance(obj,a))

# 打印内容如下
true
true

issubclass(a,b): 只能判断a类是否是b类的派生类。

class a:
    pass
class b(a):
    pass
class c(b):
    pass
print(issubclass(c,b))
print(isinstance(c,a))

# 打印内容如下
true
false

元类type。

按照python的一切皆对象理论,类其实也是一个对象,那么类这个对象是从哪里实例化出来的呢?

class a:
    pass
print(isinstance(a, type))
print(isinstance(a, object))

print(isinstance(object,type)) # object是type的实例化对象
print(issubclass(type, object))# 而type又是object的子类

# 打印内如下
true
true
true
true

type元类是获取该对象从属于的类,而type类比较特殊,python原则是:一切皆对象,其实类也可以理解为'对象',而type元类又称作构建类,python中大多数内置的类(包括object)以及自己定义的类,都是由type元类创造的。

* 而type类与object类之间的关系比较独特:object是type类的实例,而type类是object类的子类,这种关系比较神奇无法使用python的代码表述,因为定义其中一个之前另一个必须存在。所以这个只作为了解。有时间在研究。