python 4

class Person :
    '''这是一个学习Python定义的一个Person类'''
    # 下面定义了2个类变量
    name = "zhangsan"
    age = "20"
    def __init__(self,name,age):
        #下面定义 2 个实例变量
        self.name = name
        self.age = age
        print("这个人的名字是:",name," 年龄为:",age)
    # 下面定义了一个say实例方法
    def say(self, content):
        print(content)
# 将该Person对象赋给p变量
p = Person("张三",20)

1.给类对象动态添加变量

p = Person("张三",20)
p.name = '李刚'
print(p.name)
del p.name
print(p.name)

在这里插入图片描述
程序中调用 del 删除了 p 对象的 name 实例变量,但由于类中还有同名的 name 类变量,因此程序不会报错;
否则会导致 AttributeError 错误,并提示:‘Person’ object has no attribute ‘name’。

2.给类对象动态添加方法
需要说明的是,为 p 对象动态增加的方法,Python 不会自动将调用者自动绑定到第一个参数(即使将第一个参数命名为 self 也没用)。例如如下代码:

def info(self):
    print("---info函数---", self)
# 使用info对p的foo方法**赋值**(动态绑定方法)
p.foo = info

# Python不会自动将调用者绑定到第一个参数,
# 因此调用该程序需要手动将调用者绑定为第一个参数
p.foo(p)  # ①


# 使用lambda表达式为p对象的bar方法赋值(动态绑定方法)
p.bar = lambda self: print('--lambda表达式--', self)
p.bar(p) # ②

如果希望动态增加的方法也能自动绑定到第一个参数,则可借助于 types 模块下的 MethodType 进行包装。例如如下代码:

def intro_func(self, content):
    print("我是一个人,信息为:%s" % content)
    
# 导入MethodType
from types import MethodType

# 使用MethodType对intro_func进行包装,将该函数的第一个参数绑定为p
p.intro = MethodType(intro_func, p)

# 第一个参数已经绑定了,调用时无需传入
p.intro("生活在别处")

3.类实例方法

class Person :
    #类构造方法,也属于实例方法
    def __init__(self, name = 'Charlie', age=8):
        self.name = name
        self.age = age
        
    # 下面定义了一个say实例方法
    def say(self, content):
        print(content)

实例方法最大的特点就是,它最少也要包含一个 self 参数,用于绑定调用此方法的实例对象
实例方法通常会用类对象直接调用,当然也可以用类名调用

#创建一个类对象
person = Person()

#类对象调用实例方法
person.say("类对象调用实例方法")

#类名调用实例方法,===========需手动给 self 参数传值
Person.say(person,"类名调用实例方法")

======================================================

4.类方法
类方法和实例方法相似,它最少也要包含一个参数,只不过,类方法中通常将其命名为 cls,且 Python 会自动将类本身绑定给 cls 参数(而不是类对象)。因此,在调用类方法时,无需显式为 cls 参数传参。
类方法需要使用@classmethod进行修饰

5.类静态方法
静态方法需要使用@staticmethod修饰

6.类调用实例方法:
Python 只要求手动为第一个参数绑定参数值,并不要求必须绑定 User 该类的对象,因此也可使用其他代替:

User.walk('fkit')

7.类命名空间

class Item:
    # 直接在类空间中放置执行性质代码
    print('正在定义Item类')
    for i in range(10):
        if i % 2 == 0 :
            print('偶数:', i)
        else:
            print('奇数:', i)

=====================================================
直接运行,无需调用类生成对象?

global_fn = lambda p: print('执行lambda表达式,p参数: ', p)
class Category:
    cate_fn = lambda p: print('执行lambda表达式,p参数: ', p)
    
# 调用全局范围内的global_fn,为参数p传入参数值
global_fn('fkit')  # ①
c = Category()

# 调用类命名空间内的cate_fn,Python自动绑定第一个参数
#对象调用实例方法。
c.cate_fn()  # ②

程序直接在 类体中放置普通的输出语句、循环语句、分支语句,这都是合法的。当程序执行类时,类命名空间中的这些代码都会被执行

二、property
1.Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性。

property() 函数的基本使用格式如下:
属性名=property(fget=None, fset=None, fdel=None, doc=None)
fget 参数用于指定获取该属性值的类方法
fset 参数用于指定设置该属性值的方法
fdel 参数用于指定删除该属性值的方法,
doc 是一个文档字符串,用于提供说明此函数的作用。

开发者调用 property() 函数时,可以传入 0 个(既不能读,也不能写的属性)、
1 个(只读属性)、
2 个(读写属性)、
3 个(读写属性,也可删除)

4 个(读写属性,也可删除,包含文档说明)参数。

class Rectangle:
    # 定义构造方法
    def __init__(self, width, height):
        self.width = width
        self.height = height
        
    # 定义setsize()函数
    def setsize (self , size):
        self.width, self.height = size
        
    # 定义getsize()函数
    def getsize (self):
        return self.width, self.height
        
     # 定义getsize()函数
    def delsize (self):
        self.width, self.height = 0, 0 
        
    # 使用property定义属性
    size = property(getsize, setsize, delsize, '用于描述矩形大小的属性')

====================================================

# 访问size属性的说明文档
print(Rectangle.size.__doc__)

# 通过内置的help()函数查看Rectangle.size的说明文档
help(Rectangle.size)
rect = Rectangle(4, 3)

# 访问rect的size属性
print(rect.size) # (4, 3)

# 对rect的size属性赋值
rect.size = 9, 7

# 访问rect的width、height实例变量
print(rect.width) # 9
print(rect.height) # 7

# 删除rect的size属性
del rect.size

# 访问rect的width、height实例变量
print(rect.width) # 0
print(rect.height) # 0

**2、**既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作操作类属性,除了使用 property() 函数,Python 还提供了 @property 装饰器。

@property 的语法格式如下:
@property
def 方法名(self)
    代码块

例如,定义一个矩形类,并定义用 @property 修饰的方法操作类中的 area 私有属性,代码如下:

   class Rect:
        def __init__(self,area):
            self.__area = area
        @property
        def area(self):
            return self.__area
   rect = Rect(30)

#直接通过方法名来访问 area 方法

 print("矩形的面积是:",rect.area)
运行结果为:
矩形的面积为: 30

上面程序中,使用 @property 修饰了 area() 方法,这样就使得该方法变成了 area 属性的 getter 方法

需要注意的是,如果类中只包含该方法,那么 area 属性将是一个只读属性。

也就是说,在使用 Rect 类时,无法对 area 属性重新赋值,即运行如下代码会报错:

rect.area = 90
print("修改后的面积:",rect.area)

运行结果为:

Traceback (most recent call last):
  File "C:\Users\mengma\Desktop\1.py", line 10, in <module>
    rect.area = 90
AttributeError: can't set attribute

而要想实现修改 area 属性的值,还需要为 area 属性添加 setter 方法,就需要用到 setter 装饰器,它的语法格式如下:

@方法名.setter
def 方法名(self, value):
    代码块

例如,为 Rect 类中的 area 方法添加 setter 方法,代码如下:

@area.setter
def area(self, value):
    self.__area = value

再次运行如下代码:

rect.area = 90
print("修改后的面积:",rect.area)

运行结果为:

修改后的面积: 90

这样,area 属性就有了 getter 和 setter 方法,该属性就变成了具有读写功能的属性。

除此之外,还可以使用 deleter 装饰器来删除指定属性,其语法格式为:

@方法名.deleter
def 方法名(self):
    代码块

例如,在 Rect 类中,给 area() 方法添加 deleter 方法,实现代码如下:

@area.deleter
def area(self):
    self.__area = 0

然后运行如下代码:

del rect.area
print("删除后的area值为:",rect.area)

运行结果为:

删除后的area值为: 0

三、函数装饰器及用法

当程序使用“@函数”(比如函数 A)装饰另一个函数(比如函数 B)时,实际上完成如下两步:

1.将被修饰的函数(函数 B)作为参数传给 @ 符号引用的函数(函数 A)。
2.函数 B 替换(装饰)成第 1 步的返回值。

def funA(fn):
    print('A')
    fn() # 执行传入的fn参数
    return 'fkit'

@funA
def funB():
    print('B')
print(funB) # fkit

上面程序使用 @funA 修饰 funB,这意味着程序要完成两步操作:

1、将 funB 作为 funA() 的参数,也就是上面代码中 @funA 相当于执行 funA(funB)
2.将 funB 替换成 funA() 执行的结果,funA() 执行完成后返回 fkit,因此 funB 就不再是函数,而是被替换成一个字符串

运行上面程序,可以看到如下输出结果:

A
B
Fkit

通过 @ 符号来修饰函数是 Python 的一个非常实用的功能,
1.在被修饰函数的前面添加一些额外的处理逻辑(比如权限检查)
2.在被修饰函数的后面添加一些额外的处理逻辑(比如记录日志)
3.在目标方法抛出异常时进行一些修复操作……
这种改变不需要修改被修饰函数的代码,只要增加一个修饰即可。

下面例子示范了如何通过函数装饰器为函数添加权限检查的功能。程序代码如下:

def auth(fn):
    def auth_fn(*args):
        # 用一条语句模拟执行权限检查
        print("----模拟执行权限检查----")
        # 回调要装饰的目标函数
        fn(*args)
    return auth_fn
    
@auth
def test(a, b):
    print("执行test函数,参数a: %s, 参数b: %s" % (a, b))

# 调用test()函数,其实是调用装饰后返回的auth_fn函数
test(20, 15)

上面程序使用 @auth 修饰了 test() 函数,这会使得 test() 函数被替换成 auth() 函数所返回的 auth_fn 函数,而 auth_fn 函数的执行流程是:
1.先执行权限检查
2.回调被修饰的目标函数。
简单来说,auth_fn 函数就为被修饰函数添加了一个权限检查的功能。

四、封装
Python 并没有提供类似于其他语言的 private 等修饰符,因此 Python 并不能真正支持隐藏。
为了隐藏类中的成员,Python 玩了一个小技巧:
只要将 Python 类的成员命名为以双下画线开头的,Python 就会把它们隐藏起来。

Python 其实没有真正的隐藏机制,Python 会“偷偷”地改变以双下画线开头的方法名,会在这些方法名前添加单下画线和类名。

u._User__hide()

# 对隐藏的__name属性赋值
u._User__name = 'fk'

# 访问User对象的name属性(实际上访问__name实例变量)
print(u.name)

五、继承

class 类名(父类1, 父类2, ...):
    #类定义部分

1.)关于Python的多继承
当一个子类有多个直接父类时,该子类会继承得到所有父类的方法,这一点在前面示例中己经做了示范。现在的问题是,如果多个父类中包含了同名的方法,此时会发生什么呢?
此时排在前面的父类中的方法会遮蔽排在后面的父类中的同名方法。例如如下代码:

class Item:
    def info (self):
        print("Item中方法:", '这是一个商品')
        
class Product:
    def info (self):
        print("Product中方法:", '这是一个工业产品')
        
class Mouse(Item, Product): # ①
    pass
m = Mouse()
m.info()

运行上面程序,将看到如下输出结果:

Item 中方法:这是一个商品

如果将程序中第 7 行代码改为如下形式:

class Mouse(Product,Itern): #①

此时 Product 父类的优先级高于 Item 父类,因此 Product 中的 info() 方法将会起作用。运行上面程序,将会看到如下输出结果:

Product中方法:这是一个工业产品

2.)super()函数:调用父类的构造方法

1.Python 的子类也会继承得到父类的构造方法,但如果子类有多个直接父类,那么会优先选择排在最前面的父类的构造方法。

2.重写父类的构造方法。Python 要求,如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法。

子类的构造方法调用父类的构造方法有两种方式:
1.使用未绑定方法,这种方式很容易理解。因为构造方法也是实例方法,当然可以通过这种方式来调用。
2.使用 super() 函数调用父类的构造方法。

# Manager继承了Employee、Customer
class Manager(Employee, Customer):
    # 重写父类的构造方法
    def __init__(self, salary, favorite, address):
        print('--Manager的构造方法--')
        
        # 通过super()函数调用父类的构造方法
        super().__init__(salary)
        
        # 与上一行代码的效果相同
        #super(Manager, self).__init__(salary)
        
        # 使用未绑定方法调用父类的构造方法
        Customer.__init__(self, favorite, address)
# 创建Manager对象
m = Manager(25000, 'IT产品', '广州')
m.work()  #①
m.info()  #②

super调用根据MRO调用。详情见
https://www.runoob.com/w3cnote/python-super-detail-intro.html

3.)slots
1. slots 属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名

class Dog:
    __slots__ = ('walk', 'age', 'name')
    def __init__(self, name):
        self.name = name
    def test():
        print('预先定义的test方法')
d = Dog('Snoopy')
from types import MethodType
# 只允许动态为实例添加walk、age、name这3个属性或方法
d.walk = MethodType(lambda self: print('%s正在慢慢地走' % self.name), d)
d.age = 5
d.walk()
d.foo = 30 # AttributeError

2.slots 属性并不限制通过类来动态添加属性或方法

Dog.bar = lambda self: print('abc') # AttributeError
d.bar()

3.此外,slots 属性指定的限制只对当前类的实例起作用,对该类派生出来的子类是不起作用的。

4.如果要限制子类的实例动态添加属性和方法,则需要在子类中也定义 slots 属性,这样,子类的实例允许动态添加属性和方法就是子类的 slots 元组加上父类的 slots 元组的和

4) type() 函数
1.可以查看变量的类型
2.python 完全允许使用 type() 函数(相当于 type 类的构造器函数)来创建 type 对象,又由于 type 类的实例就是类,因此 Python 可以使用 type() 函数来动态创建类。

def fn(self):
    print('fn函数')
    
# 使用type()定义Dog类
Dog = type('Dog', (object,), dict(walk=fn, age=6))

参数一:创建的类名。
参数二:该类继承的父类集合。由于 Python 支持多继承,因此此处使用元组指定它的多个父类。即使实际只有一个父类,也需要使用元组语法(必须要多一个逗号)。
参数三:该字典对象为该类绑定的类变量和方法。其中字典的 key 就是类变量或方法名,如果字典的 value 是普通值,那就代表类变量;如果字典的 value 是函数,则代表方法。

5) MetaClass
MetaClass(元类),简单的理解,就是创建类的类,即创建类之后,再由类来创建实例进行应用。使用元类可以在创建类时动态修改类定义。为了使用元类动态修改类定义,程序需要先定义元类。

# 定义Item元类,继承type
class ItemMetaClass(type):
    # cls代表动态修改的类
    # name代表动态修改的类名
    # bases代表被动态修改的类的所有父类
    # attr代表被动态修改的类的所有属性、方法组成的字典
    def __new__(cls, name, bases, attrs):
        # 动态为该类添加一个cal_price方法
        attrs['cal_price'] = lambda self: self.price * self.discount
        return type.__new__(cls, name, bases, attrs)

上面程序定义了一个 ItemMetaClass,该类继承了 type 类,并重写了 new 方法,在重写该方法时为目标类动态添加了一个 cal_price 方法。

元类的 __new__ 方法的作用是:
当程序使用 class 定义新类时,如果指定了元类,那么元类的 __new__ 方法就会被自动执行。


# 定义Book类
class Book(metaclass=ItemMetaClass):
    __slots__ = ('name', 'price', '_discount')
    def __init__(self, name, price):
        self.name = name
        self.price = price
    @property
    def discount(self):
        return self._discount
    @discount.setter
    def discount(self, discount):
        self._discount = discount
        
# 定义cellPhone类
class CellPhone(metaclass=ItemMetaClass):
    __slots__ = ('price', '_discount' )
    def __init__(self, price):
        self.price = price
    @property
    def discount(self):
        return self._discount
    @discount.setter
    def discount(self, discount):
        self._discount = discount

上面程序定义了 Book 和 CellPhone 两个类,在定义这两个类时都指定了元类信息,因此当 Python 解释器在创建这两个类时,ItemMetaClass 的 new 方法就会被调用,用于修改这两个类。

ItemMetaClass 类的 new 方法会为目标类动态添加 cal_price 方法,因此,虽然在定义 Book、CellPhone 类时没有定义 cal_price() 方法,但这两个类依然有 cal_price() 方法。

b = Book("Python基础教程", 89)
b.discount = 0.76
# 创建Book对象的cal_price()方法
print(b.cal_price())

cp = CellPhone(2399)
cp.discount = 0.85
# 创建CellPhone对象的cal_price()方法
print(cp.cal_price())

67.64
2039.1499999999999

6)多态
对于弱类型的语言来说,变量并没有声明类型,因此同一个变量完全可以在不同的时间引用不同的对象。当同一个变量在调用同一个方法时,完全可能呈现出多种行为(具体呈现出哪种行为由该变量所引用的对象来决定),这就是所谓的多态

class Canvas:
    def draw_pic(self, shape):
        print('--开始绘图--')
        shape.draw(self)
        
class Rectangle:
    def draw(self, canvas):
        print('在%s上绘制矩形' % canvas)
        
class Triangle:
    def draw(self, canvas):
        print('在%s上绘制三角形' % canvas)
        
class Circle:
    def draw(self, canvas):
        print('在%s上绘制圆形' % canvas)
        
c = Canvas()
# 传入Rectangle参数,绘制矩形
c.draw_pic(Rectangle())
# 传入Triangle参数,绘制三角形
c.draw_pic(Triangle())
# 传入Circle参数,绘制圆形
c.draw_pic(Circle())

7)Python枚举类定义和使用

程序有两种方式来定义枚举类:
1.直接使用 Enum 列出多个枚举值来创建枚举类。

import enum

# 定义Season枚举类

Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))

该构造方法的第一个参数是枚举类的类名;第二个参数是一个元组,用于列出所有枚举值。

在定义了上面的 Season 枚举类之后,程序可直接通过枚举值进行前问,这些枚举值都是该枚举的成员。

每个成员都有 name、value 两个属性,其中 name 属性值为该枚举值的变量名,value 代表该枚举值的序号(序号通常从 1 开始)。

例如,如下代码测试了枚举成员的用法:

# 直接访问指定枚举
print(Season.SPRING)

# 访问枚举成员的变量名
print(Season.SPRING.name)

# 访问枚举成员的值
print(Season.SPRING.value)

运行该程序,可以看到如下输出结果:

Season.SPRING
SPRING
1

程序除可直接使用枚举之外,还可通过枚举变量名或枚举值来访问指定枚举对象。例如如下代码:

变量名是【】,变量值是()

# 根据枚举变量名访问枚举对象
print(Season['SUMMER']) # Season.SUMMER

# 根据枚举值访问枚举对象
print(Season(3)) # Season.FALL

此外,Python 还为枚举提供了一个 members 属性,该属性返回一个 dict 字典,字典包含了该枚举的所有枚举实例。

程序可通过遍历 members 属性来访问枚举的所有实例。
例如如下代码:

# 遍历Season枚举的所有成员

for name, member in Season.__members__.items():
    print(name, '=>', member, ',', member.value)
    
运行上面代码,可以看到如下输出结果:
SPRING => Season.SPRING , 1
SUMMER => Season.SUMMER , 2
FALL => Season.FALL , 3
WINTER => Season.WINTER , 4

2.通过继承 Enum 基类来派生枚举类。

import enum
class Orientation(enum.Enum):
    # 为序列值指定value值
    EAST = '东'
    SOUTH = '南'
    WEST = '西'
    NORTH = '北'
    
    def info(self):
        print('这是一个代表方向【%s】的枚举' % self.value)
        
print(Orientation.SOUTH)
print(Orientation.SOUTH.value)

# 通过枚举变量名访问枚举
print(Orientation['WEST'])

# 通过枚举值来访问枚举
print(Orientation('南'))

# 调用枚举的info()方法
Orientation.EAST.info()

# 遍历Orientation枚举的所有成员
for name, member in Orientation.__members__.items():
    print(name, '=>', member, ',', member.value)

枚举的构造器

import enum
class Gender(enum.Enum):
    MALE = '男', '阳刚之力'
    FEMALE = '女', '柔顺之美'
    def __init__(self, cn_name, desc):
        self._cn_name = cn_name
        self._desc = desc
    @property
    def desc(self):
        return self._desc
    @property
    def cn_name(self):
        return self._cn_name
        
# 访问FEMALE的name
print('FEMALE的name:', Gender.FEMALE.name)

# 访问FEMALE的value
print('FEMALE的value:', Gender.FEMALE.value)

# 访问自定义的cn_name属性
print('FEMALE的cn_name:', Gender.FEMALE.cn_name)

# 访问自定义的desc属性
print('FEMALE的desc:', Gender.FEMALE.desc)

上面程序定义了 Gender 枚举类,并为它定义了一个构造器,调用该构造器需要传入 cn_name 和 desc 两个参数,因此程序使用如下代码来定义
Gender 的枚举值。

MALE = '男', '阳刚之力'
FEMALE = '女', '柔顺之美'

上面代码为 MALE 枚举指定的 value 是‘男’和‘阳刚之力’这两个字符串,其实它们会被自动封装成元组后传给 MALE 的 value 属性;而且此处传入的‘男’和‘阳刚之力’ 这两个参数值正好分别传给 cnname 和 desc 两个参数。简单来说,枚举的构造器需要几个参数,此处就必须指定几个值。

运行上面程序,可以看到如下输出结果:

FEMALE的name: FEMALE
FEMALE的value: ('女', '柔顺之美')
FEMALE的cn_name: 女
FEMALE的desc: 柔顺之美
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值