函数 & 类
函数
定义
def 函数名(形参列表)
函数执行语句
...
参数类型
Python 弱类型语言,可动态改变变量的类,虽然可通过变量名: 类型
指定变量的类型,但是仅用于提示开发者。
默认值
Python 支持为参数指定参数的默认值。有默认值的参数一定得在没有默认值参数的后面。
⚠️
Python 的默认值只会计算一次,即如果默认值是一个可变类型,并且函数内部对其进行了修改,那么多次调用会导致,每一次调用时的默认值都不同。
# 每多一次调用,输出的列表就多一个值
def test1(l = []):
l.append(random.randint(0, 100))
print(l)
# 不管调用多少次,都只包含一个值
def test2(l = None):
if l is None:
l = []
l.append(random.randint(0, 100))
print(l)
动态参数列表
Python 使用 *
定义动态参数列表,用于接收形参列表以外的非具名参数,如:
def add_all(*args): # args 是一个元组
result = 0
for i in args:
result += i
return result
print(add_all(1, 2, 3)) # 输出 6
Python 还可以使用 **
接受非形参列表的命名参数的字典,如:
def values(**kwargs):
print(kwargs)
values(a=1, b=2) # 输出 {'a': 1, 'b': 2}
如果同时存在普通参数和 *args 和 **kwargs,则先定义普通参数、元组动态参数、最后是具名动态参数
特殊参数
Python 默认情况下参数都能通过按位置传或者使用关键字传参。
⚠️
按位置传参需要在使用关键字之前。
- '/': 在之前的参数只能使用位置传参
- '*': 在之后的参数只能使用关键字传参
在这个两个符号之间的,就是默认情况。
调用
函数名(参数列表)
函数名(参数名=参数值,...)
传入动态参数列表
Python 使用 *
传入动态参数列表,如:
def add(a, b):
return a + b
print(add(*[1, 2])) # 输出 3
print(add(*(1, 2))) # 输出 3
Python 还可以使用 **
传入命名参数的动态参数列表,如:
def add(a, b):
return a + b
print(add(**{'a': 1, 'b': 2})) # 输出 3
全局变量和局部变量
在函数题内部的变量为局部变量,可通过 global
关键字申明为全局变量。
匿名函数
使用 lambda
关键字定义
匿名函数名 = lambda 形参列表: 执行代码
类
定义
class 类名:
# 变量
# 方法
TIP
- 使用
__
开头的变量或者方法为类私有,只能在类内部调用(可通过添加_类名
前缀强行访问) - 类方法的形参列表的第一个使用
self
表示类自身的引用 __init__
为类初始化方法
大部分情况下可能会说
__init__
方法是 Python 类的构造方法,不过我认为__new__
叫构造方法更合理。 通过实现__new__
实现单例模式(不能使用@classmethod
的类方法):pythonclass Signleton: __instance = None def __new__(cls, *args, **kwargs): if cls.__instance is None: cls.__instance = super().__new__(cls) # object 的 __new__ 不支持其它参数 return cls__instance obj1 = Singleton() obj2 = Singleton() print(obj1 == obj2)
继承
class 子类名(父类名):
# 变量
# 方法
- 默认继承至
object
,可忽略不写 - 子类可通过
super()
调用父类的方法或变量 - 子类不具有父类的私有成员(初始化方法除外,子类默认继承父类的初始化方法,自定义后,可通过
supper().__init__()
调用父类的初始化方法)
注意
- Python 支持多继承
- 如果父类具有相同的变量,Python 会通过
MRO
机制进行处理(先定义的父类,优先级高,使用了super()
调用父类的方法,则 Python 会通过类.__mro__
属性的顺序执行父类的方法)
- 如果父类具有相同的变量,Python 会通过
- Python 不支持方法重载
类方法 | 静态方法 | 普通方法(实例方法)
- 类方法:使用
@classmethod
装饰的方法,推荐使用 cls 作为第一个参数名,在通过类和实例调用时都是传入类的 type 实例,也就是类方法只能修改类顶层定义的变量。 - 静态方法:使用
@staticmethod
装饰的方法,和 Java 等其它语言的静态方法类类似,不会自动传入任何参数。 - 普通方法:不使用装饰器,且第一个参数为
self
(叫什么名字是什么无所谓) 的方法
为什么普通方法叫什么名无所谓
- 首先就算是普通的方法,也能通过
类名.method()
直接调用 - 如果存在参数列表,则通过类名调用需要手动传
- 普通方法其实是 python 帮忙处理了,在通过实例调用时会自动将实例对象传为第一个参数,这也是一个问题点,如果类里有一个没参数列表的方法,通过通过实例调用则会引发错误,因为 python 会自动传入实例作为第一个参数
实例方法 | 类方法 | 静态方法 | |
---|---|---|---|
定义方法 | 接受一个默认参数 一般命名为 self | 使用 @classmethod 装饰器装饰 并接受一个默认参数 一般命名为 cls | 使用 @staticmethod 装饰器装饰 不接受默认参数 |
调用方式 | 使用类实例调用:obj.methodName(args) 使用类类型调用: className.methodName(obj, args) | 也支持使用类实例和类直接调用:className.methodName(args) obj.methodName(args) | 和类方法一样的调用方式 |
权限范围 | 可以范围实例变量:self.objVar type(self).classVar self.classVar | 只能访问类变量,类变量是共享的 | 不能访问类变量和实例变量,只能访问方法参数 |
使用场景 | 需要访问或修改实例的状态 | 需要操作类属性或调用类方法 | 方法逻辑与类和实例无关,但与类相关联 |
在实例方法中使用 type(self).classVar
和 self.classVar
访问的区别
使用 type(self).classVar
访问使用的是类变量,修改会使得后续实例范围时的值被更改。
同时因为实例变量的优先级访问比类变量优先级高,定义实例变量后,直接访问的就是实例变量。
class MyClass:
count = 1
def type_count(self):
# self.count += 1
type(self).count += 1
def self_count(self):
self.count += 1
MyClass().type_count()
print(MyClass.count) # 2
MyClass().type_count()
print(MyClass.count) # 3
cls = MyClass()
cls.self_count() # 4
print(cls.count)
cls.self_count() # 5
print(cls.count)
cls = MyClass()
cls.self_count() # 4
print(cls.count)
cls.self_count() # 5
print(cls.count)
不能使用 @classmethod
的方法
这些方法默认就是类方法,第一个参数为 cls
,下面列出类方法,还有一个用在元类中的 __prepare__
方法
__new__
在上面的类的定义中有提到,通过使用 __new__
实现单例模式。
__init_subclass__
初始化子类的时候调用,第一个参数也是 cls
,表示子类的类型。
__class_getitem__
让类型可直接使用 className[item]
语法进行访问,第一个参数是 cls
、第二个参数是传入的 item
。
元类
Python 的元类(metaclass
)用于控制类的创建和属性的访问等。
- 控制类的创建过程:在元类中实现
__new__
方法 - 修改类的属性:
- 其它操作
元类需要继承至
type
类,通过metaclass
指定类使用的元类类型
使用元类为所有方法添加日志
class LoggerMeta(type):
@staticmethod
def __logger_advice(func):
def wrapper(*args, **kwargs):
print(f"LoggerMeta: {func.__name__}")
return func(*args, **kwargs)
return wrapper
def __new__(cls, name, bases, attrs):
for key, attr in attrs.items():
if callable(attr):
attrs[key] = cls.__logger_advice(attr)
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=LoggerMeta):
def test(self):
print("MyClass.test()")
cls = MyClass()
cls.test()
常见的可重写方法
官方文档:
__str__
重写调用 str()
返回的结果。
默认的
__str__
实现是调用了__repr__
方法可通过 PyObject_Str 中做到对应实现代码。
序列化为字符串时调用
class Person:
def __init__(self, name, age):
self.name = name;
self.age = age;
def __str__(self):
return f"Person(name = {self.name}, age = {self.age})"
person = Person("张三", 24)
print(f"person = {person}")
print(person)
person_str = str(person)
print(person_str)
__repr__
重写调用 repr()
返回的结果,repr()
用于返回对象的标准字符串。 一般推荐返回能直接通过 eval()
函数直接重新构建相同对象内容的字符串。
修改 repr()
返回的内容
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
print("__str__")
return super().__str__()
def __repr__(self):
return f"Person(name = '{self.name}', age = {self.age})"
person = Person("张三", 24)
person_repr = repr(person)
# 输出 repr(person) = Person(name = '张三', age = 24)
print(f"repr(person) = {person_repr}")
# 输出 type(eval(person_repr)) = <class '__main__.Person'>
print(f"type(eval(person_repr)) = {type(eval(person_repr))}")
__hash__
重写 hash()
函数返回的内容,应该返回一个整形值。集合的元素判断使用的就是这个函数的返回结果。
示例
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Person(name = '{self.name}', age = {self.age})"
def __hash__(self):
# 使得同名的 Person 对象返回相同的哈希值
return hash(self.name)
person1 = Person("张三", 23)
person2 = Person("张三", 25)
person3 = Person("张四", 25)
# 前两行输出一定相同,后一行一定和前两行不同
print("hash(person1) = ", hash(person1))
print("hash(person2) = ", hash(person2))
print("hash(person3) = ", hash(person3))
__iter__
返回一个迭代器(iterator),可使用 for in
语法。
返回的迭代器也要实现
__iter__
方法,返回迭代器本身,还需要__next__
方法返回下一个元素。
示例
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
class Classroom:
def __init__(self, *names):
self.names = [Student(name) for name in names]
def __iter__(self):
return iter(self.names)
classroom = Classroom("张三", "李四", "王五")
for student in classroom:
print(student)
运算符重载
二元运算
运算符 | 需要重写方法 |
---|---|
+ | __add__(self, other) 、__radd(self, other) |
- | __sub__(self, other) 、__rsub(self, other) |
* | __mul__(self, other) 、__rmul(self, other) |
** | __pow__(self, other) 、__rpow(self, other) |
pow() | __pow__(self, other, modulo=None) 、__rpow(self, other, modulo=None) |
/ | __truediv__(self, other) 、__rtruediv(self, other) |
// | __floordiv__(self, other) 、__rfloordiv(self, other) |
% | __mod__(self, other) 、__rmod(self, other) |
@ | __matmul__(self, other) 、__rmatmul(self, other) |
<< | __lshift__(self, other) 、__rlshift(self, other) |
>> | __rshift__(self, other) 、__rrshift(self, other) |
& | __and__(self, other) 、__rand(self, other) |
| | __or__(self, other) 、__ror(self, other) |
^ | __xor__(self, other) 、__rxor(self, other) |
- 正常情况下:
x + y => type(x).__add__(x, y)
- 上面的抛出
NotImplemented
错误时:x + y => type(y).__radd__(y, x)
赋值运算
运算符 | 需要重写方法 |
---|---|
+= | __iadd(self, other)__ |
-= | __isub(self, other)__ |
*= | __imul(self, other)__ |
**= | __ipow(self, other)__ |
/= | __itruediv(self, other)__ |
//= | __ifloordiv(self, other)__ |
%= | __imod(self, other)__ |
@= | __imatmul(self, other)__ |
<<= | __ilshift(self, other)__ |
>>= | __irshift(self, other)__ |
&= | __iand(self, other)__ |
|= | __ior(self, other)__ |
^= | __ixor(self, other)__ |
x += y => type(x).__iadd(x, y)
一元元算
运算符 | 需要重写方法 |
---|---|
- | __neg__(self) |
+ | __pos__(self) |
~ | __invert__(self) |
abs() | __abs__(self) |