『Python』面向对象(一)

类和对象

类(class)是用来描述具有相同属性(attribute)和方法(method)的对象的集合,对象(object)是类(class)的具体实例。比如学生都有名字和分数,他们有着共同的属性。这时我们就可以设计一个学生类, 用于记录学生的名字和分数,并自定义方法打印出他们的名字和方法。

class Student(object):
    def __init__(self,name,age,score):
        self._name = name
        self._age = age
        self._score = score

    def show(self):
        print("{}的年龄是{}岁,分数是{}分".format(self._name,self._age,self._score))

aStudent = Student("瑞雯",18,99)
  • 属性:类里面用于描述所有对象共同特征的变量或数据。如_name_age_score
  • 方法:类里面的函数,用来区别类外面的函数, 用来实现某些功能。如show()
  • 对象(实例):根据类创建出来的,如aStudent

属性

类属性

定义在类中函数体外的属性,属于类共有属性

class A(object):
    count = 0
    def __init__(self):
         # 类中只有通过类名.类属性访问,self.__class__返回的就是类名
        self.__class__.count += 1	# 或 A.count += 1
        
a = A()
b = A()

# 类外既可以通过类来访问,也可通过类的实例来访问
print(a.count)  # 2
print(A.count)  # 2

要修改类属性必须通过类名.属性的方式来修改,而不能通过实例,因为实例.属性这种方式其实是创建了同名的实例属性,屏蔽了类的属性,通过del 实例.属性操作后,会发现实例.属性还是之前的。

实例属性

定义在__init__()方法中,属于对象

class B(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age

a = B("易",18)
b = B("金克斯",8)

一般来说,对象的属性应该设为private,不能被外界直接访问,这里暂且不管访问权限问题。当能从外界直接访问时,必须通过对象.属性来访问,这时候属性是属于对象实例的。

访问控制

Python中并没有publicprotectedprivate这样的关键字,所以无法实现数据封装,只能从语法上来定义可见性,依靠程序员自觉遵守规约。

Python中,不存在函数的重载,因为函数名和普通变量名一样,都是引用,指向一个对象,是一对一的关系,都是标识符。Python中就是通过标识符的命名来区分访问的可见性。

  • 字符开头的标识符,如:age
    这种标识符相当于public,可以通过对象.属性或者对象.方法来执行。

  • 双下划线开头结尾的标识符,如: __init__
    用户最好不要自定义这种类型的标识符,因为这通常是系统调用。

  • 单下划线开头的标识符,如:_age
    这种标识符相当于protected,不过Python中没有protected的概念,所以被视为private,但是,你可以按照public用,属于推荐private但是可以public的。

  • 双下划线开头的标识符,如:name
    相当于privatePython通过更改标识符名(改为_类名__标识符)来实现无法访问的机制。

方法

实例方法

class Test(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age

    def grow(self,growth):
        self.__age += growth

只能被对象实例调用,第一个参数必须是self,它指向调用这个方法的实例。

类方法

class Test(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age

    @classmethod
    def getInstance(cls):
        return cls("薇恩",16)

绑定到类的方法,必须在定义函数的上方使用 @classmethod 装饰器,同时,第一个参数必须是 cls,这个 cls 代表这个类的类型,如上面例子中的Test,所以在类方法内部可以用 cls(参数) 创建对象,也可以用它调用属于类的属性、其他可使用的方法。对外,类方法可以通过类调用,也可以通过实例对象来调用类方法。

静态方法

class Test(object):
    def __init__(self,name,age):
        self.__name = name
        self.__age = age

    @staticmethod
    def static_func():
        print("正在调用静态方法")

静态方法不像前两个有特定参数,比较自由,即使你强行把第一个参数名写出selfcls,也不会有相应的作用。静态方法可以通过类或者实例对象调用。

property方法

内置函数property()是用来使类的属性能像Java Bean那样操作的函数,它的函数定义为:

property(fget=None, fset=None, fdel=None, doc=None)
  • fget:    function to be used for getting an attribute value
  • fset:    function to be used for setting an attribute value
  • fdel:    function to be used for deleting an attribute
  • doc:    docstring
class C(object):
    def getx(self): return self._x
    def setx(self, value): self._x = value + 1
    def delx(self): del self._x
    x = property(getx, setx, delx)

c = C()
c.x = 5
print(c.x)  # 6
del c.x
# print(c.x) AttributeError: 'C' object has no attribute '_x'

这样,既能使对象能简单调用属性,也能保证数据的封装,可是,这么写太麻烦,普遍用的形式是使用装饰器来实现Bean:

class Hero(object):
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        print("getter方法...")
        return self.__name

    @name.setter
    def name(self,value):
        print("setter方法...")
        self.__name = value

    @name.deleter
    def name(self):
        print("deleter方法...")
        del self.__name

p = Hero("蝙蝠侠")
p.name                      # getter方法...
p.name = "游城十代"          # setter方法...
del p.name                  # deleter方法...

一般@property只用于私有属性“公有化”,并且getter方法和deleter方法只能有self参数。

类的特殊成员

__doc__

表示类的描述信息

class Foo(object):
    '''Foo的描述'''

print(Foo.__doc__)					# Foo的描述

__module____class__

  • __module__表示当前操作的对象在那个模块
  • __class__表示当前操作的对象的类是什么,也就是谁创建了这个类,metaclass还是type
class Foo(object):
    pass

print(Foo.__module__)					# __main__
print(Foo.__class__)					# <class 'type'>

__all__

__all__是一个字符串list,用来定义模块中对于from XXX import *时要对外导出的符号,即要暴露的借口,但它只对import *起作用,对from XXX import XXX不起作用

__all__ = ['MNIST', 'FashionMNIST', 'CIFAR10', 'CIFAR100',
           'ImageRecordDataset', 'ImageFolderDataset']

__init__

构造方法,创建对象时自动执行的初始化函数

class Foo(object):
    def __init__(self):
        print("init方法...")

f = Foo()  # init方法...

__del__

  • 析构方法,当对象在内存中被释放时,自动触发执行
  • 此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
class Foo:
    def__del__(self):
        pass

__call__

  • 对象后面加括号,触发执行
  • 构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Demo(object):
    def __init__(self):
        print("执行init...")

    def __call__(self, *args, **kwargs):
        print("执行call...")

d = Demo()  # 执行init...
d()  # 执行call...

__dict__

  • 返回类或对象中的所有成员
  • 普通字段属于对象,静态字段和方法属于类
class Province(object):
    country = 'China'

    def __init__(self, name, count):
        self.name = name
        self.count = count

    def func(self, *args, **kwargs):
        print('func')

print(Province.__dict__)					# 返回类的所有成员
'''
{'__module__': '__main__', 'country': 'China', 
'__init__': <function Province.__init__ at 0x0000022A0C5278C8>, 
'func': <function Province.func at 0x0000022A0C527950>, 
'__dict__': <attribute '__dict__' of 'Province' objects>, 
'__weakref__': <attribute '__weakref__' of 'Province' objects>,
 '__doc__': None}
'''

print(Province("卡兹克",1).__dict__)		# 返回对象的成员
'''
{'name': '卡兹克', 'count': 1}
'''

__str____repr__

  • 都是更改类的显示方式
  • __str__是给用户看到的字符串,__repr__是给开发者看的,比如debug时,变量列表显示的是__repr__函数返回的内容
  • 一般情况下只用写一个__str__,然后__repr__ = __str__
class Text(object):
    def __init__(self,text):
        self.__text = text

    def __str__(self):
        return self.__text
    __repr__ = __str__  # 一般都这么写偷懒

t = Text("无极剑圣")
print(t)  # 无极剑圣

__getitem____setitem____delitem__

  • 用于索引操作,如字典。以上分别表示获取、设置、删除数据
  • __getitem__也可以传入切片
class Subject(object):
    def __getitem__(self, item):  # 根据item返回一个值
        print("调用getitem...")

    def __setitem__(self, key, value):  # 设置key对应的值为value
        print("调用setitem...")

    def __delitem__(self, key):	  # 删除这组键值对
        print("调用delitem...")

m = Subject()
n = m['math']  # 调用getitem...
m['math'] = 100  # 调用setitem...
del m['math']  # 调用delitem...

__iter____next__

用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了__iter____next__

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1  # 初始化两个计数器a,b

    def __iter__(self):
        return self  # 实例本身就是迭代对象,故返回自己

    def next(self):
        self.a, self.b = self.b, self.a + self.b  # 计算下一个值
        if self.a > 100000:  # 退出循环的条件
            raise StopIteration();
        return self.a  # 返回下一个值

__slots__

一般情况下,可以任意地给对象添加属性,这会造成很大的漏洞,影响程序安全,为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

s = Student()
s.name = 'Michael'
s.age = 25
s.score = 99  # AttributeError: 'Student' object has no attribute 'score'

显然,这时候不能添加限制以外的属性,但要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类的属性不做限制,除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身__slots__加上父类__slots__

其他

例如各种重载运算符的函数,需要用到Google即可

创建对象

传统创建类

class Demo(object):
    def __init__(self,name):
        self.__name = name

d = Demo("乐芙兰")

d是通过Demo类实例化的对象,其实不仅d是一个对象,Demo本身也是一个对象,因为在Python中,一切皆对象,d是通过执行Demo类的构造方法创建,那么Demo也应该是通过某个类的构造方法创建。

print(type(d))  # <class '__main__.Demo'>    表示d由Demo类创建
print(type(Demo))  # <class 'type'>          表示Demo由type类创建

所以,d对象是Demo类的一个实例,Demo类对象是 type 类的一个实例,即:Demo类对象是通过type类的构造方法创建。

type创建类

  • 语法:type('类名',父类的元组,成员字典)
def init(self,name):
    self.__name = name

def show(self):
    print(self.__name)

Demo = type('Demo',(object,),{'__init__':init,'output':show,'a':3})
d = Demo("诡术妖姬")
print(d)  # <__main__.Demo object at 0x0000017F3C0F32E8>
d.output()  # 诡术妖姬
  • 一般用类名同名的变量来接受创建的类
  • 父类只有object时,注意元组单元素时的逗号,成员字典中,成员名是字符串,对应的值可以是方法地址,可以是属性值
  • initshow这些方法可以在前面加 @classmethod 或者 @staticmethod 等,来定义类函数和静态函数

__new__方法

__new__方法是类自带的一个方法,可以重写,__new__方法在实例化的时候也会执行,并且先于__init__方法之前执行,简单理解,创建对象和初始化对象。

class Foo(object):
    def __init__(self, name):
        self.name = name
        print("Foo __init__")

    def __new__(cls, *args, **kwargs):
        print("Foo __new__", cls, *args, **kwargs)
        return object.__new__(cls)

f = Foo("凯特琳")
"""
Foo __new__ <class '__main__.Foo'> 凯特琳
Foo __init__
"""

重写__new__方法

  • 重写时,必须要调用父类的_new__方法,不然会覆盖父类的__new__方法,实例创建不了
  • __new__()方法创建出该类的实例,然后返回该实例给__init__()方法调用。__init__()方法的self就是__init__()方法创建返回的
  • 依照Python官方文档的说法,__init__()方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径,还有就是实现自定义的metaclas

继承不可变对象的例子

假如我们需要一个永远都是正数的整数类型,通过继承int

class PositiveInteger(int):

    def __new__(cls, value):
        return super(PositiveInteger, cls).__new__(cls, abs(value))


i = PositiveInteger(-3)

print(i)  # 3

这时候可能有的人会通过__init__方法来使-3变为3,但是,因为继承于int,是不可变的,__new__创建完时,值已经确定了,再通过__init__修改是不行的。

__new__方法与__init__方法的关系

class A(object):
    def __init__(self):
        print(self)
        print("这是__init__()方法...")

    def __new__(cls):
        print(id(cls))
        print("这是__new__()方法...")
        ret = super().__new__(cls)
        print(ret)
        return ret


print(id(A))
a = A()
'''
输出:
	2151279654824
	2151279654824
	这是__new__()方法...
	<__main__.A object at 0x000001F4E4406FD0>
	<__main__.A object at 0x000001F4E4406FD0>
	这是__init__()方法...
'''

单例模式

class Number(object):
    __instance = None

    def __new__(cls, val):
        if cls.__instance is None:
            cls.__instance = super().__new__(cls)
            cls.__instance.__value = val
            return cls.__instance
        else:
            return cls.__instance

    def __str__(self):
        return str(self.__value)


m = Number(-3)
n = Number(-6)

print(m)  # -3
print(n)  # -3

print(id(m))  # 2199891453768
print(id(n))  # 2199891453768

metaclass

  • metaclass,直译为元类,简单的解释就是,当我们定义了类以后,就可以根据这个类创建出实例,所以,先定义类,然后创建实例。但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以,先定义metaclass,然后创建类,连接起来就是:先定义metaclass,就可以创建类,最后创建实例。所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

  • metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况,所以,以下内容看不懂也没关系,因为基本上你不会用到

我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法。定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass

# metaclass是创建类,所以必须从type类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class MyList(list,metaclass=ListMetaclass):
    pass

当我们写下metaclass = ListMetaclass语句时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义

测试一下MyList是否可以调用add()方法:

l = MyList()
l.add(1)
print(l)  # [1]

元类中,最好只定义__new__()方法,因为其他类如果使用了“metaclass = XXXMetaclass”,只会调用这个metaclass__new__()方法来创建类,也就是避免了直接创建类,在创建类之前还封装了一组操作,但是,metaclas中其他的方法、属性等,是不会让使用metaclass创建的类继承的,所以一般在元类中,只写__new__()方法。metaclass可以隐式继承到子类

posted @ 2020-04-02 13:36  芜情  阅读(222)  评论(0编辑  收藏  举报