7、面向对象

1、类和实例

在 Python 中,定义类是通过class关键字:

# 类的定义:方式一
class Student(Object):
    pass 

# 类的定义:方式二
class Student():
    pass 

class后面紧接着是类名,即Student类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类(也可以省略不写)。

定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。

2、属性和方法

类可以有属性和方法。

属性

属性又可以分为类数据属性、实例数据属性、特殊的类属性。

首先看一个类的示例:

# coding=utf-8


# 类中的属性分为类属性和实例属性
class Person(object):
    tall = 180  # 类属性

    def __init__(self, name, age, weight):
        # 以下 3 个为实例属性
        self.name = name
        self.age = age
        self.weight = weight

    def infoma(self):
        print('%s is %s weights %s' % (self.name, self.age, self.weight))


person = Person('bruce', 25, 60)
person.infoma()

在上面的Person类中,tallnameageweight都被称为类的数据属性,但是它们又分为 类数据属性实例数据属性

(1)类数据属性

类数据属性属于类本身,可以通过类名进行访问/修改:

Person.tall = 178
print("person tall : " % Person.tall)

在类定义之后,可以通过 类名 动态添加 类数据属性,新增的类数据属性也被类和所有实例共有:

Person.habbies = ['reading', 'swimming', 'running']
print("Person.habbies: " % Person.habbies)

(2)实例数据属性

实例数据属性只能通过实例来访问。

Bruce = Person("Bruce", 25,60)
print ("%s is %d years old" %(Bruce.name, Bruce.age))

在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例

对于类属性和实例属性,可以总结为:

  • 类属性属于类本身,可以通过类名进行访问/修改
  • 类属性也可以被类的所有实例访问/修改
  • 在类定义之后,可以通过类名动态添加类数据属性,新增的类数据属性也被类和所有实例共有
  • 实例数据属性只能通过实例访问
  • 在实例生成后,还可以动态添加实例数据属性,但是这些实例数据属性只属于该实例

注意事项(重要)虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦

(3)特殊的类属性

__ name__:			类的名字(字符串)
__ doc __ :			类的文档字符串
__ bases __:		类的所有父类组成的元组
__ dict __:			类的属性组成的字典
__ module __:		类所属的模块
__ class __:		类对象的类型

示例:

# -*- coding: utf-8 -*-
"""
    Description:类的特殊属性
"""


# 类中的属性分为类数据属性和实例数据属性
class Person(object):
    """类的特殊属性介绍"""
    tall = 180  # 类数据属性

    def __init__(self, name, age, weight):
        # 以下 3 个为实例数据属性
        self.name = name
        self.age = age
        self.weight = weight

    def infoma(self):
        print('%s is %s weights %s' % (self.name, self.age, self.weight))


print(Person.__name__)      # Person
print(Person.__doc__)       # 类的特殊属性介绍
print(Person.__bases__)     # (<class 'object'>,)
print(Person.__dict__)      
# {
#     '__module__': '__main__', 
#     '__doc__': '类的特殊属性介绍', 
#     'tall': 180, 
#     '__init__': <function Person.__init__ at 0x10ca928c8>, 
#     'infoma': <function Person.infoma at 0x10ca92950>, 
#     '__dict__': <attribute '__dict__' of 'Person' objects>, 
#     '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
print(Person.__module__)    # __main__
print(Person.__class__)     # <class 'type'>

(4)获取对象信息

type()方法和types模块

用于判断对象的类型。基本类型都可以使用type()函数判断:

>>> type(123)
<class 'int'>

>>> type('str')
<class 'str'>

>>> type(None)
<type(None) 'NoneType'>

如果变量指向一个函数或者类,也可以用type()函数判断:

>>> type(abs)
<class 'builtin_function_or_method'>

>>> type(A)
<class '__main__.Animal'>

type()函数返回的对应的class类型。如果要判断一个对象是否是一个函数,则需要借助types模块:

>>> import types
>>> def fn():
...     pass
...

>>> type(fn)==types.FunctionType
True

>>> type(abs)==types.BuiltinFunctionType
True

>>> type(lambda x: x)==types.LambdaType
True

>>> type((x for x in range(10)))==types.GeneratorType
True

isinstance()方法

对于class的继承关系来说,使用type()就很不方便。此时可以使用isinstance()方法。例如有如下继承关系:

object -> Animal -> Dog -> Husky

那么,isinstance()就可以告诉我们,一个对象是否是某种类型:

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

>>> isinstance(h, Husky)
True

能用type()方法的地方也可以用isinstance()方法进行判断:

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

dir()方法

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

(5)属性隐藏

类数据属性属于类本身,被所有该类的实例共享;并且,通过实例可以去访问/修改类属性。但是,在通过实例中访问类属性的时候一定要谨慎,因为可能出现属性”隐藏”的情况。

# -*- coding: utf-8 -*-
"""
    Description:属性隐藏
    一般情况下,类数据属性只推荐使用类名.属性名来访问,不推荐使用 实例.类数据数据属性来访问,因为后一种方式可能会出现属性隐藏
    的现象
"""


# 类中的属性分为类数据属性和实例数据属性
class Person(object):
    """属性隐藏"""
    tall = 180  # 类数据属性
    hobbies = []

    def __init__(self, name, age, weight):
        # 以下 3 个为实例数据属性
        self.name = name
        self.age = age
        self.weight = weight

    def infoma(self):
        print('%s is %s weights %s' % (self.name, self.age, self.weight))


Bruce = Person("Bruce", 25, 180)

print("person.tall is Bruce.tall: ", Person.tall is Bruce.tall)
Bruce.tall = 185  # 通过实例对类数据属性进行重新赋值或者修改
print("person.tall is Bruce.tall: ", Person.tall is Bruce.tall)
print(Person.__dict__)
print(Bruce.__dict__)

del Bruce.tall  # 再次删除实例的赋值
print("person.tallis Bruce.tall: ", Person.tall is Bruce.tall)  # person.tall is Bruce.tall为True

# person.tall is Bruce.tall:  True
# person.tall is Bruce.tall:  False
# {
#     '__module__': '__main__',
#     '__doc__': '属性隐藏',
#     'tall': 180,
#     'hobbies': [],
#     '__init__': <function Person.__init__ at 0x10d4d38c8>,
#     'infoma': <function Person.infoma at 0x10d4d3950>,
#     '__dict__': <attribute '__dict__' of 'Person' objects>,
#     '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
# {'name': 'Bruce', 'age': 25, 'weight': 180, 'tall': 185}
# person.tallis Bruce.tall:  True

print("person.hobbies is Bruce.hobbies: ", Person.hobbies is Bruce.hobbies)
Bruce.hobbies = ["C#", "Python"]
print("person.hobbies is Bruce.hobbies : ", Person.hobbies is Bruce.hobbies)
print(Person.__dict__)
print(Bruce.__dict__)
del Bruce.hobbies
print("person.hobbies is Bruce.hobbies: ", Person.hobbies is Bruce.hobbies)

# person.hobbies is Bruce.hobbies:  True
# person.hobbies is Bruce.hobbies :  False
# {
#     '__module__': '__main__',
#     '__doc__': '属性隐藏',
#     'tall': 180,
#     'hobbies': [],
#     '__init__': <function Person.__init__ at 0x10d3108c8>,
#     'infoma': <function Person.infoma at 0x10d310950>,
#     '__dict__': <attribute '__dict__' of 'Person' objects>,
#     '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
# {'name': 'Bruce', 'age': 25, 'weight': 180, 'hobbies': ['C#', 'Python']}
# person.hobbies is Bruce.hobbies:  True

Bruce.hobbies.append("CSS") #内存地址没有新指向,在原来基础上扩展
print("person.hobbies is Bruce.hobbies: ", Person.hobbies is Bruce.hobbies)
print(Person.__dict__)
print(Bruce.__dict__)

# person.hobbies is Bruce.hobbies:  True
# {
#     '__module__': '__main__',
#     '__doc__': '属性隐藏',
#     'tall': 180,
#     'hobbies': ['CSS'],
#     '__init__': <function Person.__init__ at 0x1081ed8c8>,
#     'infoma': <function Person.infoma at 0x1081ed950>,
#     '__dict__': <attribute '__dict__' of 'Person' objects>,
#     '__weakref__': <attribute '__weakref__' of 'Person' objects>
# }
# {'name': 'Bruce', 'age': 25, 'weight': 180}

对于不可变类型的类属性,隐藏属性可以总结为:

  • 对于不可变类型的类属性person.tall,可以通过实例Bruce进行访问,并且”person.tall is Bruce.tall”
  • 当通过实例赋值/修改tall属性的时候,将为实例Bruce新建一个tall实例属性,这时,”person.tall is not Bruce.tall”
  • 当通过”del Bruce.tall”语句删除实例的tall属性后,再次成为”person.tall is Bruce.tall”

对于可变类型的类属性,隐藏属性可以总结为:

  • 同样对于可变类型的类属性person.hobbies,可以通过实例Bruce进行访问,并且”person.hobbies is Bruce. hobbies”
  • 当通过实例赋值hobbies 属性的时候,都将为实例Bruce新建一个 hobbies-> [‘c#’,‘python’] 实例属性,这时,”person.hobbies is not Bruce. hobbies”
  • 当通过”del Bruce. hobbies”语句删除实例的hobbies属性后,再次成为”person. hobbies is Bruce.books”
  • 当通过实例修改hobbies属性的时候,将修改Bruce. hobbies指向的内存地址(即person.hobbies),此时,”person.hobbies is not Bruce. hobbies”

ps:虽然通过实例可以访问类属性,但是,不建议这么做,最好还是通过类名来访问类属性,从而避免属性隐藏带来的不必要麻烦

方法

(1)专有方法

类的专有方法有:

# 和实例创建、运行相关的方法
__init__       构造函数,在生成对象时调用
__new__			   创建实例
__call__       函数调用
__del__        析构函数,释放对象时使用

# 和字符串有关的方法
__str__				打印转换
__repr__       打印,转换
__formate__
__bytes__

# 和运算有关的方法
__add__        加运算
__sub__        减运算
__mul__        乘运算
__div__        除运算
__mod__        求余运算
__pow__        称方
__eq__				相等
__ge__				大于等于
__gt__				大于
__le__				小于等于
__lt__				小于
__ne__				不等于
__len__        获得长度
__cmp__        比较运算
__bool__			

# 和实例属性有关的方法
__getattribute__
__getattr__
__setattr__
__delattr__
__getitem__
__setitem__
__delitem__
__missing__

和实例创建、运行相关的特殊方法__init____new____call__方法

类名加括号调用了__init__方法类创建一个实例对象。这一过程分成了两步:

  • 第一步:类调用__new__来创建实例对象;

  • 第二步:__new__调用__init__来初始化实例对象。

# -*- coding: utf-8 -*-
"""
    Description:和实例创建、运行相关的专有方法
"""
# class A:
#     count = 0
#
#     def __init__(self):
#         print("__init__ has called. 2")
#
#     def __new__(cls):
#         print("__new__ has called. 1")
#         return object.__new__(cls)
#
#     def __call__(cls):
#         print("__call__ has called. 3")
#
#
# a = A()
# 通过调用方法的调用顺序也能看出:__init__和__new__方法执行的先后顺序。
# __new__ has called. 1
# __init__ has called. 2

# a()
# __call__ has called. 3


class Obj(object):
    def __init__(self, name, price):
        self.name = name
        self.__price = price

    def __say(self):
        print("{}, {}.".format(self.name, self.__price))

    def __call__(self, discount):
        self.__price = discount * self.__price
        self.__say()


# 类名加括号,先调用__new__方法创建实例,创建的实例在调用__init__方法进行初始化。
apple = Obj("apple", 10.0)

# 实例对象后加括号:调用__call__方法,作为程序的入口。
apple(0.8)

类的__new__()方法很少通过用户代码定义。如果定义了它,它通常是用原型__new__(cls, *args, **kwargs)编写的。其中argskwargs与传递给__init__()的参数相同。

__new__()始终是一个类方法,接受类对象作为第一个参数。尽管__new__()会创建一个实例,但它不会自动地调用__init__()

实例对象加括号会调用__call__方法,一般作为程序的入口,并且可以在这一步修改实例属性或调用实例方法。

和实例属性有关的专有方法__getattribute____getattr____setattr____delattr____getitem____setitem____delitem____missing__方法

__getattribute____getattr__ 方法

  • 首先执行__getattribute__方法,去调用object基类的__getattribute__来查询__dict__里的键值对。如果存在则直接返回,相当于执行了member.__dict__.get(obj),如果不存在则调用__getattr__方法。
  • __getattr__方法会在实例内存空间中尽可能的搜索该属性值,如果搜索到则直接返回,搜索不到会抛出AttributeError
# -*- coding: utf-8 -*-
"""
    Description:类的特殊属性
"""


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

    def __getattribute__(self, obj):
        print("__getattribute__ is called.")
        # 调用Object 基类的__getattribute__方法来查询__dict__中的键值对。
        return object.__getattribute__(self, obj)

    def __getattr__(self, obj):
        print("__getattr__ is called.")
        return "{} Not assgined.".format(obj)


# 创建示例对象,并进行初始化
member = MemberCounter("An", 24)

# 访问示例对象的属性
print(member.name)
"""
首先调用__getattribute__方法,去调用object基类的__getattribute__来查询__dict__
        里的键值对。如果存在则直接返回,相当于执行了member.__dict__.get(obj),
__getattribute__ is called.
An
"""
print("\n------------------\n")
print(member.gender)

"""
首先调用__getattribute__方法,去调用object基类的__getattribute__来查询__dict__
        里的键值对,如果不存在则调用__getattr__方法。
__getattribute__ is called.
__getattr__ is called.

调用__getattr__方法会在实例内存空间中尽可能的搜索该属性值,如果搜索到则直接返回,
        搜索不到会抛出AttributeError。
        
gender Not assgined.
"""

注意下面两段代码的区别:__getattribute__return只是一个硬编码的字符串而不是属性值,__getattr__return则是实例的属性值。因此,如果有需求,一般会在__getattr__里写需求代码。

class MemberCounter:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __getattribute__(self, obj):
        if obj == "gender":
            return "male"
        else:
            return object.__getattribute__(self, obj)

member = MemberCounter("An", 24)
print(member.gender)
member.gender = "female"
print(member.gender)
print(member.__dict__)
"""
male
male
{'name': 'An', 'age': 24, 'gender': 'female'}
"""
class MemberCounter:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __getattr__(self, obj):
        if obj == "gender":
            return "male"
        else:
            raise AttributeError

member = MemberCounter("An", 24)
print(member.gender)
member.gender = "female"
print(member.gender)
print(member.__dict__)

"""
male
female
{'name': 'An', 'age': 24, 'gender': 'female'}
"""

__setattr____delattr__ 方法

__init__时,会调用__setattr__初始化实例属性,在__dict__中添加键值对。

# -*- coding: utf-8 -*-
"""
    Description:类的特殊属性
"""


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

    def __setattr__(self, old, new):
        print("{} = {} has been created.".format(old, new))
        object.__setattr__(self, old, new)

    def __delattr__(self, key):
        print("{} is deleted.".format(key))
        del self.__dict__[key]

    def __getattr__(self, item):
        print("__getattr__ 方法被调用。")
        return self.__dict__[item]  # object.__getattr__(self, item)


# 创建并初始化实例对象
member = MemberCounter("An", 24)
# __init__方法中调用__setattr__方法,对实例属性进行赋值
# name = An  has been created.
# age = 24  has been created.

# 访问实例的字典
print(member.__dict__)
# {'name': 'An', 'age': 24}


# 删除某个实例属性:调用 delattr 方法
del member.age
# age is deleted.

print(member.__dict__)
# {'name': 'An'}

在使用self.name = namedel member.age时,实际上调用了__setattr____delattr__。这两个函数没有类似__getattribute__的使用规则。即无论何时给属性赋值,都会调用 __setattr__() 方法;无论何时删除一个属性,都将调用 __delattr__() 方法。

__getitem____setitem____delitem____missing__ 方法

通过操作实例的__dict__属性来获取、设置和删除实例的属性值

# -*- coding: utf-8 -*-
"""
    Description:类的特殊属性
"""


class A:
    def __init__(self, *args):
        self.name, self.age = args

    def __getitem__(self, key):
        print("__getitem__ has called.")
        if key in self.__dict__:
            return self.__dict__.get(key, ValueError)
        else:
            self.__missing__(key)
            return self.__dict__.get(key, ValueError)

    def __setitem__(self, key, value):
        print("__setitem__ has called.")
        self.__dict__.update({key: value})

    def __delitem__(self, key):
        print("__delitem__ has called.")
        del self.__dict__[key]

    def __missing__(self, nonexistent_key):
        print("__missing__ has called")
        self.__dict__[nonexistent_key] = 'male'


# 创建并初始化实例
a = A("Li", 27)
print(a.__dict__)
# {'name': 'Li', 'age': 27}

# 通过键来获取值:__getitem__方法被调用
a["name"]
print(a.__dict__)
# __getitem__ has called.
# {'name': 'Li', 'age': 27}

# 通过键来设置值:__setitem__方法被调用
a["name"] = "Alex"
print(a.__dict__)
# __setitem__ has called.
# {'name': 'Alex', 'age': 27}

# 删除一个键值对:__delitem__方法被调用
del a["age"]
print(a.__dict__)
# __delitem__ has called.
# {'name': 'Alex'}

print(a['gender'])
# male

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vCDMdg9I-1573040226033)(./pic/类的特殊方法 2.jpeg)]

跟运算有关的专有方法 __eq__(==)__ge__(>=)__gt__(>)__le__ (<=)__lt__ (<)__ne__ (!=)__bool__

# -*- coding: utf-8 -*-
"""
    Description:类的特殊属性
"""


class A:
    def __init__(self, number):
        self.number = number

    def __gt__(self, obj):
        print("__gt__ has called.")
        return self.number > obj.number

    def __ge__(self, obj):
        print("__ge__ has called.")  # 既然执行了这个函数,你可以尽情的在return之前写想写的功能
        return self.number >= obj.number


a = A(23)  
b = A(45)  # 这里A就相当于int
c = A(45)

print(a > b)  # 当你写23 > 45时,实际等同于 a = int(23), b = int(45); a > b

print(b >= c)

"""
__gt__ has called.
False

__ge__ has called.
True
"""

和字符串相关的方法__str____repr____formate____bytes__方法

# -*- coding: utf-8 -*-
"""
    Description:类的特殊属性
"""


class Cls(object):
    def __init__(self):
        self.name = "alex"
        self.age = 27
        self.gender = "female"

    def __str__(self):
        print("__str__ has called.")
        return "Str: my name is {}, {}.".format(self.name, self.age)

    def __format__(self, special):
        print("__format__ has called.")
        if special == "":
            return self.__str__()   # 也可以写成str(self)
        for _, v in self.__dict__.items():  # 实际上是{"name": "alex", "age": 27}
            special = special.replace("%s", str(v), 1)
        return special

观察x下面两段代码的打印结果:

cls = Cls()
str1 = "{:%s, %s, %s}".format(cls)
print(str1)


"""
__format__ has called.
alex, 27, female
"""
cls = Cls()
str2 = format(cls, "my name is %s, %s, %s.")
print(str2)

"""
__format__ has called.
my name is alex, 27, female.
"""

以上两端代码本质都是一样的,即format和一个字符串匹配,也就是__format__函数传递了special参数(字符串)。当format函数没有和字符串匹配,也就是__format__函数没有传入参数special时,会调用__str__函数。

__str____repr__的区别和联系

__str__存在时,print(obj)str(obj)实际调用了__str__方法;当__str__不存在时,就会调用__repr__。但是当定义了__repr__时,obj本身会返回一个"合法"的字符串表达式,而不再返回一个描述符<'__main__.A object at XXX>,当然这个描述符也可以被__str__修改,只是会被__repr__覆盖。

class A:
    def __str__(self):
        print("__str__ has called.")
        return self.__class__.__name__ + "__str__"

a = A()

a   # <__main__.A at 0x110a764e0>
print(a)  
# __str__ has called.
# A__str__

str(a)
# __str__ has called.
# A__str__
class A:
        def __repr__(self):
        print("__repr__ has called.")
        return self.__class__.__name__ + "__repr__"
a = A()

a
#__repr__ has called.
#A__repr__

print(a)
#__repr__ has called.
#A__repr__

str(a)
#__repr__ has called.
#A__repr__
class A:
    def __str__(self):
        print("__str__ has called.")
        return self.__class__.__name__ + "__str__"
    
    def __repr__(self):
        print("__repr__ has called.")
        return self.__class__.__name__ + "__repr__"
a = A()

a
#__repr__ has called.
#A__repr__

print(a)
#__str__ has called.
#A__str__

str(a)
#__str__ has called.
#'A__str__'

通常__str__()__repr__()代码都是一样的,所以会这么写:

class Student(object):
   def __init__(self, name):
       self.name = name
   def __str__(self):
       return 'Student object (name=%s)' % self.name
   __repr__ = __str__

(2)自定义方法

在类中定义方法时,有三种类型的方法可供供选择:

实例方法

通过def定义,需要至少传入一个参数,即self,这样定义的方法必须通过一个类的实例取访问,类似于C++中通过对象去访问。

类方法

这种类方法的特点是可以通过类名去调用,但是也必须传递一个参数:cls。即class,表示可以通过类名直接调用。

静态方法

静态的类方法,类似C++的静态函数,特点是参数可以为空,支持类名和对象两种调用方式

class A(object):
    # 实例方法
   def foo1(self):
       print("hello, ", self)

    # 静态方法
   @staticmethod
   def foo2():
       print("hello, ")

    # 类方法
   @classmethod
   def foo3(cls):
       print("hello, ", cls)


a = A()
a.foo1()
# 最常见的调用方式

A.foo1(a)
# 与第一种调用方式相同

A.foo2()
a.foo2()
# 静态方法可以通过类名和对象调用

A.foo3()
print(A)
# 类方法,支持通过类名调用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值