前言

前面写的都是运算符、流程控制、排序查找等,下面说一说面向对象的一些内容。这是前面写的,有兴趣可以去看一看。

PHP入门之类型与运算符

PHP入门之流程控制

PHP入门之函数

PHP入门之数组

PHP基础之排序

PHP基础之查找

接下来写一下关于面向对象的内容。

类与对象基本概念

用一个案例入门:

<?php
//创建一个对象
class cat {
public $name;
public $age;
public $color;
}
//创建一个猫
$cat1= new cat;
$cat1->name="小刘";
$cat1->age=18;
$cat1->color="yellow";
//再创建一个猫
$cat2= new cat;
$cat2->name="小陈";
$cat2->age=16;
$cat2->color="pink";
//输出两个猫的信息
if ($cat1->name="小刘"){
echo $cat1->name."||".$cat1->age."||".$cat1->color.'<br/>';
}if ($cat2->name="小陈"){
echo $cat2->name."||".$cat2->age."||".$cat2->color;
}
?>

总结几句话:

  • ①类是抽象的,代表一类事物。
  • ②对象是具体,是类的一个具体实例。
  • ③类是对象的模板,对象是类的一个个具体实例。

    类的基本格式

    class 类名{

    成员属性(变量);

}

成员属性是从某个事物提取出来的,它可以是 基本数据类型,也可以是复合数据类型(数组,对象)

如何创建对象?

$对象名=new 类名();

$对象名=new 类名; //两种方式都可以

对象如何访问(使用)对象的属性?

$对象名->属性名;

对象在内存中存在形式

对象在内存中如何存在?

用下面代码说明:

<?php
class Person {
public $name;
public $age;
}
$p1= new Person();
$p1->name="小红";
$p1->age=18;
$p2=$p1;
echo $p1->name.'<br/>';
echo $p2->age.'<br/>'; ?>

现在画一下内存图:

|-----------------------------------|

| | | | //name="小红";age=18;变量$p1->name和age时就会由栈区指向堆区。

| | 数据区 | 栈区 | //指向得是地址

|堆区 |全局区(静态区)| |

| ------------ $p1 |

| 小红 | | |

| 18 | 数据区 | |

| | 常量区 | |

| ----------- |

| | | |

| | 代码区 | |

| | | |

|-----------------------------------

函数接收对象时候,究竟接收得是值,还是地址?

看一段代码:

<?php
class Person {
public $name;
public $age;
}
$p1= new Person();
$p1->name="小红";
$p1->age=18; #我们发现输出结果为大红,所以,函数接收对象时候,接收得是地址。
function test ($p){
$p->name="大红";
}
test($p1);
echo $p1->name.'<br/>';
?>

如果给函数传递的是基本数据类型(整行,浮点型,布尔型),传递的是什么?

默认情况下传递的是值。如果希望传地址,那就加上&符。

如果给一个函数传递的是一个数组,则默认情况下是传值。

举个例子:

<?php
$arr=array($a1,$a2);
$a1=array(3,5,8);
$a2=array(5,7,9);
var_dump($arr);
?>

可以输出结果吗?答案是无法输出结果。会报变量没有定义的错误。因为是传值,所以第一行的$a1和第二行的$a1是两码事。

如果换一下顺序,就可以了。

<?php
$a1=array(3,5,8);
$a2=array(5,7,9);
$arr=array($a1,$a2);
var_dump($arr);
?>

这样就可以输出了,数组有值了。

构造函数

什么是构造函数(方法)?

想要知道什么是构造函数,我们先看一个需求,之前我们创建一个对象的时候,是创建好之后,再给对象的属性进行赋值,如果我们再创建对象的时候就直接给属性赋值,这样该如何做呢?下面我们就要引入构造函数了。

上面的问题,我们只需要定义一个构造函数就可以了。构造函数是类的一种特殊的函数,它的主要作用是完成对新对象的初始化。

构造函数特点:

①没有返回值。

②在创建一个类的新对象时,系统会自动的调用该类的构造函数完成对新对象的初始化。

用一个小案例说明:

<?php
class Person{
public $name;
public $age;
function __construct($iname,$iage)
{
$name=$iname;
$age=$iage;
echo "我是构造函数";
echo '<br/>';
}
}
$p1=new Person("小可爱",18);
echo $p1->name;
echo '<br/>';
echo $p1->age;
?>

如果我们这样写,我们认为会输出:我是构造函数小可爱18,但是,最后只会输出我是构造函数。这位为什么呢?

之前我们说过,构造函数也是函数,也会开一个新栈。这里他会把$name和$age当成一个新的变量。并不会指向对象的属性。

所以,这里引入了一个重要的概念。$this(这个很重要)!!!!

如果使用$this,它就会指向当前对象,

再理解的深一点,就是这个对象的地址。哪个对象使用到$this,就是哪个对象地址。$this不能再类外部使用。

我们需要将上面的代码进行修改。

        $name=$iname;
$age=$iage;

改为:

    $this->name=$iname;
$this->age=$iage;

这样,程序就可以正常输出了。

这里需要注意的一点是,如果我们没有定义构造函数,系统会有一个默认的构造函数。

function __construct(){}

所以之前我们创建对象的时候都是 $p1= new person();

如果我们自定义了构造函数,再这样创建对象的时候,系统就会报错。

类中只能有一个构造函数(不能重载)

类的构造方法小结:

  • ①再PHP4中,构造方法名和类名相同,PHP5之后可以和类名相同也可以是__construct()。
  • ②构造方法没有返回值。
  • ③主要作用是完成对新对象的初始化,并不是创建对象本身。
  • ④在创建新对象后,系统自动的调用该类的构造方法。
  • ⑤一个类有且只有一个构造方法。
  • ⑥如果没有给类自动义构造方法,则该类使用系统默认的构造方法。
  • ⑦如果给类自定义了构造方法,则该类的默认构造方法被覆盖。
  • ⑧构造方法的默认访问修饰符是public。

析构函数

什么是析构函数?

析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。在PHP5中引用。

其实就是释放资源,比如(释放数据库的链接,图片资源,销毁某个变量...)等等。

用小案例入门:

<?php
class Person{
public $name;
public $age;
//构造函数
function __construct($name,$age)
{
$this->name=$name;
$this->age=$age;
}
//析构函数
function __destruct()
{
// TODO: Implement __destruct() method.
echo $this->name.'销毁资源'.'<br/>';
}
}
$p1= new Person("小王",18);
$p2=new Person("小张",20);
?>

运行程序,我们发现,析构函数会自动调用。主要用于销毁资源。

析构函数调用顺序是,先创建的对象后销毁。(想象一下子弹上膛,最后一颗子弹第一颗打出去,先进先出)。

所以上面的执行结果为:

小张销毁资源

小王销毁资源

什么时候系统会调用析构函数?

  • 一、程序运行完退出的时候。
  • 二、当对象没有变量指向它的时候,它会变成垃圾对象,会立刻调用析构函数回收。(和Java不一样)。

    还有两点需要注意:
  • 一、析构函数没有返回值。
  • 二、一个类最多只能有一个析构函数。

静态变量与静态方法

先提出一个需求:

如果现在有一群孩子在玩游戏,不停的有新得小朋友加入,统计小朋友的个数并输出。用面向对象的程序完成。

可以考虑全局变量的方式,但是不推荐,因为那就不算纯对象了。但是也可以做出来。

代码如下:

<?php
global $child_sums;
$child_sums=0;
class Child
{
public $name; function __construct($name)
{
$this->name = $name;
} function JoinChild()
{
//申明使用全局变量
global $child_sums;
$child_sums+=1;
echo $this->name . "加入游戏";
}
}
//创建三个小孩
$child1=new Child("拉拉");
$child1->JoinChild();
$child2=new Child("哈哈");
$child2->JoinChild();
$child3=new Child("哒哒");
$child3->JoinChild();
echo "<br/>"."有".$child_sums."个小朋友";
?>

虽然可以实现,但不推荐,下面我们使用静态变量的方法。

代码如下:

<?php
class Child{
public $name;
public static $sums=0;
//构造函数
function __construct($name)
{
$this->name=$name;
}
function JoinChild(){
self::$sums+=1;
echo $this->name.'加入游戏';
}
}
//创建三个小孩
$child1=new Child("拉拉");
$child1->JoinChild();
$child2=new Child("哈哈");
$child2->JoinChild();
$child3=new Child("哒哒");
$child3->JoinChild();
//看看多少人
echo '<br/>'."一共".Child::$sums."个小孩";
?>

那什么是静态变量呢,就是所有对象共享的一个变量,它不在堆区,在全局区。对象想要访问它,就指向它的地址。

如何定义呢?

访问修饰符 static 变量名;

如何访问呢?

在类外部 类名::$类变量名

在类内部有两种 类名::$类变量名或者self::$类变量名。

这里需要注意的一点是,访问静态变量和是否创建对象无关,你不创建对象,也可以访问。

访问静态变量,禁止使用$this,会报错。

静态方法

静态方法和静态变量是对应的,只能调用静态变量,如果调用非静态变量它是会报错的。反过来就可以,就是普通成员函数是可以调用静态变量的。原因是静态变量和静态方法都属于这个类,都是公开的。

还是上面的例子,进行一下修改。

<?php
class Child{
public $name;
public static $sums=0;
//构造函数
function __construct($name)
{
$this->name=$name;
}
static function JoinChild(){
//self::$sums+=1;
Child::$sums+=1;
}
function haizi(){
echo $this->name;
}
}
//创建三个小孩
$child1=new Child("拉拉");
$child1->haizi();
$child1->JoinChild();
$child2=new Child("哈哈");
$child2->haizi();
$child1->JoinChild();
$child3=new Child("哒哒");
$child3->haizi();
$child1->JoinChild();
//看看多少人
echo '<br/>'."一共".Child::$sums."个小孩";
?>

我们只需要在普通方法前加关键字static,就可以成为静态方法,如下面这样:

   static function JoinChild(){
//self::$sums+=1;
Child::$sums+=1;
}

有上面两种调用方法。

面向对象三大特性之封装

提到封装,应该先说一说修饰符。

public(公开的)、protected(受保护的)、private(私有的)

正因为有了protected(受保护的)、private(私有的)这两个修饰符,才能体现封装的概念。

写一个例子:

<?php
class Person{
public $name;
protected $age;
private $wage;
public function __construct($name,$age,$wage)
{
$this->name=$name;
$this->age=$age;
$this->wage=$wage; }
}
$p1=new Person("小利",18,1000);
echo $p1->name;
echo $p1->age; #报错
echo $p1->wage; #报错
?>

这样就体现了封装的概念,protected(受保护的)、private(私有的)这两个修饰符修饰的变量不让你直接调用。

如果,我们想要调用呢,那就写一个公开的方法,调用那个方法就可以了。

把上面的例子改一下,再类里添加:

    public function PersonAge($age){
echo $this->age=$age;
}
public function PersonWage($wage){
echo $this->wage=$wage;
}

然后类外调用这两个函数就可以了。

$p1->PersonAge(20);
$p1->PersonWage(3000);

你可能会有疑问,我们直接调就可以了,为什么要多走这一步,不是没事找事嘛。肯定是有原因的,方法里,我们可以对变量进一步控制,比如加个范围,权限再控制的细一些等等。

也可以用另外一种方法,PHP为我们提供的,叫做魔术方法:__set()、__get()

__set()对protected或是private属性,进行赋值操作。

__get()获取protected或是private属性的值。

面向对象三大特性之继承

先来看一个小问题,如果我们做一个学生管理系统,有小学生,大学生,研究生。如果我们创建三个类的话,那么我们就会发现一个问题,那就是代码重复。所以我们有了继承的概念。

写个小案例:

<?php
//父类
class Student{
public $name;
public $age;
public $studentID; public function ShowInfo($name,$age){
echo $this->name=$name."||".$this->age=$age;
}
}
//子类
class universityStudent extends Student{ public function study(){
echo "大学生在学习";
}
}
$u1=new universityStudent();
$u1->ShowInfo("小练习",18);
$u1->study();
?>

我们发现,子类可以使用父类的方法,这就解决了刚才的问题,解决了代码的重复性。如果想要使用继承,关键字extends不能少。

其实所谓继承,就是子类通过extends关键字,把父类的(public、protected)属性和(public、protected)方法继承下来。

我们还要注意,只能继承(public、protected)属性和(public、protected)方法,private的属性和方法只能本类使用。

注意:

子类最多只能继承一个父类(指直接继承)

在创建某个子类对象时,默认情况不会自动调用其父类的构造函数。(和Java不一样)。

举个例子:将上面的代码修改

<?php
class Student{
public $name;
public $age;
public $studentID;
function __construct()
{
echo "我是父类的构造函数"."<br/>";
}
public function ShowInfo($name,$age){
echo $this->name=$name."||".$this->age=$age;
}
}
class universityStudent extends Student{
public function __construct()
{
echo "我是子类的构造函数"."<br/>";
} public function study(){
echo "大学生在学习";
}
}
$u1=new universityStudent();
$u1->ShowInfo("小练习",18);
$u1->study();
?>

上面的代码会输出:

我是子类的构造函数
小练习||18大学生在学习

父类的构造函数不会自动调用。那如果想调用父类的构造函数呢。只需要在子类的代码中加入:父类名::构造函数名或者parent::构造函数名两种方法都可以。

    public function __construct()
{
Student::__construct();
echo "我是子类的构造函数"."<br/>";
}

这样的话,会输出:

我是父类的构造函数
我是子类的构造函数
小练习||18大学生在学习

如果子类的方法名和父类的方法名相同,这叫做方法的重写(覆盖),这就是多态了,后面再详细说多态。

面向对象三大特性之多态

多态是一种概念,下面说两个知识点。

函数重载

“重载”是类的多态的一种实现,是指的是一个标识符被用作多个函数名,并且能够通过参数个数或者参数类型将这些同名的函数区分开,调用不发生混淆。

PHP虽然支持重载,但重载在具体实现上,和其他语言有较大的差别。举个例子:

class A{
public $name;
public $age;
public function test(){
echo "hello,123";
}
public function test($a){ #如果我们这么写,PHP会报错!!!!其他的语言可以,Java这么写的话没问题。
echo "hello,456";
}
}
$a=new A();
$a->test();
$a->test($a);

上面的是错误的写法。PHP有自己的方法,这里PHP引进了魔术方法。魔术方法:__call()

这个方法比较神奇。下面看代码:

class A{
public $name;
public $age;
public function test1($a){
echo "hello,123";
}
public function test2($a){
echo "hello,456";
}
public function __call($name, $arguments)
{
var_dump($arguments);
if($name=="test"){
if(count($arguments)==1){
$this->test1($arguments);
}elseif (count($arguments)==2){
$this->test2($arguments);
}
}
// TODO: Implement __call() method.
}
}
$a=new A();
$a->test(1);
$a->test(2,6);
/*执行结果为:
array(1) { [0]=> int(1) } hello,123array(2) { [0]=> int(2) [1]=> int(6) } hello,456
我们发现执行成功了,实现了函数重载。这是多态的一种体现。*/

我们需要知道一些魔术常量:

echo "<br/>".__LINE__;
echo "<br/>".__DIR__;
echo "<br/>".__FILE__;
echo "<br/>".__CLASS__;
echo "<br/>".__TRAIT__;
echo "<br/>".__FUNCTION__;
echo "<br/>".__METHOD__;
echo "<br/>".__NAMESPACE__;
输出结果为:
150
D:\phpstudy_pro\WWW\PHP
D:\phpstudy_pro\WWW\PHP\object02.php
A
test1
A::test1
array(2) { [0]=> int(2) [1]=> int(6) } hello,456

方法重写(覆盖)

提一个问题,如果我们设计一个类,提取一些相同的特征,设计成父类,并有一些函数。如果子类中想要完善父类的方法,只需要在子类中方法的命名和父类相同,参数完全相同就可以。我们把它叫做方法的重写(覆盖)。如果子类想要调用父类的方法,可以使用parent::方法名()就可以。

子类方法不能缩小父类方法的访问权限,可以扩大。

上面的内容体现了面向对象的多态性。

抽象类

提一个问题,为什么设计抽象类。

为了快速开发,我们可能有这样的类,是其他类的父类,但它本身并不需要实例化,主要用途是用于子类去继承。这样可以达到代码复用,并且利于项目设计者设计类。

设计成抽象类二点格式:

abstract class 类名{

abstract 修饰符 function 函数名(参数列表);//这里要注意,没有方法体。

}

注意事项

  • 抽象类不能被实例化。
  • 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法。
  • 一旦类包含了abstract方法,则这个类必须声明为abstract。
  • 抽象方法不能有函数体。
  • 如果一个类继承了某个抽象类,则它必须实现该抽象类的所有抽象方法(除非自己也声明为抽象类)。

接口

为什么有接口,肯定是为了方便,也是为了规范,因为只要你要实现我这个接口,就比如实现里面的所有方法。

小案例入门:

<?php
interface iTest{
public function start();
public function stop();
}
class camera implements iTest{
public function start(){
echo "相机开始工作";
}
public function stop(){
echo "相机停止工作";
}
}
class phone implements iTest{
public function start(){
echo "手机开始工作";
}
public function stop(){
echo "手机停止工作";
}
}
$c1=new camera();
$c1->start();
$c1->stop();
echo "<br/>";
$p1=new phone();
$p1->start();
$p1->stop();
?>

输出结果:

相机开始工作相机停止工作

手机开始工作手机停止工作

接口细节讨论:

接口比抽象类更抽象,所以,接口更不能被实例化了。

接口中所有的方法都不能有主体。

一个类可以实现多个接口,逗号隔开,变相的完善了类继承(直接继承)的不足。

语法:

public class A implements 接口1,接口2{

}

接口中可以有属性,但必须是常量,默认是public。

接口中的方法必须是public,默认就是public,你想想,你接口就是给别人用的,你不公开那不是闲的没事嘛。

一个接口不能继承其他的类,但是可以继承别的接口。

语法:

interface 接口名 extends 接口1,接口2{

}

final关键字

如果我们希望某个类不被其他类继承,我们可以使用final关键字来修饰这个类。

如果我们用final来修饰某个类中的方法,则这个方法无法被重写。

final不能用来修饰成员属性。

const概念

当不希望一个成员变量被修改,希望该变量的值是固定不变的,这时候可以用const来修饰该成员变量。

基本用法:

const 常量名=值;

访问:

类名::常量名或者接口名::常量名

常量名应该全部大写,并且前面不要有$

PHP如何对错误进行处理

如果我们尝试打开一个文件:

<?php
$fp=fopen("123.txt","r");
echo '<br/>继续执行';
?>

上面这个代码,打开文件没有做任何验证,这是不对的。

系统会给一个默认警告:

Warning: fopen(123.txt): failed to open stream: No such file or directory in D:\phpstudy_pro\WWW\PHP\error.php on line 2

因为你不知道文件到底在不在,应该先判断。所以将上面的代码进行修改。

<?php
/*$fp=fopen("123.txt","r");
echo '<br/>继续执行';*/
if (!file_exists("abc.txt")){
echo "文件不存在!!";
exit();
}else{
$fp=fopen("abc.txt","r");
echo "文件打开成功";
fclose($fp); //这个必须有!!! }
?>

输出结果:

文件不存在!!

还有一种简单得处理错误得方式

<?php
if (!file_exists("abc.txt")){
die("文件不存在!");
}else{
//文件处理。。。。
}
?>

或者直接:

file_exists("abc.txt") or die("文件不存在!!!!");
#文件存在向下执行,不存在得话执行die()

小结

面向对象基本语法就上面那些,适合入门,希望对大家有所帮助。

PHP基础之面向对象篇的更多相关文章

  1. Python基础复习面向对象篇

    目录 类与对象的概念 实例方法 实例变量 初始化方法 析构方法 常用内置方法 继承 类方法与静态方法 动态扩展类与实例 @property装饰器 概述 面向对象是当前流行的程序设计方法,其以人类习惯的 ...

  2. (转)Python成长之路【第九篇】:Python基础之面向对象

    一.三大编程范式 正本清源一:有人说,函数式编程就是用函数编程-->错误1 编程范式即编程的方法论,标识一种编程风格 大家学习了基本的Python语法后,大家就可以写Python代码了,然后每个 ...

  3. 老王Python培训视频教程(价值500元)【基础进阶项目篇 – 完整版】

    老王Python培训视频教程(价值500元)[基础进阶项目篇 – 完整版] 教学大纲python基础篇1-25课时1.虚拟机安装ubuntu开发环境,第一个程序:hello python! (配置开发 ...

  4. Java基础-初识面向对象编程(Object-Oriented-Programming)

    Java基础-初识面向对象编程(Object-Oriented-Programming) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Java是一门面向对象的程序设计语言.那么什 ...

  5. [.net 面向对象编程基础] (11) 面向对象三大特性——封装

    [.net 面向对象编程基础] (11) 面向对象三大特性——封装 我们的课题是面向对象编程,前面主要介绍了面向对象的基础知识,而从这里开始才是面向对象的核心部分,即 面向对象的三大特性:封装.继承. ...

  6. [.net 面向对象编程基础] (12) 面向对象三大特性——继承

    [.net 面向对象编程基础] (12) 面向对象三大特性——继承 上节我们说了面向对象的三大特性之一的封装,解决了将对同一对象所能操作的所有信息放在一起,实现统一对外调用,实现了同一对象的复用,降低 ...

  7. [.net 面向对象编程基础] (13) 面向对象三大特性——多态

    [.net 面向对象编程基础] (13) 面向对象三大特性——多态 前面两节,我们了解了面向对象的的封装和继承特性,面向对象还有一大特性就是多态.比起前面的封装和继承,多态这个概念不是那么好理解.我们 ...

  8. Linux shell脚本编程基础之练习篇

    shell脚本编程基础之练习篇. 1.编写一个脚本使我们在写一个脚本时自动生成”#!/bin/bash”这一行和注释信息. #!/bin/bash ] then echo "请输入一个参数& ...

  9. shell基础二十篇 一些笔记

    shell基础二十篇 转自 http://bbs.chinaunix.net/thread-452942-1-1.html 研讨:Bash 内建命令 read (read命令更具体的说明见博客收藏的一 ...

随机推荐

  1. DataNode(面试开发重点)

    1 DataNode工作机制 DataNode工作机制,如图所示. 1)一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和 ...

  2. 第4篇 Scrum 冲刺博客(专✌️团队)

    一.站立式会议 1.1会议图片 1.2成员完成情况 成员 昨天完成的任务 今天计划完成的任务 工作中的困难 陈忠明 按下载热度返回歌曲信息,与前端尝试交互 歌曲信息的上传/下载包 前后端交互问题 吴茂 ...

  3. 消息型中间件之RabbitMQ基础使用

    1.概念 RabbitMQ是AMQP(高级消息队列协议)协议的实现主要功能用于分布式应用当中的各组件间解耦.在传统C/S架构中,如果客户端发送一个请求消息,服务端必须得在线,有了中间件,客户端不是非得 ...

  4. shazidouhuiapp

    在选择了软件工程专业之后,指导教师也让我们参加到了学长学姐的作业之中来,使用学长学姐们的软件并写出自己的使用评价以及自己的一些小评价. 我这次体验的是第三组的学长学姐们的软件,他们的队名叫天公疼憨仔, ...

  5. Vue最全知识点

    声明:本篇文章纯属笔记性文章,非整体原创,是对vue知识的整理, 基础篇 说说你对MVVM的理解 Model-View-ViewModel的缩写,Model代表数据模型,View代表UI组件,View ...

  6. AQI分析

    A Q I  分 析 1.背景信息 AOI( Air Quality Index),指空气质量指数,用来衡量空气清洁或污染的程度.值越小,表示空气质量越好.近年来,因为环境问题,空气质量也越来越受到人 ...

  7. Mac系统下php.ini的位置

    http://blog.csdn.net/meegomeego/article/details/25704645 /private/etc/php.ini /usr/local/etc/php/5.5 ...

  8. JAVA 各种锁机制

    可重入锁 可重锁是指同一个线程,外层函数获取锁后,内层函数可以自动获取到锁. java中synchronized和ReentrantLock都是可重入锁. 对于synchronized,其实现机制有j ...

  9. 2020年的UWP——通过Radio类控制Cellular(1)

    最近在做UWP的项目,在2020年相信这已经是相对小众的技术了,但是在学习的过程中,发现某软这么几年仍然添加了不少的API,开放了相当多的权限.所以打算总结一下最近的一些经验和收获,介绍一下2020年 ...

  10. 1DadaFrame和Series创建

    通过GroupBy创建DF对象 sn_group=data.groupby('SN') purchase_count=sn_group.count().Price average_purchase_p ...