1.


Apache Hive는 Hadoop 기반의 데이터 웨어하우스 시스템으로,
HDFS, S3 등에 저장된 대량의 데이터 대상으로 SQL을 사용할 수 있도록 도와줌

Hive 는 데이터를 따로 저장하는 storage 가 아님, 다른 저장소(HDFS, S3) 에 있는 데이터에 작업을 날려서 실행하는 것임
(근데 그 작업이 쿼리를 실행하는 작업)

쿼리를 실행 할 때는 다음과 같은 요소들을 사용
- Hive Query Engine 에서 HiveQL로 작성된 쿼리를 실행 계획으로 변환하고 실행
- Execution Engine Hive 에서 쿼리를 실제 실행 (MapReduce, Tez, Spark 등의 엔진 사용)

Hive 가 HDFS 내의 데이터를 읽으려면
HDFS 내의 데이터가 규칙적인 path(by partitioning) 및 파일 포맷(Orc, Parquet, Text 등)을 갖고 있어야 함
HDFS 의 데이터에 대한 메타 정보(데이터베이스, 테이블, 컬럼, 파티션, 파일 포맷, 데이터 위치 등)는
Hive Metastore 에 구조화된 형태로 저장됨
테이블 정의와 파티션 정보도 메타스토어에 저장됨.
테이블이 HDFS 내의 어느 디렉토리에 저장되어 있는지, 각 파티션이 어떤 경로에 위치하는지 등의 정보가 메타스토어에 기록됨

create external table 을 사용하여 테이블을 만들고 location 을 설정했다면,
사용자가 hive 를 통하지 않고 직접 HDFS 에 데이터를 넣었어도 Hive 에서 해당 location 에 접근해서
(create external table 실행할 때 미리 생성해둔 ) metadata를 기반으로 데이터를 읽을 수 있음(schema-on-read)
다시 말해, 테이블이 Hive Metastore에 존재하면, 해당 테이블의 메타데이터를 통해 HDFS에서 실제 데이터를 찾을 수 있기 때문에
사용자가 직접 데이터를 HDFS 에 저장했더라도 쿼리가 가능함

Hive 에서 처리하는 "데이터베이스", "테이블" 이라는 개념은, HDFS 데이터를 구분하고 관리하기 위한 논리적인 개념임
(실제 HDFS 에 "데이터베이스", "테이블"이 있는 것이 아님)
실제 HDFS 의 directory 가 Hive 에서 바라볼 때 "데이터베이스"가 됨
그리고 데이터베이스 dir 아래 위치한 dir 혹은 파일이 "테이블"이 됨

Hive는 배치 처리 시스템이기 때문에, 실시간 데이터 분석에는 부적합함.
Presto 같은 실시간 쿼리 엔진 사용하면 됨
그리고 Hive는 기본적으로 Append-only 모델이기 때문에, 데이터를 삭제하거나 업데이트하는 것이 어려움.
ACID Transactions(Hive 3.0 이상) 또는 Hudi, Iceberg 같은 테이블 포맷 사용하여 해결



하이브에서 작성된 쿼리는 일련의 job(MR, spark, etc)으로 변환되어 실행됨

하이브는 HDFS 에 저장된 데이터에 스키마를 입히는 방식으로 데이터를 테이블로 구조화 (읽기 스키마)

테이블 스키마, 파티션 같은 메타데이터를 메타스토어라 불리는 DB(MySQL, derby 등) 에 저장
메타스토어가 로컬 머신에 있으면, 한번에 한명의 유저만 사용 가능(derby DB를 사용하지 말아야 하는 이유)
실제 운영 환경에선 공유 원격 메타스토어를 사용

Hive 는 HADOOP_HOME 환경변수가 설정되어 있다면 HDFS 사용 가능


'hive' 명령어를 사용하면, 사용자 머신에 메타스토어 데이터베이스를 만듦
hive 명령어 실행한 위치에 metastore_db 라는 디렉터리 만들어 그 안에 필요한 파일 저장

local 데이터를 읽고 local 에 저장할 수 있음
저장시 데이터를 변경하지 않고 그대로 저장
local 에 저장할 때 하이브가 저장하는 경로는 웨어하우스 디렉터리(기본값 /user/hive/warehouse)

하이브는 특정 테이블 질의할 때 모든 파일을 다 읽음
그래서 효율적인 쿼리를 위해 버켓팅, 파티셔닝이 필요



하이브 속성 설정 우선순위
1. hive> 에 넣는 SET 명령어
2. 명령어에 넣는 -hiveconf 옵션
3. *-site.xml
4. *-default.xml



Hive 쿼리 실행 엔진은 MR, Tez, Spark 를 지원


 

2.


하이브 서비스
- cli : hive 드라이버에 바로 접근. 접근 권한 확인 하지 않아서 되도록 hiveserver2 사용
- hiveserver2 : 다른 언어로 개발된 클라이언트와 연동 가능하도록 하이브 쓰리프트 서비스 실행
  hiveserver2 로 쓰리프트 서비스가 실행되고,
  이를 통해 다른 언어에서 thrift, JDBC, ODBC 연결자로 하이브에 연결 가능
  접근 권한 확인 등을 hiveserver2 에서 진행
- beeline : JDBC 로 hiveserver2 프로세스에 접근하는 명령행 인터페이스
- hwi : 하이브 웹 인터페이스
- 메타스토어 : 기본적으로 메타스토어는 하이브 서비스와 동일한 프로세스에서 실행됨
  이 서비스는 메타스토어를 독립형 원격 프로세스로 실행함

 

 

3.


메타스토어는 하이브 메타데이터를 저장하는 핵심 저장소
메타스토어는 서비스데이터 보관 저장소로 나뉨

내장 메타스토어
- 메타스토어 서비스 : 하이브 서비스와 동일한 JVM 내에서 동작
- 메타스토어 저장소 : 하이브 서비스와 동일한 JVM 내에서 동작. derby
  local 에 데이터 저장하는 derby 데이터베이스
  derby 는 한 번에 db파일 하나에만 접근 가능해서 하나의 하이브 세션만 사용 가능
  (다른 사용자 등에 의해) 두 번째 세션 사용 불가
  따라서 다중 세션(다중 사용자) 지원 불가

로컬 메타스토어
- 메타스토어 서비스 : 하이브 서비스와 동일한 JVM 내에서 동작
- 메타스토어 저장소 : 원격 머신에서 별도의 프로세스로 실행되는 데이터베이스. mysql, postgresql etc
  다중 세션(다중 사용자) 지원 가능
  mysql, postgresql 등을 원격 데이터베이스로 사용

원격 메타스토어
- 메타스토어 서비스 : 하나 이상의 메타스토어 서버가 하이브 서비스와 별도의 JVM 프로세스로 실행
- 메타스토어 저장소 : 원격 머신에서 별도의 프로세스로 실행되는 데이터베이스. mysql, postgresql etc
  클라이언트와 메타스토어 서버는 thrift로 통신함
  데이터베이스 계층이 방화벽 역할을 대신하기 때문에
  클라이언트는 데이터베이스 자격 증명을 얻을 필요가 없어서 관리성과 보안성이 높아진다는데
  무슨 말인지 모르겠음
  메타스토어 서비스가 별도의 서버에서 실행되면, 가용성과 확장성이 제공됨


 

 

4.


rdb 와 hive 차이
- rdb 는 쓰기 스키마, hive 는 읽기 스키마
  쓰기 스키마는 index 를 지원해서 쿼리가 빠르지만,
  읽기 스키마는 디스크 직렬화가 필요없어서 데이터 write 가 매우 빠름
- rdb 의 트랜잭션, 색인 기능은 hive 에서 일부 지원
  트랜잭션(update)이 활성화된 hive 는 update 가 가능하지만,
  실제 테이블 내 데이터가 업데이트를 하는 건 아니update 내역을 별도의 작은 델타 파일로 저장
- hive 의 insert into : 하이브 테이블, 파티셔닝 된 테이블 내에 데이터를 추가하며 insert 함
  insert overwrite : 하이브 테이블, 파티셔닝 된 테이블 내의 데이터를 모두 지우고 insert 함
- 데이터를 읽을 때 SHARED_READ lock 을 얻는데,
  해당 lock 이 존재하면 다른 사용자가 읽기 가능, update 불가능
- 데이터를 update 할 때 EXCLUSIVE lock 을 얻는데,
  해당 lock 이 존재하면 다른 사용자가 읽기/update 가 불가능

hive 가 지원하는 색인
- 콤팩트 색인 : HDFS 블록 넘버로 색인
- 비트맵 색인 : 특정 값이 출현하는 행을 효율적으로 저장하기 위해 압축된 비트셋으로 색인
색인을 위한 데이터는 별도의 테이블에 저장됨

 

6.


하이브 테이블의 데이터 저장소는 local disk, s3, HDFS 등 다양하게 사용 가능

관리 테이블 : 하이브가 데이터를 직접 관리
  직접 관리한다고 해도, 데이터는 여전히 HDFS 등 외부 저장소에 있음
  직접 관리한다는 의미는, 테이블이 삭제되었을 때 데이터가 실제로 삭제된다는 의미임
  load 쿼리 사용시, 해당 데이터가 웨어하우스 디렉터리(local, HDFS 등)으로 이동
  drop table 쿼리 사용시, 해당 데이터와 메타데이터가 실제로 삭제

외부 테이블 : 하이브가 데이터를 직접 관리하지 않음
  drop 쿼리 사용시, 메타데이터만 삭제되고 데이터는 삭제되지 않음


파티션 : 데이터를 각 dir 에 나눠 저장. PARTITIONED BY
  year=2024/month=01/ 같은 구조로 HDFS에 저장됨.
버킷 : 지정한 컬럼값을 해쉬 처리 한 후, 버킷수로 나눈 나머지만큼 파일로 나눠 저장. dir 가 아닌 파일에 저장. CLUSTERED BY

버킷을 사용하는 이유
- 매우 효율적인 쿼리가 가능
  테이블에 대한 추가 구조를 부여하게 되고, 쿼리 수행 시 이 추가 구조 사용 가능
- 효율적인 샘플링에 유리
- 버켓팅한 테이블은 조인시에 SMB(sort merge bucket) 조인으로 처리하여 속도 향상



row format : 행 구분자 설정, 특정 행의 필드가 저장된 방식 설정

- 지정가능한 구분자 
  FIELDS TERMINATED BY '\t'            -- 칼럼을 구분하는 기준
  COLLECTION ITEMS TERMINATED BY ','   -- 리스트를 구분하는 기준
  MAP KEYS TERMINATED BY '='           -- 맵데이터의 키와 밸류를 구분하는 기준
  LINES TERMINATED BY '\n'             -- 로(row)를 구분하는 기준
  ESCAPED BY '\\'                      -- 값을 입력하지 않음
  NULL DEFINED AS 'null'               -- null 값을 표현(0.13 버전에서 추가)

- 특정 행의 필드 저장 방식 : 데이터 저장시 SerDe 를 통해 직렬화하여 저장하고 읽을 때 역직렬화하여 읽나 봄
기본서데, 정규식(RegExSerDe), JSON(JsonSerDe), CSV(OpenCSVSerde)가 존재함

stored as : 데이터를 저장하는 파일 포맷 지정
저장 포맷은 TEXTFILE, SEQUENCEFILE, ORC, PARQUET 등이 존재
바이너리인 sequence, orc, parquet 등은 행의 형식이 바이너리 포맷에 따라 결정되므로 row format 지정 불필요

참고 https://wikidocs.net/23469



hive 는 읽기 스키마를 사용하기 때문에
테이블의 이름 변경, 테이블의 정의 변경, 새로운 컬럼 추가 등이 자유로움


 

셔플 조인은 가장 느린 조인

맵(Map) 단계에서 각 테이블을 읽고, 조인 컬럼을 키로 설정하여 셔플

리듀서로 데이터가 이동되고 리듀서에서 테이블을 조인

 

버켓팅 되어있으면 Bucket Map Join 이 빨라짐.

https://data-flair.training/blogs/bucket-map-join/

join 의 기준이 되는 key 들이 모두 버케팅 되어있는 상황에서 Join 을 진행하면,

작은 테이블의 버킷들(Table a, Table c)이 큰 테이블의 버킷(Table b)의 메모리에 모두 복사됨

이렇게되면 join 에 필요한 모든 key 가 하나의 Mapper 에서 접근 가능하기 때문에 join 속도 향상

작은 테이블 크기가 메모리에 올라갈 정도로 작아야 함

브로드캐스트 조인임.

 

Sort Merge Join 은 조인 테이블이 버켓팅 되어 있을 때 사용 가능

버켓팅된 키의 정보를 이용하여 빠르게 조인

다음 절차로 join 이 진행됨

- Table a 와 Table b 에서 join 에 필요한 데이터를 읽음

- 별도의 공간에서 읽은 데이터를 정렬sort 함

- 정렬된 데이터를 기준으로 join 함

 

참고 : https://coding-factory.tistory.com/757

 

 


hive 명령어와 beeline 명령어 차이?

- hive 명령어는 하이브 옵션 설정, 쿼리 실행 등이 가능

- beeline 은 단지 hive 에 thrift 로 접근하는 인터페이스

 

thrift : 다양한 언어로 개발된 소프트웨어들을 쉽게 결합(통신)하는 프레임워크. 내부에서 RPC 사용

JDBC :

- Java Database Connectivity

- JAVA 언어로 DB 에 접근해 DML 쿼리 하기 위한 인터페이스

- Java와 연동되는 DBMS에 따라 그에 맞는 JDBC를 설치할 필요가 있음

ODBC

- Open Database Connectivity

- 응용프로그램에서 다양한 DB 에 접근해 DML 쿼리하기 위한 인터페이스

- 접속처의 데이터베이스가 어떠한 DBMS에 의해 관리되고 있는지 의식할 필요가 없음

 

hive 에서 orc 를 사용하는 이유

- 높은 압축률

- 컬럼 기반 포맷이라 처리 속도가 빠름

- 스키마를 가지고 있음

- orc 는 hive 에 최적화되어있고, parquet 은 spark 에 최적화 되어있음

 

 

 

ORDER BY vs DISTRIBUTE BY vs SORT BY vs CLUSTER BY

 

ORDER BY

- 매퍼에서 나오는 모든 데이터를 하나의 리듀서로 몰아서 정렬 수행

- 리듀서가 하나 뿐이라, 저장되는 파일도 하나

- limit 을 추가하여 리듀서에 부하를 줄이는 게 좋음

- order by COLUMN

 

SORT BY

- ORDER BY 와 다르게, 리듀서가 하나 이상, 각 리듀서에서 정렬 수행

- 각 리듀서별로 정렬하기 때문에, 모든 리듀서 결과는 전체 정렬 되어있지 않음

- 리듀서 개수를 1로 설정하면 ORDER BY 와 같은 결과

- sort by COLUMN

 

DISTRIBUTE BY

- distributed by 의 대상이 되는 컬럼의 값 기준으로 group 지어 하나의 리듀서에 들어감

정렬 수행하지 않음

- 예)

  정렬 대상 : a a b c c c d

  리듀서 1) a d a

  리듀서 2) c b c c

  (리듀서 개수와 상관 없이) 같은 값은 모두 하나의 리듀서에 몰려 있음. 

 

CLUSTER BY : 

 

- distributed by 와 sort by 를 같이 사용

- 즉, distributed by 실행하며 정렬까지 진행

- 예)

  정렬 대상 : a a b c c c d

  리듀서 1) a a d

  리듀서 2) b c c c

 

참고) https://saurzcode.in/2015/01/hive-sort-order-distribute-cluster/

 

 

 

Hive 정적 파티션 vs 동적 파티션

 

정적 파티션

- 데이터의 테이블에 파티션 값을 직접 입력

  예)

  INSERT INTO TABLE tbl(yymmdd='20220625')
  SELECT name FROM temp;

  > hdfs://hive/tables/yymmdd=20220625/

 

동적 파티션

- 데이터의 컬럼값을 기준으로 파티션이 생성

- 쿼리 시점에 어떤 데이터가 어떤 파티션에 가는지 모름

- 동적 파티션은 느림

  예)

  INSERT INTO TABLE tbl(yymmdd)
  SELECT name, yymmdd FROM temp;

  > hdfs://hive/tables/yymmdd=20220625/

  > hdfs://hive/tables/yymmdd=20220626/

  > hdfs://hive/tables/yymmdd=__HIVE_DEFAULT_PARTITION__/

 

 

 

HDFS 에서 작은 파일들 합치기

https://gyuhoonk.github.io/hive-merge-query

 

결론

작은 파티션들이 많으면, HDFS IO 도 많아지고 NN memory 에 부담도 커짐

Hive

- insert into 등의 쿼리는 매퍼만 동작하는데,

  매퍼에서 읽은 데이터를 그대로 HDFS 블럭으로 저장하기 때문에 블럭 개수가 늘어남

  이를 합쳐주기 위해 (sort by 등을 추가하여) 리듀서를 추가

Spark

- 셔플 파티션은 기본값이 200 이라서, 셔플 후에는 200개의 작은 파티션들이 생성되어 HDFS 에 저장됨

  이를 합쳐주기 위해 셔플 파티션 값을 조정하거나, repartition 혹은 coalesce 를 사용하여 파티션 개수 줄임

 

 

 

< Presto >


Apache Presto 는 Hive 와 마찬가지로, 대규모 데이터에 대한 SQL 기반 쿼리 처리 시스템
Hive 처럼 외부 스토리지(HDFS, S3)에 저장된 데이터 대상으로 쿼리를 사용할 수 있도록 도와줌

하지만 Hive와 다른 목적과 작동 방식을 갖음

- 목적
  - Hive : batch 쿼리 처리
  - Presto : realtime 분석 쿼리 처리
- 실행 엔진
  - Hive : MR, Tez, Spark
  - Presto : 자체 엔진(MPP:massively parallel processing)
- 처리 방식
  - Hive : 디스크 기반
  - Presto : in-memory 기반
- 속도
  - Hive : 대용량 데이터 처리하므로 느림
  - Presto : 빠름 (...)
- 데이터 소스
  - Hive : HDFS, ORC, Parquet
  - Presto : HDFS, ORC, Parquet, MySQL, Kafka, Cassandra 등 다양하게 지원
- 사용 사례
  - Hive : 대량 데이터 ETL, 데이터 웨어하우스에서 OLAP 에 사용
  - Presto : BI, 실시간 대시보드, 쿼리 성능이 중요한 환경, 대화형 분석 환경 제공

Presto 로 대용량 데이터 ETL 혹은 배치 처리하기는 부적합함.
이런 경우는 Hive 를 사용하고, 실시간 분석 처리할 때 Presto 사용

Hive 와 Presto 를 혼합하여 사용하기도 함
이를테면, 대량의 데이터를 HDFS 에 적재할 때는 Hive 를 사용하고,
데이터를 조회할 때는 Presto 를 사용하는 식.

 

 

< Iceberg >


Apache Iceberg 는 대규모 데이터 레이크에서 테이블을 효율적으로 관리하기 위해 설계된 테이블 포맷
HDFS, S3 등 다양한 스토리지에서 대량의 데이터를 안정적으로 관리하고 빠르게 쿼리할 수 있도록 최적화 됨



Iceberg 의 장점은 다음과 같음
- 대용량 데이터(수십~수백 페타바이트) 처리 최적화
- ACID 트랜잭션 지원 (데이터 일관성 보장)
  (ACID : 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability) )
- Schema Evolution 지원 (스키마 변경 가능)
- Partition Evolution 지원 (파티셔닝 변경 가능)
- Time Travel 지원 (과거 데이터 조회 가능)
- 다양한 엔진(Hive, Spark, Trino, Flink, Presto)과 호환




 

 

https://spark.apache.org/docs/latest/index.html

 

-  pandas API on Spark for pandas workloads

 

- Downloads are pre-packaged for a handful of popular Hadoop versions

 

- Spark runs on both Windows and UNIX-like systems, and it should run on any platform that runs a supported version of Java

 

- it is necessary for applications to use the same version of Scala that Spark was compiled for

For example, when using Scala 2.13, use Spark compiled for 2.13

 

- use this class in the top-level Spark directory.

 

- with this approach, each appliction is given a maximum amount of resources it can use

and holds onto them for its whole duration.

 

- Resource allocation can be configured as follows, based on the cluster type.

 

- At a high level, Spark should relinquish executors when they are no longer used and acquire when  they are needed.

 

- We need a set of heuristics to determine when to remove and request executors.

 

- By default, Spark's scheduler runs jobs in FIFO fashion.

 

- If the jobs at the head of the queue don't need to use the whole cluster, 

later jobs can start torun right away, but if the jobs at the head of the queue are large,

then ddlater jobs may be delayed significantly.

 

- Under fair sharing, Spark assigns tasks between jobs in a "round robin" fashion,

so that all jobs get a roughly equal share of cluster resources.

 

- This feature is disabled by default and available on all coarse-grained cluster managers.

 

- Without any intervention, newly submitted jobs go into a default pool

 

- This is done as follows

 

- This setting is per-thread to make it easy to have a  thread run multiple jobs on behalf of the same user.

 

- If you would like to clear the pool that a thread is associated with, simply call this.

 

- jobs run in FIFO order.

 

- each user's queries will run in order instead of later queries taking resources from that user's earlier ones.

 

- At a high level, every Spark application consists of a driver program that runs the user's main function and executes various parallel opperations on a cluster.

 

- ...the cluster that can be operated on in parallel.

 

- This guide shows each of these features in each of Spark's supported languages.

 

- it's easiest to follow along with if you launch Spark's interactive shell.

 

 

 

 

 

 

 

 

 

 

 

- It is not only Value but also Pointer, both of these together make up the node.

 

- We do it by just having the next value of A node be the B node.

 

- the same is true of the C node.

 

- if you look at how we're going to have to traverse this, we are going to have to start at head.

 

- that's what we are going to do down here with this print statement.

 

- the syntac is a little bit different than if you are going to use dictionaries.

 

 

 

 

 

 

 

 

 

 

 

 

 

'English' 카테고리의 다른 글

Study English 24.07.03-05  (0) 2024.07.06
Study English 24.06.29-07.02  (0) 2024.07.02
Study English 24.06.28  (0) 2024.06.29
Study English 24.06.27  (0) 2024.06.27
Study English 24.06.26  (0) 2024.06.26

 

https://eyeballs.tistory.com/648

 

[IT] CS 면접 대비 Python 질문 모음

< First-Class 함수 > First-Class 함수 : 프로그래밍 언어가 함수(Function)를 first-class 시민으로 취급하는 것 함수가 다른 함수의 인자로 전달될 수 있고, 함수의 결과로 리턴될 수 있고, 변수에 함수를 할

eyeballs.tistory.com

 

 


python3 : print("hi eyeballs!")
python2 : print "hi eyeballs!"

 


python 은 동적 언어(dynamic language) 이기 때문에
변수를 생성할 때 타입을 직접 작성하지 않음
또한, 변수(데이터 값)는 객체이며
객체는 내부에 타입 정보, 실제 값, 객체 ID, 참조 횟수 등을 갖고 있음
그래서 type(123) 등으로 타입을 확인할 수 있는 것임


타입을 확인하기 위해 아래 메소드를 사용 가능

type(1)  # <class 'int' >
isinstance(1, int)  # True
isinstance("hi!", int)  # False

 


python은 강타입(strong type) 언어임
즉, 객체의 값 변경은 가능하지만, 객체의 타입은 변경할 수 없음

 


python 에서 변수는 객체를 가리키는 이름임
다른 정적 언어(static language) 들은 변수 자체에 타입이 있기 때문에,
변수에 값을 할당 할 때부터 타입을 지정해줘야 하지만
python 은 동적 언어이기 때문에, 변수 자체에 타입이 없고 
변수에 값을 할당 할 때 타입 지정이 필요 없음


a = 1
b = a
print(a)  # 1
id(a)  #9440320
print(b)  # 1
id(b)  #9440320

a = 2
print(a)  # 2
id(a)  # 9440355
print(b)  # 1
id(b)  # 9440320

여기서 id 값이 달라짐
왜냐면 a = 2 를 통해 a에 다른 불변 객체(2)를 바라보도록 할당했기 때문
b는 여전히 기존 불변 객체(1)를 바라보고 있음


a = [1,2,3]
b = a
print(a)  # [1,2,3]
print(b)  # [1,2,3]

a[0] = 99
print(a)  # [99,2,3]
print(b)  # [99,2,3]

반대로 여기선 b 가 a와 동일하게 업데이트 되었음
왜냐면 list 는 가변 값의 배열이기 때문

하지만 list 객체 자체는 불변임
만약 a를 새로 할당했다면, b 는 바뀌지 않았을 것

a = [1,2,3]
b = a
a = [2,3,4]
print(a)  # [2,3,4]
print(b)  # [1,2,3]


a = "!"
b = "!"
id(a)  #21926656
id(b)  # 21926656

이렇게 자주 사용된다 싶은 객체(여기선 "!" 를 담고 있는 객체)는 파이썬이 따로 저장해 둠


def func(p) : ...
func(1)
func([1,2,3])
func("eyeballs")

함수 파라미터에 들어가는 정보는 "변수의 참조값"임
def func(p) 에서 p 는 참조값을 넘겨받은 변수가 됨
이를 Call by Object Reference 라고 부름



 


0이나 empty 값, None 아닌 값은 True 로 간주함

bool(True)  #True
bool(1)  #True
bool(-1)  #True
bool(0)  #False
bool(0.0)  #False
bool("")  #False
bool(None)  #False
bool(Set())  #False
bool({})  #False
bool(())  #False
bool([])  #False

 


숫자를 표현할 때 세 자릿수를 underbar 를 이용하여 표현 가능
million = 1_000_000
print(million)  # 1000000

물론 꼭 위와 같이 쓰지 않아도, 숫자 사이에 어느 곳에나 넣을 수 있음
a = 1_2_3
print(a)  # 123

 


print(1/2)  # 0.5
print(1//2)  # 0. 소수점 이하 버려짐
print(1%2)  # 1. 나머지를 반환

 


print(chr(65))  # 'A'
print(ord('A'))  # 65

 


int 는 굉장히 큰 값도 들어감. 심지어 10의 100제곱(googol)도 들어감

print(int(10**100))  # 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

 


a = b = c = 1
print(a)  # 1
print(b)  # 1
print(c)  # 1


print(1<2<3)  # True


a = 1
print(a == 1)  # True
print(a != 1)  # False
print((not a) == 1)  # False

 


문자열, 배열 등 내부 값을 갖는 객체에서 어떤 값이 포함되어있는지 확인하려면 in 을 사용

print('a' in "abcde")  # True
print("abc" in "abcde")  # True
print('a' in ('a','b','c'))  # True
print('a' in {'a':'a', 'b':'b'})  # True

 


:= 는 바다코끼리 연산자라고 불림...
이것은 코드 실행실행 결과 할당을 한 번에 처리할 수 있게 도와줌

이를테면 아래 두 코드는 동일한 결과를 출력함

diff = 2 - 1
if diff >= 0 :
    print("+")
else :
    print("-")

출력 결과 : +

if diff := 2-1 >= 0 :
    print("+")
else :
    print("-")

 


탈출 문자(Escape character) 를 무효화하려면 r 포맷팅을 사용

print(r"a\nb\tc\\d\e")  # a\nb\tc\\d\e


문자열을 연결할 때 + 를 사용 할 필요 없음

print("a" "b" "c")  # "abc"



print("aba".replace('a', 'x'))  # xbx
print("abcdefg"[::2])  # aceg. 2 개씩 건너뛰면서 슬라이싱
print("abcdefg"[::-1])  # gfedcba. -1 개씩 건너뛰며 슬라이싱. 결과적으로, 뒤에서부터 읽게 되어 문자열이 reverse 됨



문자열 검색에 사용되는 메소드는 두 가지
find() : 처음부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 -1 을 반환
index() : 처음부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 에러
rfind() : 끝에서부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 -1 을 반환
rindex() : 끝에서 부터 문자열을 찾으면 오프셋(index)를 반환, 못 찾으면 에러

"abcba".find("b")  # 1
"abcba".find("x")  # -1
"abcba".rfind("b")  # 3



문자열이 알파벳이나 숫자로 이루어져있는지 확인 가능

"123".isnumeric()  #True. 숫자로만 이루어져 있음
"123".isdigit()  #True. 숫자로만 이루어져 있음
"abc".isalpha()  # True. 문자로만 이루어져 있음
"abc123".isalnum()  # True. 숫자와 문자로만 이루어져 있음



"..a..".strip('.')  # 'a'
"..a..".lstrip('.')  # 'a..'
"..a..".rstrip('.')  # '..a'



format 사용 예제

"a{}{}d".format("b","c")  # 'abcd'
"a{1}{0}d".format("b","c")  # 'acbd'
"a{x}{y}d".format(x="b", y="c")  # 'abcd'



f-문자열 사용 예제

a = "a"
b = "b"
f"x{a}{b}y"  # "xaby"
f"x{a=}{b=}y"  # "xa='a'b='b'y"


 


break 문이 포함된 loop 문에서 break 가 실행되지 않으면 실행되는 무언가를 넣어야 할 때 else 를 사용함



while index < len([0,1,2,3]):
    index += 1
    if index > 5 : break
else : print("no break")

출력 결과 : no break



for n in (0,1,2) :
    if n < 0 : break
else : print("no break")

출력 결과 : no break



위와 같이 else 를 break checker 로 사용 가능


 


(1,2,3) ['a', 'b', 'c'] 를 각 index 별로 묶어서
[(1, 'a'), (2, 'b'), (3, 'c')] 로 만드는 기능은 zip 메소드를 통해 가능함

a = (1,2,3)
b = ['a', 'b', 'c']
z = zip(a, b)

type(z)  # <class 'zip' >

print(list(z))  # [(1, 'a'), (2, 'b'), (3, 'c')]

for x, y in zip(a,b) :
    print(x+", "+y)

출력 결과 : 
1, a
2, b
3, c


>>> a = (1,2,3)
>>> b = ['a','b','c']
>>> c = ([], (), {})
>>> z = zip(a,b,c)
>>> print(list(z))
[(1, 'a', []), (2, 'b', ()), (3, 'c', {})]



>>> a = (1,2,3)
>>> b = ['a','b','c']
>>> print( dict(zip(a,b)) )
{1: 'a', 2: 'b', 3: 'c'}



a = (1,2,3)  # a 는 3개
b = ['a']  # b 는 1개. a 보다 2개 적음
z = zip(a, b)

print(list(z))  # [(1, 'a')]. 가장 적은 개수를 갖는 b 에 따라 zip 결과가 정해짐


zip 메소드로 나온 결과를 어떤 방식으로든 한 번 사용하면
그 뒤로는 empty 값이 나와버림

z = zip(a,b)
print(list(z))  # [(1, 'a'), (2, 'b'), (3, 'c')]
print(list(z))  # [] 바로 윗 줄에서 사용했기 때문에 z 에 빈 값이 들어감


 


튜플 만드는 법

t = ()
t = "eyeballs",
t = ("eyeballs",)
t = 1, 2, 3
t = (1, 2, 3)
t = tuple([1,2,3])
t = (1, 2) + (3, 4)
t += t


튜플로 여러 변수에 값을 넣어줄 수 있음

a, b, c = (1,2,3)
a, b, c = 1, 2, 3
a, b = b, a



named tuple 이란 것이 있음
이름과 위치로 값에 접근 가능한 자료구조임
튜플의 서브클래스이며, collections 모듈을 통해 사용 가능

이름이 있는 필드를 가진 불변(immutable) 객체를 만들어 사용한다고 생각하면 됨
튜플처럼 동작하면서도 필드에 이름을 부여할 수 있어 가독성이 뛰어나고 코드 유지보수성이 좋아짐


from collections import namedtuple
>>> Person = namedtuple("Person", ["name", "age"])
>>> p1 = Person(name = "A", age = 30)  # 속성은 두 가지 뿐이지만, 불변의 dict 같은 객체를 만들 수 있음
>>> p2 = Person(name = "B", age = 60)
>>> p1[0], p1[1]
('A', 30)
>>> p2.name, p2.age
('B', 60)

>>> p3 = Person._make(['C', 90])  # _make 를 사용하여, 리스트를 namedtuple 에 바로 넣음
>>> p3[0], p3[1]
('C', 90)

>>> type(p3._asdict())  # _asdict 를 사용하여, namedtuple 을 dictionary 로 변경
<class 'dict'>
>>> print(p3._asdict())
{'name': 'C', 'age': 90}

>>> p4 = p3._replace(age=10)  # _replace 를 사용하여, 기존 namedtuple 속성을 수정한 새로운 namtedtuple 객체 생성
>>> print(p4)
Person(name='C', age=10)
>>> id(p3)
68810088
>>> id(p4)
68794984  # 기반이 된 p3 과는 다른, 새로운 객체 p4


이게 왜 tuple 이랑 비슷하게 동작한다는 것인지 모르겠음...
dirtionary 랑 더 비슷해 보임

dictionary 보다 namedtuple 이 더 효율적으로 동작한다고 함




리스트 만드는 법

l = []
l = [1,2,3]
l = list()
l = list('eyeballs')  # ['e','y','e','b','a','l','l','s']
l = list((1,2,3))
l = "a.b.c".split(".")
l += l



append 를 사용하면 메소드의 리스트  인자가 리스트의 마지막 항목에 들어감
extend 를 사용하면 메소드의 리스트 인자가 리스트에 병합됨

>>> a = [1,2,3]
>>> a.append([4,5])
>>> print(a)  # [1, 2, 3, [4, 5]]

>>> a = [1,2,3]
>>> a.extend([4,5])
>>> print(a)  # [1, 2, 3, 4, 5]



list 에서 항목 제거하기

a = [1,2,3]
del a[1]  # 숫자 2를 삭제
print(a)  # [1,3]

a = [1,2,3]
a.remove(2)  # 숫자 2를 삭제
print(a)  # [1,3]


a.del(1) 이런 문법은 아님...



list 에서 항목을 가져옴과 동시에 제거하기

a = [1,2,3,4,5]
n = a.pop()
print(n)  # 5
print(a)  # [1,2,3,4]

a = [1,2,3,4,5]
n = a.pop(0)
print(n)  # 1
print(a)  # [2,3,4,5]

a = [1,2,3,4,5]
n = a.pop(1)
print(n)  # 2
print(a)  # [1,3,4,5]
n = a.pop(1)
print(n)  # 4
print(a)  # [1,4,5]



sort() 는 list 자체 내부 정렬을 진행함
sorted() 는 list 의 정렬된 복사본을 반환

s = [2,5,4,1,3]
result = s.sort()
print(result)  # None
print(s)  # [1,2,3,4,5]

s = [2,5,4,1,3]
result = sorted(s)
print(result)  # [1,2,3,4,5]
print(s)  # [2,5,4,1,3]

내림차순 정렬하려면 인수에 reverse = True 추가

s = [2,5,4,1,3]
result = s.sort(reverse = True)
print(s)  # [5,4,3,2,1]



a = [1,2,3]
b = a

여기서 a 와 b 는 동일한 리스트 객체를 바라보고 있음
a 에서 리스트가 수정되면 b에서도 수정된 객체를 바라봄

a 의 복사본을 b 에 넣고 싶다면 아래와 같은 방법들을 사용

a = [1,2,3]
b = a.copy()
a[0]=-99
print(a)  # [-99, 2, 3]
print(b)  # [1, 2, 3]

a = [1,2,3]
b = a[:]
a[0]=-99
print(a)  # [-99, 2, 3]
print(b)  # [1, 2, 3]

a = [1,2,3]
b = list(a)
a[0]=-99
print(a)  # [-99, 2, 3]
print(b)  # [1, 2, 3]



copy 는 얕은 복사임

a = [1,2,[3,4,5]]
b = a.copy()
a[2][0]=-99
print(a)  # [1, 2, [-99, 4, 5]]
print(b)  # [1, 2, [-99, 4, 5]]
id(a[2])  # 30645800
id(b[2])  # 30645800

내부에 중첩된 list 까지 모두 제대로 복사하려면 deepcopy 를 사용하면 됨

import copy
a = [1, 2, [3, 4, 5]]
b = copy.deepcopy(a)
a[2][0] = -99
print(a)  # [1, 2, [-99, 4, 5]]
print(b)  # [1, 2, [3, 4, 5]]
id(a[2])  # 62359848
id(b[2])  # 30659592



리스트 컴프리헨션을 사용하여, 한 줄로 for 문을 구현할 수 있음

mylist = [i for i in a]
print(mylist)  # [1, 2, 3, 4, 5]

mylist = [i**2 for i in a]
print(mylist)  # [1, 4, 9, 16, 25]

mylist = [i for i in a if i%2==0]
print(mylist)  # [2, 4]



list 보다 tuple 을 사용하는 이유는
- tuple 이 공간을 더 적게 사용함
- tuple 은 한 번 생성되면 내부 값들이 변하지 않기 때문에, 값이 손상될 염려가 없음
- tuple 을 dictionary key 로 사용 가능 (list 는 안 되나보네..?)
- namedtuple  이 객체의 단순한 대안으로 사용 가능함


 


dictionary 만드는 법

d = {}
d = dict()
d = {"a" : 1, 2 : "b"}
d = dict( [ ['a',1], ['b',2], ['c',3] ] )
d = dict( ( ['a',1], ['b',2], ('c',3) ) )



dictionary 에서 값 확인 및 추출하는 법

d['a']  # 만약 'a' 키가 없으면 exception
d.get('a')  # 만약 'a' 키가 없으면 None 반환
d.get('a', 'nothing here')  # 만약 'a' 키가 없으면 'nothing here' 를 반환

'a' in d  # 'a' 가 d 의 key 값이라면 True
if key := 'a' in d:
    print("key : ", key)
    print("value : ", d['a'])

d.keys()  # 모든 키 얻기
d.values()  # 모든 값 얻기
d.items()  # 모든 키값 쌍 얻기



dictionary 합치기

d1 = {1:1}
d2 = {1:1, 2:2}
d3 = {2:2, 3:3}
d = {**d1, **d2}  #{1: 1, 2: 2}
d = {**d1, **d2, **d3}  # {1: 1, 2: 2, 3: 3}

d1 = {1:1}
d2 = {1:1, 2:2}
d1.update(d2)  # 결과값이 나오는 메소드가 아님, 자기 자신의 dictionary 에 추가하는 것
print(d1)  # {1: 1, 2: 2}

d1 = {1:1}
d2 = {1:'a'}
d1.update(d2)
print(d1)  # {1:'a'}.  key 가 동일한 아이템은 update 의 인자(d2) 값으로 업데이트 됨



dictionary 삭제

d = {1:1, 2:2}
del d[1]
print(d)  # {2:2}
del d[-99]  # exception 발생

d = {1:1, 2:2}
a = d.pop(1)
print(a)  # 1
print(d)  # {2:2}
a = d.pop(-99)  # exception 발생
a = d.pop(-99, 'Nothing here')  # key 가 없는 경우, default 값 반환
print(a)  # Nothing here
d.pop()  # exception 발생



list 와 마찬가지로, dictionary 로 얕은 복사, 깊은 복사가 있음

얕은 복사
a = {1:1}
b = a.copy()

깊은 복사
import copy
a = {1:1}
b = copy.deepcopy(a)



== 혹은 != 를 사용하여 비교 가능함

a = {1:1, 2:2}
b = {2:2, 1:1}

a==b  # True
a!=b  # False
not a==b  # False

a = {1:[1,2]}
b = {1:[2,3]}
a==b  # False



list 와 마찬가지로, 딕셔너리 컴프리헨션을 사용하여 for 문을 한 줄로 사용 가능함

>>> d = {k : k for k in (1,2,3)}
>>> d
{1: 1, 2: 2, 3: 3}


>>> word = "eyeballs"
>>> letter_counter = {key: word.count(key) for key in word}
>>> letter_counter
{'e': 2, 'y': 1, 'b': 1, 'a': 1, 'l': 2, 's': 1}


>>> word = "eyeballs"
>>> letter_counter = {key: word.count(key) for key in word if key in ('b','l','s')}
>>> letter_counter
{'b': 1, 'l': 2, 's': 1}



존재하지 않는 key 로 접근할 시 default 값을 반환하도록 할 수 있음

>>> d = {'a':1, 'b':2}
>>> print(d.get('c'))  # get 으로 접근하면 None 을 반환받음
None


>>> d = {'a':1, 'b':2}
>>> print(d.setdefault('a', 3))  # 'a' 는 존재하는 key 이므로 1 을 반환
1
>>> print(d.setdefault('b', 3))  # 'b' 는 존재하는 key 이므로 2 를 반환
2
>>> print(d.setdefault('c', 3))  # 'c' 는 존재하지 않는 key 이므로 default 값으로 넣은 두 번째 인수 3 을 반환 
3
>>> print(d)
{'a': 1, 'b': 2, 'c': 3}  # 더불어 'c':3 을 추가해줌



defaultdict 를 사용하여, 존재하지 않는 key 로 접근할 시 default 값을 반환하도록 할 수 있음
이 때 함수를 넣어줄 수 있음

>>> from collections import defaultdict
>>> dd_int = defaultdict(int)  # int 를 넣었음, int 의 default 값은 0으로 제공됨
>>> print(dd_int)
defaultdict(<class 'int'>, {})  # 처음에는 아무것도 없음

>>> dd_int['a'] = 1  # key 'a', value 1 을 넣음
>>> print(dd_int['a'])  # key 'a' 는 존재하기 때문에 1 을 반환
1
>>> print(dd_int['b'])  # key 'b' 는 존재하지 않기 때문에 int 의 default 값인  0을 반환
0
>>> print(dd_int)
defaultdict(<class 'int'>, {'a': 1, 'b': 0})  # 더불어 a, b 모두 dict 에 넣어줌


>>> print(defaultdict(str)['key'])  # str 의 default 값은 "" 으로 제공됨

>>> print(defaultdict(dict)['key'])  # dict 의 default 값은 {} 으로 제공됨 
{}
>>> print(defaultdict(list)['key'])  # list 의 default 값은 [] 으로 제공됨
[]

>>> def default_func(): return "default_value"  # default 값을 반환하는 함수를 넣어 default 값을 설정할 수 있음
>>> print(defaultdict(default_func)['key'])
default_value

>>> print(defaultdict(lambda: 'default_value')['key'])  # 간단하게 lambda 를 사용하여 default 값을 반환하는 함수 넣기 가능
default_value


 


set 생성하기

s = set()
s = {1,2,3,3}
s = set('aabbcc')  # {'a', 'b', 'c'}
s = set( [1,1,2,2,3,3] )  # {1,2,3}
s = set( {1:'a', 2:'b', 2:'c'} )  # {1,2}. 키 값만 사용됨



s = {1,2,3}
s.add(4)  # {1,2,3,4}
s.remove(1)  # {2,3,4}



a = {1,2,3}
b = {2,3,4}

>>> a & b  # {2, 3}. 교집합
>>> a - b  # {1}. 차집합
>>> b - a  # {4}. 차집합
>>> a | b  # {1, 2, 3, 4}. 합집합
>>> a.symmetric_difference(b)  # {1, 4}. exclusive

아래는 부분집합
a = {2,3}
b = {1,2,3,4}
>>> a.issubset(b)  # True
>>> a <= b  # True
>>> a < b  # True
>>> b.issubset(a)  # False
>>> b <= a  # False
>>> b < a  # False

a = {1, 2, 3}
b = {2}
b < a  # True. b가 a 와 같지 않으면서 b의 모든 요소가 a 안에 포함된 진부분집합
b <= a  # True. b의 모든 요소가 a 안에 포함된 부분 집합

a = {1, 2, 3}
b = {1, 2, 3}
b < a  # False. b가 a 와 같기 때문에 False. 진부분집합이 되지 못 함
b <= a  # True. b의 모든 요소가 a 안에 포함된 부분 집합



셋 컴프리헨션

>>> s = {a for a in (1,1,2,2)}
>>> s
{1, 2}
>>> s = {a%3 for a in (1,2,3,4,5,6)}
>>> s
{0, 1, 2}
>>> s = {a%3 for a in (1,2,3,4,5,6) if a % 3 != 0}
>>> s
{1, 2}



set 의 값을 불변(추가, 삭제, 업데이트 되지 않는 불변)으로 만들려면 frozenset 을 사용

>>> s = frozenset([1,1,2,2])
>>> s.add(3)
Traceback (most recent call last):
  File "<pyshell#136>", line 1, in <module>
    s.add(3)
AttributeError: 'frozenset' object has no attribute 'add'


 


None 과 False 구분 할 때는, is 를 사용함

a = None
if a is None : print("None")
else : print("False")

 


함수 호출시 인수 이름으로 값 직접 지정 가능

def func(a, b) :
    print(a, b)

print(func(b = "BB", a = "AA"))  # "AA, BB"


함수 인수의 기본값 설정

def func(a, b="BB"):
    print(a, b)

print(func("AA"))  # "AA, BB"
print(func(a = "AA"))  # "AA, BB"
print(func("AA", "XX"))  # "AA, XX"
print(func(a = "AA", b = "XX"))  # "AA, XX"



함수 인수의 기본값은, 함수 호출할 때 계산되는 게 아니라, 함수가 정의될 때 계산됨
즉, 함수가 정의될 때 인수의 기본값이 유지되는 것임
아래 예제로 이해해보자 

>>> def func(a, l = []):
    l.append(a)
    print(l)
>>> func(1)
[1]
>>> func(2)
[1, 2]  # [2] 가 나올 줄 알았지만, 바로 위 1 이 포함된 [1, 2] 가 나옴. 왜냐면 인수의 기본값이 유지되기 때문


함수에 리스트 같은 가변 인수가 들어가는 경우엔, 함수 파라미터에 리스트의 참조값이 복사되기 때문에
함수 내부에서 가변 작업한 것이 리스트에 그대로 적용됨

l = [1,2,3]
def func(l) :
    l.append(99)
print(l)  # [1,2,3,99]



함수에 넣을 인자 개수를 특정지을 수 없는 상황일 때 인수에 애프터리크 ( * ) 를 사용

>>> def func(*args):
    print(args)
>>> func(1)
(1,)
>>> func(1,2)
(1, 2)
>>> func(1,2,3,4,5)
(1, 2, 3, 4, 5)

아래와 같이, 함수 파라미터로 넣을 튜플에 애프터리스크를 사용하면
함수 내부에서 튜플로 인식하지 않고 각각의 값이 들어온 것으로 인식함(즉, 매개변수로 분해함)

>>> def func(*args):
    print(args)
>>> a = (1,2,3)
>>> func(a)
((1, 2, 3),)  # 튜플 하나가 들어온 것으로 인식
>>> func(*a)
(1, 2, 3)  # 1, 2, 3 이 들어온 것으로 인식



가변 인자(*args) 가 앞에 올 수도 있음
가변 인자 뒤에 오는 인자들이 키워드 기반 인자라면...

>>> def func(*args, c, d):
    print(args, c, d)


>>> func(1, c="CC", d="DD")
(1,) CC DD

>>> func(1,2,3,4,5, c="CC", d="DD")
(1, 2, 3, 4, 5) CC DD

>>> func(c="CC", d="DD", 1,2,3)  # 가변 인자 값부터 넣어야 함
SyntaxError: positional argument follows keyword argument



애프러리스크가 두 개 붙으면, 받은 keyword 값 쌍들을 함수 내부에서 dictionary 로 만들어 받음

>>> def func(**kwargs):
    print(kwargs)
>>> func()
{}
>>> func(a="AA", b="BB")
{'a': 'AA', 'b': 'BB'}
>>> func(c="CC")
{'c': 'CC'}  # 애프터 리스크 사용시 인수의 기본값 유지되지 않음


근데 정작 dictionary 를 넣으면 에러가 발생..
>>> func({1:1, 2:2})
Traceback (most recent call last):
  File "<pyshell#174>", line 1, in <module>
    func({1:1, 2:2})
TypeError: func() takes 0 positional arguments but 1 was given



단일 애프터리스크는, 함수의 위치 기반 인수키워드 전용 인수 사이에 넣어, 이 둘을 구분하는 역할을 함
예를 들어 def func(a, b, *, c="CC", d="DD") 처럼 위치 기반 인수(a, b) 와 키워드 전용 인수(c, d) 를 나누고 구분짓는 역할

애프터리스크 앞에 위치한 a, b 에는 (무조건) 위치 기반의 인수가 들어가야하고
애프터리스크 뒤에 위치한 c, d 에는 (무조건) 키워드 기반 인수가 들어가야 함

>>> def func(a, b, *, c="CC", d="DD") :
print(a,b,c,d)

>>> func(1,2)
1 2 CC DD

>>> func(1)  # 위치 기반으로 들어와야 할 b 의 인수가 들어오질 않아서 에러
Traceback (most recent call last):
  File "<pyshell#206>", line 1, in <module>
    func(1)
TypeError: func() missing 1 required positional argument: 'b'

>>> func(1,2)
1 2 CC DD

>>> func(1,2, c="XX")
1 2 XX DD

>>> func(c="XX", 1, 2)  # 위치 기반으로 들어와야 할 a 자리에 c 가 들어와서 에러
SyntaxError: positional argument follows keyword argument

>>> func(1,2,3,4)  # 키워드 기반으로 들어와야 할 c, d 자리에 키워드가 들어오질 않아서 에러
Traceback (most recent call last):
  File "<pyshell#210>", line 1, in <module>
    func(1,2,3,4)
TypeError: func() takes 2 positional arguments but 4 were given



단일 애프터리스크를 이용하여, 모든 인자를 키워드 기반 인자로 받도록 강제할 수 있음

>>> def func(*, a, b):
    print(a,b)

>>> func(a="AA", b="BB")  # a 와 b 에 키워드 기반 인자를 넣음
AA BB

>>> func(1)  # 키워드 기반 인자가 아닌 값은 들어갈 수 없음
Traceback (most recent call last):
  File "", line 1, in 
    func(1)
TypeError: func() takes 0 positional arguments but 1 was given

>>> func(1, a="AA", b="BB")  # 키워드 기반 인자가 아닌 값은 들어갈 수 없음
Traceback (most recent call last):
  File "<pyshell#224>", line 1, in <module>
    func(1, a="AA", b="BB")
TypeError: func() takes 0 positional arguments but 1 positional argument (and 2 keyword-only arguments) were given



함수 바디가 시작되기 전에 문자열을 넣어 함수에 대한 간단한 문서를 작성할 수 있음
문서는 help 를 사용하거나, 함수의 .__doc__ 을 호출하여 확이 가능
이를 독스트링이라고 부름

예를 들어
>>> def func():
    "this func print your name"
    print("eyeballs")

>>> help(func)
Help on function func in module __main__:
func()
    this func print your name

>>> func.__doc__
'this func print your name'


이런 방식을 통해, 사용법(무슨 인자가 얼마나 어떻게 들어가야 하는지)을 모르는 함수 사용시 도움을 받을 수 있음

>>> import copy
>>> help(copy.deepcopy)
Help on function deepcopy in module copy:

deepcopy(x, memo=None, _nil=[])
    Deep copy operation on arbitrary Python objects.
    
    See the module's __doc__ string for more info.

이 간단한 문서를 읽어보고, 나는
"아 인수로 x, memo, _nil 이 들어갈 수 있고 memo 는 default 값이 None 이구나" 라고 알 수 있음



추가로 어떤 객체를 받았을 때 그 객체가 사용 가능한 메소드를 보려면 dir 를 사용하면 됨

>>> dir(copy.deepcopy)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

>>> dir("eyeballs")
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']



함수 내에 함수를 선언하고 사용할 수 있음
이렇게 만든 내부 함수는 함수 바깥에서 사용 불가하기 때문에
임시로 만들어 사용하기 좋음

>>> def func():
     def inner_func():
          print("hi eyeballs")
     inner_func()


>>> func()
hi eyeballs
>>> inner_func()  # 여기서 내부 함수 호출이 불가능
Traceback (most recent call last):
  File "<pyshell#292>", line 1, in <module>
    inner_func()
NameError: name 'inner_func' is not defined



내부 함수를 이용하여 '클로저'를 만들 수 있음
chatGPT 에 의하면, 클로저란 다음과 같음

"클로저(Closure)는 함수 내부에서 또 다른 함수를 정의하고 반환할 때 만들어지는 함수 객체입니다. 반환된 내부 함수는 자신이 선언된 환경(외부 함수의 변수 등)을 기억하여, 외부 함수의 실행 컨텍스트가 종료된 후에도 이 정보를 사용할 수 있습니다."

즉, 생성될 때 넣어준 정보를 계속 지니고 있는 함수가 클로저임


>>> def func(mention):
    def inner_func():
        return f"what you said was this : {mention}"
    return inner_func

>>> a = func("hi eyeballs!")
>>> b = func("bye eyeballs!")

>>> a()  # a 는 클로저(함수)이기 때문에 실행이 가능하며, 실행시 클로저를 정의할 때 넣어줬던 정보를 기억함
'what you said was this : hi eyeballs!'
>>> b()  # b 도 클로저(함수)이기 때문에 실행이 가능하며, 실행시 클로저를 정의할 때 넣어줬던 정보를 기억함
'what you said was this : bye eyeballs!'



클로저를 사용하면, 함수 객체 내부의 상태를 계속 유지할 수 있음.
내부 함수에서 내부 상태를 업데이트하는 기능을 넣어주면, 상태를 계속 업데이트 할 수 있음


>>> def func():
    c = 0
    def add(a=1):
        nonlocal c
        c += a
        return c
    return add

>>> closure = func()
>>> closure()
1
>>> closure()
2
>>> closure(98)
100


여기서 중요한 점, 외부 함수에 정의된 변수에 내부 함수가 접근할 수 없음

>>> def outer():
    c = 0
    def inner():
        c+=1  # 내부함수(inner)에서 외부함수에 정의된 변수 c 에 접근 시도
        print(c)
    inner()

>>> outer()
Traceback (most recent call last):
  File "<pyshell#375>", line 1, in <module>
    outer()
  File "<pyshell#374>", line 6, in outer
    inner()
  File "<pyshell#374>", line 4, in inner
    c+=1
UnboundLocalError: local variable 'c' referenced before assignment



외부 함수에 정의된 변수에 내부 함수가 수정하려면 nonlocal 을 사용

>>> def outer():
    c = 0
    def inner():
        nonlocal c
        c+=1
        print(c)
    inner()

>>> outer()
1


근데 nonlocal 이 "함수 바깥의 변수"에 수정하는 것을 도와주는 명령어는 아님

>>> c = 0
>>> def func():
    nonlocal c  # 가장 바깥쪽 c 에 접근하면 문법 에러가 발생함...

SyntaxError: no binding for nonlocal 'c' found


이렇게 가장 바깥쪽 c 변수가 위치한 곳을 "global scope" 라고 함
global scope 에 있는 변수는 nonlocal 을 이용하여 접근할 수 없음
nonlocal 은, 단지 nearest enclosing scope (outer scope) 에 정의된 변수에만 접근 및 수정 할 수 있게 도와줌

global scope 에 있는 변수에 접근 및 수정하려면 global 을 사용해야 함

>>> c = 0
>>> def func():
    global c
    c+=1
    print(c)

>>> func()
1
>>> print(c)
1
>>> func()
2
>>> print(c)
2


추가로, global scope(namespace) 에 있는 변수들을 보려면 global() 을 실행하여 확인 가능
local scope(namespace) 에 있는 변수들을 locals() 을 실행하여 확인 가능



클로저를 사용하면, 내부 데이터를 은닉(캡슐화)할 수 있음
아래 예제에서 625 라는 숫자는, 클로저를 호출하는 바깥에서는 볼 수 없는 미지의 은닉된 숫자임

>>> def func():
    def inner_func(a):
        if a == 625 : print("correct!")
        else : print("wrong")
    return inner_func

>>> closure = func()
>>> closure(1)
wrong
>>> closure(50)
wrong
>>> closure(100)
wrong
>>> closure(625)
correct!



클로저는 정보를 은닉하여 계속 유지하기 때문에, 메모리에 계속 남아있게 됨
따라서 사용하지 않는 클로저는 삭제하는 것이 좋음

del closure



람다 lambda 함수는 단일 문장으로 표현되는 익명 함수임
따로 def 를 이용하여 정의내리지 않고, 그 때 그 때 필요한 때 사용하고 버림

일반적인 함수

def func(a, b):
    print(a, b)

동일한 역할을 하는 람다 함수

lambda a, b : print(a, b)


아래처럼 간단하게 사용 가능

a = lambda a, b : print(a,b)
a(1,2)  # 1 2


lambda 함수는, 콜론 ( : ) 뒤의 명령어가 실행되거나 반환됨

>>> a = lambda a, b : a+b
>>> a(1,2)
3

>>> x, y = 1, 2
>>> swap = lambda a, b: (b, a)
>>> x, y = swap(x, y)
>>> print(x, y)
2 1



아래와 같은 함수를 인자로 받는 함수에서 

>>> def func(mylist, myfunc):
    for i in mylist:
        print(myfunc(i))


def 로 정의된 함수를 넣어도 되지만

>>> def myfunc(i):
    return i.capitalize()
>>> func(["hi", "eyeballs"], myfunc)
Hi
Eyeballs


lambda 함수를 바로 넣을 수 있음

>>> func(["hi", "eyeballs"],lambda i: i.capitalize())
Hi
Eyeballs



아무것도 실행하지 않는 함수를 만들기 위해 pass 를 사용

def doNothing():
    pass


 


python3 에서 모든 객체는 기본적으로 "강한 참조"로 생성됨

강한 참조는 참조 카운트를 증가시키고, garbage collector 에 의해 수거되지 않음
약한 참조는 참조 카운트를 증가시키지 않고, garbage collector 에 의해 수거됨
약한 참조는 일부러 만들어야 함
강한 참조는 메모리의 객체 자체를 직접 참조하지만, 약한 참조는 객체를 간접적으로 참조함

강한 참조와 약한 참조 예제

>>> import weakref
>>> def func(): pass  # 강한 참조를 갖는 객체 생성
>>> weak_ref = weakref.ref(func)  # 약한 참조를 갖는 객체 생성
>>> type(weak_ref())  # 약한 참조 객체 실행해보면 반환값이 function 인 것을 확인
<class 'function'>
>>> del func  # 강한 참조 객체가 삭제되면, 약한 참조도 따라서 삭제됨
>>> type(weak_ref())  # 약한 참조 객체 실행해보면 반환값이 None 으로 변한 것을 확인
<class 'NoneType'>


- 강한 참조 (Strong Reference)
  - 참조 카운트 : 증가
  - 객체 생존 : 강한 참조가 존재하면 삭제되지 않음
  - 활용례 : 일반적인 객체

- 약한 참조 (Weak Reference)
  - 참조 카운트 : 증가하지 않음
  - 객체 생존 : 강한 참조가 없으면 Garbage Collection 가능 (수거되어 사라짐)
  - 활용례 : cache 를 만들거나, 메모리 관리가 중요한 앱을 만들 때 사용됨


이거 마치 리눅스의 하드 링크와 소프트(심볼릭) 링크의 관계 같다는 느낌이 듦...
강한 참조를 갖는 객체를 바라보는 약한 참조 객체는 soft link 같아서
강한 참조 객체가 사라지면(원본 file/dir 가 사라지면) 약한 참조 객체는 참조 할 객체가 사라지게 됨(soft link 가 갈 길을 잃음)


 


nested list 가 중첩된 리스트를 flatten 하게 만들기 위해 generator 를 아래와 같이 사용 가능

>>> lol = [1,[2,[3,4],5,[6],7]]
>>> def flatten(l):
    for item in l:
        if isinstance(item, list):
            for subitem in flatten(item):
                  yield subitem
        else:
            yield item
 
>>> list( flatten(lol) )
[1, 2, 3, 4, 5, 6, 7]


 


try-except 예제


try: 
    1/0
except: 
    print("divided by zero")



try:
    [1,2,3][4]
except IndexError as ie:
    print("index error. message : ", ie)  # 여기서 ie 는 시스템에서 작성해주는 error 메세지
except Exception as e:
    print("exception. message : ", e)



def divide(by):
    try : result = 1/by
    except ZeroDivisionError as e :
        print("divided by zero", e)
    else : print(result)  # else 는 예외가 발생하지 않았을 때 실행됨
    finally : print("done")



try:
    raise Exception("exception by developer on purpose")  # 일부러 Exception 을 발생. error message 입력 할 수 있음
except Exception as e:
    print("exception message : ", e)
    riase  # raise 를 다시 사용하여, 똑같은 exception 을 다시 발생시킴

exception message :  exception by developer on purpose
Traceback (most recent call last):
  File "<pyshell#619>", line 2, in <module>
    raise Exception("exception by developer on purpose")
Exception: exception by developer on purpose



try:
    raise RuntimeError("exception by developer on purpose")  # 원하는 Error 를 발생시킬 수 있음
except RuntimeError as re:
    print("exception message :", re)

exception message : exception by developer on purpose



assert 를 이용하여 예외를 발생시킬 수 있음
asset 는 나와선 안 되는 조건을 검사할 때 넣는 명령어임
지정된 조건식이 False 일 때 AssertionError 를 발생시킴

>>> def func(i):
    assert i % 3 == 0, '3의 배수가 아님'
    print(i,'는 3의 배수임')


>>> func(3)
3 는 3의 배수임
>>> func(1)
Traceback (most recent call last):
  File "<pyshell#638>", line 1, in <module>
    func(1)
  File "<pyshell#636>", line 2, in func
    assert i % 3 == 0, '3의 배수가 아님'
AssertionError: 3의 배수가 아님



예외를 직접 만들 수 있음

>>> class NotThreeMultipleError(Exception):
    def __init__(self):
        super().__init__('3의 배수가 아닙니다.')

>>> try
    raise NotThreeMultipleError
except Exception as e:
    print("error message", e)


error message 3의 배수가 아닙니다.



예외 메세지를 raise 에 붙일 수 있음

>>> class NotThreeMultipleError(Exception):
pass

>>> try:
    raise NotThreeMultipleError("3의 배수가 아님")
except NotThreeMultipleError as e:
    print("message :", e)

message : 3의 배수가 아님


 


객체(Object) : 파이썬의 모든 데이터. 인스턴스를 포함. 객체는 데이터(변수, 속성)와 코드(메소드)를 포함하는 자료구조
인스턴스(Instance) : 특정 클래스에서 생성된 객체 (특정 클래스의 '사례')

모든 인스턴스는 객체이지만, 모든 객체가 인스턴스는 아님
즉, 인스턴스는 객체의 부분집합



클래스에 dictionary 마냥 속성 추가 가능

class MyClass(): pass

my_class = MyClass()
my_class.name = "eyeballs"
print(my_class.name)  # eyeballs



class 생성시 속성 초기화 실행

class MyClass():
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def printing(self)
        print("name : ", self.name, "age : ", self.age)

my_class = MyClass("eyeballs", 625)
my_class.printing()


여기서 self 는 클래스의 인스턴스(instance) 자신을 참조하는 변수임
__init__ 메소드를 포함한 모든 인스턴스 메서드호출될 때 첫 번째 인수로 자동으로 인스턴스 자신이 전달
따라서, self를 사용하여 클래스 내부에서 해당 인스턴스의 속성과 메서드에 접근할 수 있음

위에 MyClass 인스턴스에서 printing() 이 실행되면,
printing() 의 첫번째 인수(self) 자리에 my_class 인스턴스가 전달됨
그래서 인스턴스(my_class) 의 name 과 age 를 사용할 수 있게 됨


참고로 __init__ 은 생성자가 아니라 단지 초기화 메소드임
왜냐면, __init__ 호출 전에 이미 객체가 만들어지기 때문




class Parent(): pass
class Child(Parent): pass

issubclass(Child, Parent)  # True



super() 를 사용하여 부모의 메소드를 이용하면, 부모 클래스 레벨에서 작업이 이루어짐

>>> class Parent():
    def __init__(self, name):
        self.parent_name = name

>>> class Child(Parent):
    def __init__(self, name, age):
        super().__init__(name)
        self.child_age = age

>>> child = Child("eyeballs", 625)
>>> dir(child)
['__class__', '__delattr__', '__dict__', .......  'child_age', 'parent_name']



클래스가 갖고 있지 않은 메소드 혹은 속성을 참조하면
python 은 모든 부모 클래스를 다 조사함

만약 다중 상속을 받은 경우라면, 상속받은 순서대로 조사함

>>> class High():
    def name(self):
        return "High"

>>> class Middle1(High):
    def name(self):
        return "Middle1"

>>> class Middle2(High):
    def name(self):
        return "Middle2"

>>> class Low1(Middle1, Middle2): pass
>>> Low1().name()
'Middle1'  # Middle1 이 먼저 상속되었기 때문에 Middle1의 name() 이 호출됨

>>> class Low2(Middle2, Middle1): pass
>>> Low2().name()
'Middle2'  # Middle2 가 먼저 상속되었기 때문에 Middle2의 name() 이 호출됨



python 에는 class 의 속성에 접근하지 못하게 막는 private 접근지시어 등은 없음
대신, 속성 이름을 다른 이름으로 가려서 접근을 우회하여 막는 방법이 있음


class MyClass():
    def __init__(self, name):
        self.private_name = name
    def getter(self):
        return self.private_name
    def setter(self, name):
        self.private_name = name
    public_name = property(getter, setter)

my_class =MyClass("eyeballs")
print(my_class.public_name)   # MyClass 를 사용하는 시점에서 private_name 대신 public_name 을 사용함
my_class.public_name = "w"
print(my_class.public_name)

propert 에 getter 와 setter 를 주고 public_name 을 설정함으로써
private_name 에 접근하지 못하도록 함
(물론 "private_name" 이라는 키워드를 (dir 등으로) 알고 있는 개발자라면 접근 가능함.....)



class 내의 @property 는 '계산된 값'에 접근하도록 돕기도 함

class MyClass():
    def __init__(self, name):
        self.name = name
    @property
    def name_length(self):
        return len(self.name)

my_class =MyClass("eyeballs")
print(my_class.name_length)  # name_length 는 메소드지만, 마치 변수에 접근하는 것 마냥 접근함
8


참고로 property 로 설정된 속성은 read-only 가 됨. 아래처럼 수정하려면, setter 를 설정해야 함

>>> my_class.name_length=1
Traceback (most recent call last):
  File "py.py", line 11, in <module>
    my_class.name_length = 1
AttributeError: can't set attribute



property 를 통해 속성 이름을 다른 것으로 바꾸는 방법 외에
dunder (double underbar) 를 통해 이름을 다르게 바꿀 수 있음

>>> class MyClass():
    def __init__(self, name):
        self.__name = name  # 이름 앞에 dunder 를 붙이면, 인스턴스에서 __name 으로 접근이 불가능


>>> my_class = MyClass("eyeballs")
>>> dir( my_class )
['_MyClass__name', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

__name 대신 _MyClass__name 이 새로 생김
__name 에 접근하고 싶으면 getter, setter를 사용하던가, 아니면 (숨겨진 이름의) _MyClass__name 을 사용

이렇게 변수나 메소드의 이름을 컴파일 단계에서 일정한 규칙을 통해 바꾸는 것을 맹글링이라고 함



class method 는 인스턴스가 아닌 클래스 자기 자신 전체에 영향을 미치는 메소드임
(인스턴스가 아니라) 클래스 속성을 변경하거나 새로운 객체를 생성하는 용도로 사용됨

@classmethod 데코레이터를 추가하여 class method 생성 가능


class MyClass():
    count = 0
    def __init__(self):
        MyClass.count += 1
    @classmethod
    def get_count(cls):  # self 대신 cls 를 사용. 즉, class 자기 자신을 가리키며 참조함
        return cls.count

a = MyClass()
b = MyClass()
print(MyClass.get_count())  #2. 클래스를 통해 호출
print(b.get_count())  #2. 인스턴스를 통해 호출


self 는 클래스의 인스턴스 자신을 가리키는 변수이며
cls 는 클래스 자기 자신을 가리키는 변수임



class method 를 통해 인스턴스를 찍어내는 팩토리 패턴 구현 가능

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

    @classmethod
    def get_instance(cls, name, birth_year):
        return cls(name, 2025-birth_year)  # cls 를 이용하여 인스턴스를 생성한 후 반환

a = MyClass.get_instance("eyeballs", 1991)
b = MyClass.get_instance("w", 1965)

print(a.name, a.age)
print(b.name, b.age)



static method 는 클래스에서 곧바로 호출 가능한 메소드. 편의(유틸리티)를 위해 존재함
@staticmethod 데커레이터를 사용하여 정의


>>> class MyClass():
    @staticmethod
    def read_me():
    print("please read this first")


>>> MyClass.read_me()
please read this first




타입을 미리 정하는게 아니라 실행이 되었을 때 해당 Method들을 확인하여 타입을 정하는 것을 '덕타이핑'이라고 함

def func(obj):
    print(obj.name())


class A:
    def name(self):
        return self.__class__.__name__

class B:
    def name(self):
        return self.__class__.__name__

func(A())  # 'A' 출력.
func(B())  # 'B' 출력.


func 메소드의 obj 에 뭐가 들어오던간에
name() 이라는 메소드가 있다면 실행하게 됨. 그것도 런타임에.




dataclass 를 통해, 클래스에 데이터(속성)를 직관적으로 지정하여 설정할 수 있음
아래 두 가지 class 는 동일하게 name 속성을 갖는 class 임


class MyClass():
    def __init__(self, name):
        self.name = name

from dataclasses import dataclass
@dataclass
class MyDataClass():
    name : str

print(MyClass("eyeballs").name)  # eyeballs
print(MyDataClass("eyeballs").name)  # eyeballs


>>> from dataclasses import dataclass
>>> 
@dataclass
class MyDataClass():
    name: str
    age: int = 30

>>> MyDataClass("A", 20)
MyDataClass(name='A', age=20)

>>> MyDataClass(20, "A")   # 놀랍게도, 문법적으로 허용 됨. 왜냐면 파이썬은 동적 언어라서, 런타임에 실제 타입을 검사하거나 변환하지 않음. 위에 name: str 에서 str 은 단지 개발자한테 '이렇게 넣으셈' 하는 힌트일뿐이고, 문법적인 강제는 없음
MyDataClass(name=20, age='A')

>>> MyDataClass(name="A", age=20)
MyDataClass(name='A', age=20)

>>> MyDataClass(age=20, name="A")
MyDataClass(name='A', age=20)

>>> MyDataClass("A")  # age 생략, default 로 설정한 30이 대신 사용됨
MyDataClass(name='A', age=30)




타입을 강제하고 싶다면, __post_init__ 에서 체커를 추가함
__post_init__ 은 dataclass 의 __init__ 메서드가 호출된 후 자동으로 실행되는 메서드(__init__ 이 __post_init__ 을 호출함)


from dataclasses import dataclass

@dataclass
class MyDataClass:
    name: str

    def __post_init__(self):
        if not isinstance(self.name, str):
            raise TypeError(f"Expected str, got {type(self.name).__name__}")

MyDataClass(name="A")
MyDataClass(name=20)  # exception



데이터 클래스는 매직메소드(duner 가 들어가는 메소드들)를 자동으로 만들어주기도 함
예를 들어, __init__, __repr__, __eq__ 같은 메소드들을 자동으로 만들어 줌
위에서 __init__ 없이 데이터 클래스를 정의할 수 있었던 것도 다 이런 이유에서였음


>>> class A:
    def __init__(self, name):
        self.name = name


>>> @dataclass
class B:
    name : str


>>> print(A("eyeballs"))
<__main__.A object at 0x01562D00>  # __repr__ 가 존재하지 않아, A 클래스의 메모리값을 대신 반환

>>> print(B("eyeballs"))  # dataclass 가 __repr__ 를 대신 생성해주어, B 의 name 을 포함하여 반환
B(name='eyeballs')



 


mymodule.py 과 mycode.py 가 한 dir에 존재하는 경우 다음과 같이 바로 import 가능

< mymodule.py >
from random import choice
mylist = [1,2,3,4,5]
def pick():
    return choice(mylist)

< mycode.py >
import mymodule
print(mymodule.pick())

혹은

< mycode.py >
from mymodule import pick
print(pick())

혹은

< mycode.py >
from mymodule import pick as p
print(p())



"mypackage" 라는 dir 안에 mymodule.py 과 mymodule2.py 를 넣어둠
mycode.py 는 mypackage dir 와 동일한 위치에 존재함

< mypackage/mymodule.py >
from random import choice
mylist = [1,2,3,4,5]
def pick():
    return choice(mylist)

< mypackage/mymodule2.py >
from random import choice
mylist = [6,7,8,9,10]
def pick():
    return choice(mylist)


mypackage dir 안에 있는 mymodule.py, mymodule2.py 를 아래와 같이 from, import 로 나눠 불러올 수 있음

< mycode.py >
from mypackage import mymodule, mymodule2
print(mymodule.pick())
print(mymodule2.pick())

혹은 

from mypackage.mymodule import pick
from mypackage import mymodule2
print(pick())
print(mymodule2.pick())



위와 같이 하위 dir 에서 module 을 불러올 때 from, import 를 사용함
그럼 from random 은 어디서 불러올까?
이 모듈 파일은, 현재 작업중인 dir 이내에 없기 때문에, python 이 다른 위치에서 해당 module 을 불러옴
그 "다른 위치"라는 곳은 아래와 같이 확인 가능

>>> import sys
>>> for p in sys.path:
print("\"",p,"\"")

" "
"C:\Users\EYE\Python\"
"C:\Users\EYE\Python\Python38-32\Lib\idlelib"
"C:\Users\EYE\Python\Python38-32\python38.zip"
"C:\Users\EYE\Python\Python38-32\DLLs"
"C:\Users\EYE\Python\Python38-32\lib"
"C:\Users\EYE\Python\Python38-32"
"C:\Users\EYE\Python\Python38-32\lib\site-packages"

(현재 윈도우에서 작업중이라 위와 같은 경로들이 나타남)

가장 먼저 path 에 나타나는 것이 빈 문자열(" ") 임
이 말은, python 실행시 현재 dir 를 기준으로 먼저 module 을 찾는다는 의미임

임의의 path 를 추가하려면 아래와 같이 실행

import sys
sys.path.insert(0, "C:\Users\EYE\my\python\module\path")  # 0순위로 찾게 됨



module 경로를 상대적으로 넣어줄 수 있음
예를 들어 mycode.py 와 동일한 dir 위치에 있는 mymodule.py 을 불러오려면

< mycode.py >
from . import mymodule
...


mycode.py 보다 상위 dir 위치에 있는 mymodule.py 을 불러오려면

< mycode.py >
from .. import mymodule
...


mycode.py 보다 상위 dir 위치의 mypackage 에 있는 mymodule.py 을 불러오려면

< mycode.py >
from ..mypackage import mymodule
...



놀랍게도, import 한 module 의 값을 직접 업데이트 할 수 있음
module 을 import 한 프로그램에 module 의 사본이 생성된다고 이해하면 됨
다시 import 해도 동일한 업데이트가 반영되며,
나중에 다른 프로그램에서 동일한 module 을 import 하면 그에 맞춰 새로운 사본이 생성되기 때문에
pi=3 으로 업데이트 한 내용이 다른 프로그램에 영향을 미치지 않음

>>> import math
>>> math.pi
3.141592653589793
>>> math.pi = 3
>>> math.pi
3
>>> import math
>>> math.pi
3


 


Deque 는 스택과 큐의 기능을 모두 갖고 있음
즉, 양쪽으로 pop 이 가능함

>>> from collections import deque
>>> dq = deque([1,2,3,4,5])
>>> print(dq.pop())  # 가장 마지막에서 pop
5
>>> print(dq)
deque([1, 2, 3, 4])
>>> print(dq.popleft())  # 가장 처음에서 pop
1
>>> print(dq)
deque([2, 3, 4])



여러 시퀀스들을 차례대로 순회하기 위해 itertools.chain 을 사용

>>> import itertools
>>> for item in itertools.chain([1], [2,3], (4,5,6)):   # 3개의 다양한 시퀀스를 넣음
    print(item)

1
2
3
4
5
6



하나의 시퀀스를 순회하며 누적 계산을 하기 위해 itertools.accumulate 를 사용

>>> import itertools
>>> for item in itertools.accumulate([1,2,3,4]):
print(item)

1
3
6
10

기본적으로 누적 합계를 계산함
합계가 아닌 다른 누적 계산을 진행하려면, def 를 추가로 넣어주면 됨

>>> def mul(a,b):
    return a*b
>>> for item in itertools.accumulate([1,2,3,4], mul):
print(item)

1
2
6
24



from pprint import pprint

pprint 는 일반 print 보다 훨씬 가독성 좋게 출력해줌
dictionary 를 출력하면 정렬도 해 줌....


 


python 은 pip 를 통해 PyPI(Python package index. https://pypi.org) 로부터 패키지를 다운받아 설치할 수 있음
"패키지를 설치"한다는 것의 의미는, 해당 패키지의 코드와 관련 종속성(dependencies)을 Python 환경에 다운로드하고
적절한 위치에 배치하여 사용할 수 있도록 등록하는 과정을 의미
여기서 말하는 '적절한 위치'란, site-packages(Python의 라이브러리 디렉터리)를 의미함.
이 site-packages 에 패키지 파일을 복사함
패키지 설치 이후에 python 스크립트 내에서 import 를 통해 패키지 및 모듈 사용이 가능하게 됨

패키지 파일 위치는 아래와 같이 확인 가능

import requests
print(requests.__file__)  # 패키지의 실제 경로 출력

일반적으로 pip 로 설치한 패키지는 모든 python 프로젝트에서 사용 가능함 (global installation)
어느 특정 python 프로젝트 에서만 사용 가능하도록 패키지를 설치하려면 
venv 또는 conda 같은 가상 환경을 사용해야 함


pip list  # 설치된 패키지 목록 확인
pip show requests  # 특정 패키지 정보 확인 

pip 로 패키지 설치하기
pip install flask
pip install flask==0.9.0
pip -r installations.txt  # 해당 txt 파일에는 패키지 이름들이 개행 간격으로 적혀있고, txt 파일 내 모든 패키지가 설치됨

pip install --upgrade  # 설치된 모든 패키지를 최신 패키지로 업그레이드
pip uninstall requests  # 패키지 삭제



Python의 가상환경은 독립적인 실행 환경을 만들어서 
각 프로젝트마다 별도의 패키지 및 Python 버전을 관리할 수 있도록 도와주는 기능

global package 와 다른 버전을 사용해야 할 때 사용되며,
venv, virtualenv, conda 등이 있음



< venv (Python 내장 가상환경) >
Python 3.3 이상에서 기본 제공되는 가상환경 도구

가상 환경 생성 명령어 : python -m venv my_env
my_env 라는 dir 가 생성되고, 이 dir 내에 가상환경 관련 파일이 저장됨
해당 프로젝트에서 설치한 패키지들이 이 my_env dir 에 설치됨

가상환경 활성 명령어 : source my_env/bin/activate

가상환경 비활성 명령어 : deactivate

가상환경 제거 명령어 : rm -rf my_env
(그냥 dir 를 지우는 거네)



< virtualenv (확장 기능이 있는 가상환경) >
venv보다 더 다양한 기능 제공함
Python 2.x 및 3.x 모두 지원하며, 
하나의 가상환경에서 여러 Python 버전 사용 가능.

virtualenv 설치 명령어 : pip install virtualenv

가상환경 생성 명령어 : virtualenv my_env
특정 python 버전으로 가상환경 생성하는 명령어 : virtualenv -p /usr/bin/python3.8 my_env

가상환경 활성 명령어 : source my_env/bin/activate

가상환경 비활성 명령어 : deactivate



< conda (데이터 과학 및 패키지 관리 특화) >
데이터 과학, 머신러닝, 딥러닝 등에 최적화된 가상환경을 제공함
pip와 달리 Python 패키지뿐만 아니라 비(非)Python 패키지도 설치 가능 (예: numpy, tensorflow, R).
virtualenv 와 동일하게, 여러 Python 버전 사용 가능.

conda 를 통해 가상환경을 설정하려면, Anaconda 를 설치해야 함
설치 후 anaconda 가 제공하는 명령어를 통해 가상환경을 구축할 수 있음

특정 python 버전으로 가상환경 생성하는 명령어 : conda create --name my_env python=3.8

가상환경 활성 명령어 : conda activate my_env

가상환경 비활성 명령어 : conda deactivate

가상환경 제거 명령어 : conda remove --name my_env --all



주피터 노트북 설치 명령어 : pip install jupyter
주피터 노트북 실행 명령어 : jupyter notebook

주피터lab 설치 명령어 : pip install jupyterlab
주피터lab 실행 명령어 : jupyter lab

 


python unittest 사용 예제
첫 글자를 대문자로 바꾸는 함수를 테스트 할 예정


import unittest
def func(text):
    if text is None : return text
    try:
        if i := int(text) : return text
    except: pass
    return text.capitalize()

class Test(unittest.TestCase):

    def setUp(self): pass  # 테스트가 진행되기 전 실행되는 메소드
    def tearDown(self): pass  # 테스트가 마무리 된 후 실행되는 메소드

    def test1(self):
        text = "eyeballs"
        result = func(text)
        self.assertEqual(result, "Eyeballs")

    def test2(self):
        text = "hi eyeballs"
        result = func(text)
        self.assertEqual(result, "Hi eyeballs")

    def test3(self):
        text = 123
        result = func(text)
        self.assertEqual(result, 123)

    def test4(self):
        text = None
        result = func(text)
        self.assertEqual(result, None)

    def test5(self):
        text = True
        result = func(text)
        self.assertEqual(result, True)

if __name__ == '__main__' :
    unittest.main()


테스트 성공

C:\Users\EYE\Desktop\python>python mycode.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK



테스트 실패

C:\Users\EYE\Desktop\python>python mycode.py
.F..F
======================================================================
FAIL: test2 (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "mycode.py", line 24, in test2
    self.assertEqual(result, "Hi Eyeballs")
AssertionError: 'Hi eyeballs' != 'Hi Eyeballs'
- Hi eyeballs
?    ^
+ Hi Eyeballs
?    ^


======================================================================
FAIL: test5 (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "mycode.py", line 39, in test5
    self.assertEqual(result, None)
AssertionError: True != None

----------------------------------------------------------------------
Ran 5 tests in 0.003s

FAILED (failures=2)


 


로깅 모듈 사용하여 로깅할 때 필요한 개념들

메세지 : 로그 메세지
레벨 : 로깅의 심각한 정도 debug, info, warning, error, critical
로거 logger : 모듈과 연결되는 하나 이상의 객체
핸들러 handler : 터미널, 파일, DB 등으로 메세지를 전달하는 역할
포매터 formatter : 결과를 생성
필터 filter : 입력 기반으로 판단

import logging
logging.debug("debug")
logging.info("info")
logging.warning("warning")
logging.error("error")
logging.critical("critical")

출력 결과 
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical


기본적으로 debug, info 는 결과를 출력하지 않고 warning, error, critical 만 출력함
basicConfig 를 통해 기본 level 을 debug 로 지정하면,
이후부터 debug 부터 critical 까지 전부 출력

logging.basicConfig(level=logging.DEBUG)

출력 결과
DEBUG:root:debug
INFO:root:info
WARNING:root:warning
ERROR:root:error
CRITICAL:root:critical



로거를 설정해두면, 어떤 위치에서 어떤 로거에 의해 출력되었는지 따로 구분할 수 있음

import logging
logging.basicConfig(level=logging.DEBUG)

A_logger = logging.getLogger('A')
B_logger = logging.getLogger('B')

A_logger.warning("warning")
B_logger.critical("critical")

출력 결과
WARNING:A:warning
CRITICAL:B:critical



basicConfig 에 filename 을 추가하면, 로그를 stdout 이 아니라 file 로 저장할 수 있음

import logging
logging.basicConfig(level=logging.DEBUG, filename='warning.log')
logging.warning("warning")

프로그램을 실행한 위치에 warning.log 가 생성됨



basicConfig 에 format 을 추가하면, 로그의 포맷을 변경할 수 있음

import logging
fmt = '%(asctime)s %(levelname)s %(lineno)s %(message)s'
logging.basicConfig(level=logging.DEBUG, format=fmt)
logging.warning("warning")

출력 결과
2025-01-30 21:24:58,157 WARNING 4 warning



 


Python의 Global Interpreter Lock (GIL)
멀티스레드 환경에서 한 번에 하나의 스레드만 Python 바이트코드를 실행할 수 있도록 하는 메커니즘

Python 은 분명 멀티스레드를 지원하지만,
GIL 에 의해 동시에 두 개 이상의 스레드가 Python 코드 실행을 병렬로 수행할 수 없음

GIL 은 여러 스레드가 하나의 자원을 수정하면 발생하는 race condition 을 방지하기 위해 존재하며,
여러 스레드가 동시에 실행될 시 GC 실행이 불안정해지는 것을 방지하기 위함 (메모리 관리 안정성을 보장)
(이 GC 는 순환 참조 문제를 해결하기 위해 좀 더 복잡한 기법을 사용하는 GC라고 함...)


< CPU bound 작업 실행시 >

GIL은 멀티코어 CPU의 성능을 제대로 활용하지 못하게 만듦
실제로 멀티스레딩을 사용해도 실제 성능 향상이 거의 없음.
GIL이 있기 때문에 여러 개의 CPU 코어를 제대로 활용할 수 없음

따라서 멀티프로세싱(multiprocessing) 대신 사용하거나,
GIL 을 지원하지 않는 Numpy, Pandas, 혹은 PyPy 를 사용



< IO bound 작업 실행시 >

GIL은 입출력(I/O) 중심 작업에서는 큰 영향을 주지 않음
Python은 GIL을 사용하지만, I/O 작업을 수행하는 동안에는 GIL을 자동으로 해제함.
따라서 멀티스레딩이 성능 향상에 도움이 될 수 있음.




python 을 통해 새로운 process 를 실행하여 shell 명령어를 실행할 수 있음.
(shell 명령어를 실행한다는 의미는, process 를 하나 실행한다는 의미가 됨)

import subprocess
result = subprocess.getoutput('ls -laht | grep 2025 | wc -l')  # shell 명령어를 수행 후 stdout/stderr 결과를 반환
result = subprocess.getstatusoutput('ls -laht')  # shell 명령어 수행 후 stdout/stderr 결과와 상태 코드를 튜플로 반환
result = subprocess.call('date')  # shell 명령어 수행 후 결과의 상태 코드(0(성공) 혹은 0 외의 값)만 반환
result = subprocess.call(['date', '-u'])  # shell 명령어를 수행 후 stdout/stderr 결과를 반환
result = subprocess.call('date -u', shell=True)  # shell 명령어를 수행 후 stdout/stderr 결과를 반환

os 를 통해서도 shell 명령어 실행 가능

import os
result = os.system("ls -laht")   # shell 명령어 수행 후 결과의 상태 코드(0(성공) 혹은 0 외의 값)만 반환



python multiprocessing 예제 

import multiprocessing
import os

def target_func(name):  # multiprocessing 으로 처리 될 함수
    print(f"process id : {os.getpid()} name : {name}")

def run_multi_process():
    target_func("main process")

    p1 = multiprocessing.Process(target=target_func, args=('first multiprocess',))  # args 는 튜플로 넣어줘야 함
    p1.start()
    print(f"p1) process id : {p1.pid} name : {p1.name}")

    p2 = multiprocessing.Process(target=target_func, args=('second multiprocess',))
    p2.start()
    print(f"p2) process id : {p2.pid} name : {p2.name}")

    p3 = multiprocessing.Process(target=target_func, args=('third multiprocess',))
    p3.start()
    print(f"p3) process id : {p3.pid} name : {p3.name}")

if __name__=='__main__':
    run_multi_process()



결과
process id : 16664 name : main process

p1) process id : 10564 name : Process-1
p2) process id : 11740 name : Process-2
p3) process id : 9108 name : Process-3

process id : 10564 name : first multiprocess
process id : 11740 name : second multiprocess
process id : 9108 name : third multiprocess


위에 p1.pid 와 (target_func 내의) os.getpid() 가 동일한 것을 확인할 수 있음
즉, target_func 내에서 실행되는 명령어들은 모두 sub process 에서 동작함

multiprocessing.process 를 종료하려면 아래와 같이 terminate 실행

p1.terminate()
p2.terminate()
p3.terminate()



python multi threading 예제


import threading
import os

def target_func(name):
    print(f"process id : {os.getpid()} name : {name}")

def run_multi_thread():
    target_func("main process")
    t1 = threading.Thread(target=target_func, args=('first thread',))
    t1.start()
    print(f"t1) thread ident : {t1.ident} native_id : {t1.native_id} name : {t1.name} ")

    t2 = threading.Thread(target=target_func, args=('second thread',))
    t2.start()
    print(f"t2) thread ident : {t2.ident} native_id : {t2.native_id} name : {t2.name}")

    t3 = threading.Thread(target=target_func, args=('third thread',))
    t3.start()
    print(f"t3) thread ident : {t3.ident} native_id : {t3.native_id} name : {t3.name}")

if __name__=='__main__':
    run_multi_thread()



결과


process id : 8900 name : main process

process id : 8900 name : first thread
t1) thread ident : 19272 native_id : 19272 name : Thread-1

process id : 8900 name : second thread
t2) thread ident : 13024 native_id : 13024 name : Thread-2

process id : 8900 name : third thread
t3) thread ident : 364 native_id : 364 name : Thread-3


process id  는 모두 동일한 것을 확인
ident 는 python 내부에서 관리하는 thread 의 고유 id 이며, os 레벨에서 관리할 수 없음
native_id 는 OS 가 관리하는 실제 thread 의 고유 id 이며, os 의 프로세스 관리도구(ps 등)로 확인 가능





 

 

 

 

 

 

 

 

 

Scala 언어 문법책 공부 후 핵심만 정리함

 

 

Scala 는 JVM 언어임. 자바 런타임을 사용하여 실행됨

 

 

 

Literal : 숫자 5, 문자 'A', 문자열 "eyeballs" 처럼 소스 코드에 바로 등장하는 데이터

값(value) : 불변의 타입을 갖는 저장 단위. 정의될 때 데이터가 할당되며 재할당 불가능

변수(variable) : 가변의 타입을 갖는 저장 단위. 정의될 때 데이터가 할당되며 재할당 가능

타입(type) : 데이터의 종류, 정의, 분류. Scala 의 모든 데이터는 특정 타입에 대응하며, 모든 Scala 타입은 그 데이터를 처리하는 메소드를 갖는 클래스로 정의됨

 

불변의 값(value) 를 사용하면, 다른 어떤 코드에서 접근하더라도 같은 값을 유지하는 안정성을 갖출 수 있어

코드를 읽고 디버깅하는 일이 더 쉬워짐

동시 또는 멀티 스레드 코드에서 값을 사용하여 에러 발생 가능성을 낮출 수 있음

 

 

 

 

이름 설명 크기 최솟값 최댓값
Byte 부호 있는 정수 1byte -128 127
Short 부호 있는 정수 2byte -32768 32767
Int 부호 있는 정수 4byte -2^31 2^31 - 1
Long 부호 있는 정수 8byte -2^63 2^63 - 1
Float 부호 있는 부동 소수 4byte n/a n/a
Double 부호 있는 부동 소수 8byte n/a n/a

 

 

 

 

 

 

Literal Type 설명
5 Int 접두사/접미사 없는 정수 리터럴은 기본이 Int
0x0f Int 접두사 0x : 16진수
5l Long 접미사 l : Long 타입
5.0 Double 접두사/접미사 없는 소수 리터럴은 기본이 Double
5f Float 접미사 f : Float 타입
5d Double 접미사 d : Double 타입

 

 

 

 

정규 표현식

 

matches : 정규 표현식이 전체 문자열과 맞으면 true

"abc" matches ".*" : true

 

replaceAll : 일치하는 문자열을 모두 치환

"abc" replaceAll ("b|c","a") : "aaa"

 

replaceFirst : 첫번째로 일치하는 문자열을 치환

"abc" replaceFirst ("b|c","a") : "aac"

 

 

 

 

bool 비교 연산자인 &&,|| 은 게을러서 첫 번째 인수로 충분하다면 두 번째 인수는 평가하지 않음

&, | 은 결괏값 반환하기 전에 늘 두 인수를 모두 검사함

 

 

 

 

데이터 타입 연산

예제 설명
val aa = 5
myVal.asInstanceOf[Long]
원하는 타입으로 전환
val aa = 5
myVal.getClass
해당 값의 타입(=Class) 반환
val aa = 5
myVal.isInstanceOf[Int]
해당 값이 넣어준 타입에 해당하는지 확인
val aa = 5
myVal.hashCode
해당 값의 해시코드 반환
val aa = 5
myVal.toDouble
형변환
val aa = 5
myVal.toString
해당 값을 string 으로 변환

 

 

 

 

 

 

Tuple 은 선언 방법이 두 가지이며, 인덱스는 1부터 시작함

0부터 시작하지 않는 문제아. 아주 자기 멋대로야..

 

val myTuple = (1, "2", '3', "four", 5)

myTuple._1 // 1

myTuple._2 // "2"

 

val myTuple = 1 -> "2"

myTuple: (Int, String) = (1, "2")

 

 

 

 

 

 

표현식을 아래처럼 활용 가능

 

val result = { val x = 6*25; x*100 }

result: Int = 15000

 

여기서 x는 표현식 내에서만 사용 가능하며

표현식 바깥에서는 접근 불가

 

 

 

 

 

if ( true ) println("a")

결과 : a

 

if (1 > 2) println("a") else println("b")

결과 : b

 

if 문과 대체하여 사용 가능한 match 표현식은 아래처럼 사용 가능

 

val max = (1>2) match {

  case true => 1

  case false => 2

}

결과 : 2

 

val message = 500 match {

  case 200 => "ok"

  case 400 => { println("400"); "error" }

  case 500 => { println("500"); "error" }

}

결과 : 500 이 출력되고 "error" 가 message 에 들어감

 

val kind = "WED" match {

  case "MON" | "TUE" | "WED" | "THU" | "FRI" => "weekday"

  case "SAT" | "SUN" => "weekend"

}

결과 : weekday

 

val ifstatement = -1 match {

  case x if x > 0 => "plus"

  case x if x < 0 => "minus"

  case x => "zero"

}

결과 : minus

 

val other = 300 match {

  case 200 => "ok"

  case other => "error"

}

결과 : error

 

val wilecard = 300 match {

  case 200 => "ok"

  case _ => "error"

}

결과 : error

 

타입으로도 매칭이 가능

val x:Int = 123

x match {

  case x: String => "String"

  case x: Int => "Int"

}

결과 : Int

 

 

 

 

 

 

for 루프는 일정 범위의 데이터를 반복하며, 반복할 때마다 표현식을 실행함

yield 를 추가하면 반환값들을 컬렉션으로 돌려줌

(각 원소마다 특정 표현식을 적용하고 반환하는 것이 마치 map 과 비슷함)

 

for ( i <- 1 to 5 ) { print(i+" ") }

결과 : 1 2 3 4 5

 

for ( i <- 1 until 5 ) { print(i+" ") }

결과 : 1 2 3 4

 

val result = for ( i <- 1 to 5 ) yield { i }
결과 : results: scala.collection.immutable.IndexedSeq[Int] = Vector(1,2,3,4,5)

 

for ( i <- result ) { print(i+" ") }
결과 : 1 2 3 4 5

 

for ( x <- 1 to 2 ; y <- 1 to 3 } { print(s"($x,$y)") }

결과 : (1,1)(1,2),(1,3)(2,1)(2,2)(2,3)

 

 

아래와 같이 for문 안에 조건식을 넣을 수 있음

 

for ( i <- 1 to 10 if i%2!=0) print(i+" ")

결과 : 1 3 5 7 9

 

아래와 같이 여러개의 조건절을 넣을 수 있으며, 조건들은 서로 and 로 엮임

 

for {

  c <- "a,bc,,d,,ef".split(",")

  if c != null

  if c.size > 1

} print(c+" ")

결과 : bc ef

 

아래와 같이 for 루프 안에서만 쓰이는 임시 변수를 지정하여 사용 가능

아래 예제의 x 는 for 루프가 반복 할 때마다 매번 정의되고 할당됨

 

for ( i <- 1 to 5 ; x = i * i ) print(x+" ")

결과 : 1 4 9 16 25

 

while 루프도 사용 가능함

 

var x = 5

while( x > 0 ) x-=1

결과 : x 는 0이 됨

 

 

 

 

 

 

 

순수 함수는 아래의 조건들을 만족하는 함수임

- 하나 이상의 입력 매개변수를 가짐

- 입력 매개변수만을 이용하여 계산 수행

- 값을 반환함

- 동일 입력에 대해 항상 같은 값을 반환

- 함수 외부의 어떤 데이터도 사용하거나 영향을 주지 않음

- 함수 외부 데이터에 영향을 받지 않음

 

순수 함수는 상태 정보를 유지하지 않으며, 외부 데이터에 관계없이 독립적임

본질적으로 순수함수는 변경될 수 없어서 안정적임

(마치 수학에서 사용하는 함수와 동일한 성질을 갖음)

Scala 프로그래밍을 하면서 순수 함수 비율을 많이 늘리는 게 좋다고 함

 

 

 

함수는 def 를 사용하여 선언하며, 선언문 다음에는 표현식이 옴

 

선언 : def hi = "hi"

사용 : hi

 

선언 : def hi = {"hi"}

사용 : hi

 

선언 : def hi:String = {val result = "eyeballs", result}

사용 : hi

결과 : eyeballs


선언 : def hi() = "hi"

사용 : hi 혹은 hi()

 

선언 : def hi(name:String) = "hi "+name

사용 : hi("eyeballs")

결과 : hi eyeballs

 

선언 : def hi(name:String):String = {

  if (name != null) return "hi "+name

  else return "it's null"

}

사용 : hi("eyeballs"), hi(null)

결과 : "hi eyeballs", "it's null"

 

선언 : def hi(name:String):String = {

  if (name != null) return "hi "+name

  "it's null"

}

사용 : hi("eyeballs"), hi(null)

결과 : "hi eyeballs", "it's null"

 

선언 : def hi(name:String, ch:Char) = "hi "+name+ch

사용 : hi("eyeballs", '!')

결과 : hi eyeballs!

 

선언 : def hi(name:String, ch:Char) = "hi "+name+ch

사용 : hi(name = "eyeballs", ch = '!') , hi(ch = '!', name = "eyeballs")

결과 : hi eyeballs!

 

선언 : def hi(name:String, ch:Char = '!') = "hi "+name+ch

사용 : hi("eyeballs"), hi(name = "eyeballs")

결과 : hi eyeballs!

 

선언 : def hi(name:String)(ch:Char) = "hi "+name+ch

사용 : hi("eyeballs")('!')

결과 : hi eyeballs!

 

아래처럼 인수 개수가 가변적일 때 * 를 사용하여 처리 가능

 

선언 : def sum(items: Int*): Int = {

  var total = 0

  for ( i <- items) total += i

  total

}

사용 : sum(0), sum(1,2), sum(1,2,3,4,5)

결과 : 0, 3, 15

 

아래처럼 사용시 표현식을 사용할 수 있음

아래 에제에서 myname 은 호출할 때만 잠깐 사용되는 값

 

선언 : def hi(name:String) = "hi "+name

사용 : hi {val myname="eyeballs"; hi(myname)}

결과 : hi eyeballs

 

프로시저란 반환값을 갖지 않는 함수이며, 이 때 함수의 반환값은 Unit 이 됨

예를 들어 아래 두 함수는 동일한 함수임

def log(d:Double) = println("value : "+d)

def log(d:Double):Unit = println("value : "+d)

 

아래처럼 함수 안에 함수 중첩 가능

Scala 는 오버로딩이 가능하기 때문에, 아래 예제의 파라미터 3개짜리 max와 파라미터 2개짜리 max 는 서로 다른 함수임

파라미터 2개짜리 max는 파라미터 3개짜리 max 내에서만 사용 가능 (max 함수 바깥에서 사용 불가)

 

선언 : def max(a:Int, b:Int, c:Int) = {

  def max(x:Int, y:Int) = if (x>y) x else y

  max(a, max(b, c))

}

사용 : max(1,2,3)

결과 : 3

 

Generic 처럼 함수 내에서 다루는 타입 자체를 사용시 직접 넣어줄 수 있음

이것을 타입 매개변수라고 부르며 [ ] 를 사용하여 선언함

마지막 예제처럼 타입 매개변수를 넣어주지 않아도 추론이 가능한 경우는 에러가 나지 않음

 

선언 : def identity[TYPE] (x:TYPE):TYPE = x

사용 : identity[Int](1), identity[String]("eyeballs"), identity[Double](0.1), identity("eyeballs")

결과 : 1, eyeballs, 0.1, eyeballs

 

 

 

 

 

 

클래스 내 메소드를 호출할 때 dot 을 사용할 수 있지만, white scape 를 사용하는 것도 가능

아래 세 가지는 모두 같은 의미를 갖음

"eyeballs".endsWith("s")

"eyeballs" endsWith("s")

"eyeballs" endsWith "s"

 

 

 

 

 

Scala 에서 함수는 일급 객체임

일급 객체란, 일반적인 데이터 타입처럼 언어의 모든 부분에 사용 가능한 객체를 말 함

일급 함수는 값, 변수 등의 컨테이너에 저장될 수 있고

다른 함수의 매개변수로 사용되거나

다른 함수의 반환값으로 사용될 수 있음

 

다른 함수를 매개변수로 받아들이거나

다른 함수를 반환값으로 반환하는 함수를 고차함수 라고 함

 

함수는 일급객체이기 때문에, 아래처럼 함수를 값에 넣을 수 있음

 

def hi = "hi"

val copyHi = hi

사용 : copyHi

 

def hi() = "hi"

val copyHi = hi

사용 : copyHi()


def hi(name:String) = "hi "+name

val copyHi = hi

사용 : copyHi("eyeballs")


def hi(name:String, ch:Char) = "hi "+name+ch

val copyHi = hi _

사용 : copyHi("eyeballs", '!')

 

def hi(name:String)(ch:Char) = "hi "+name+ch

val copyHi = hi _

사용 : copyHi("eyeballs")('!')

 

함수는 일급객체이기 때문에, 아래처럼 함수의 파라미터에 함수를 넣을 수 있음

 

def reverser(s: String) = s.reverse

def hi(name: String, f:String => String) = {

  if(name!=null) f(name)

  else name

}

사용 : hi("abcde" reverser), hi(null, reverser)

결과 : edcba, null

 

아래처럼 함수 리터럴(익명함수) 를 사용하여 바로 값에 할당 가능

 

선언 : val hi = (s: String) => s.reverse

사용 : hi("abc")

 

선언 : def hi(name: String, f:String => String) = { if(name!=null) f(name) else name }

사용 : hi("abcde", s => s.reverse) , hi("abcde", (s:String) => s.reverse)

 

선언 : val logging = () => "logging..."

사용 : println(logging())

 

하지만 아래처럼 함수 리터럴을 선언에 사용하는 것은 안 됨

def hi(name:String, s=>s.reverse) = {....} //안 됨 

def hi(name:String, (s:String)=>s.reverse) = {....} //안 됨

 

 

 

 

자리표시자 구문은 함수 리터럴의 축약형임

지정된 매개변수를 와일드카드( _ )로 대체함

함수의 명식적 타입이 리터럴 외부에 지정되어 있고, 매개변수가 한 번 이상 사용되지 않는 경우에만 자리표시자 사용 가능

 

예를 들어 다음과 같이 사용 가능

val double: Int => Int = _*2

Int => Int 를 통해 함수에 입력값이 int 인 것을 알려주었고,

함수 내부에서 _ 가 한 번만 사용되었음

 

비슷한 예로 아래와 같이 사용 가능

선언 : val rev: String => String = _.reverse

사용 : rev("eyeballs")

 

선언 : def hi(name: String, f:String => String) = { if(name!=null) f(name) else name }

일반 사용 : hi("abcde", s => s.reverse) , hi("abcde", (s:String) => s.reverse)

자리표시자 사용 : hi("abcde", _.reverse)

 

선언 : def combination(x:Int, y:Int, f:(Int,Int)=>Int) = f(x,y)

일반 사용 : combination(2,3, (x,y) => x*y)

자리표시자 사용 : combination(2,3, _ * _)

 

선언 : def combination[A,B](x:A, y:A, f:(A,A)=>B) = f(x,y)

일반 사용 : combination[Int,Double](2,3, (x,y) => x/y*1.0)

자리표시자 사용 : combination[Int,Double](2,3, _ / _ * 1.0)

 

 

 

함수를 val 에 넣을 때 파라미터를 고정시킨 후에 넣을 수 있음

이것을 부분 적용 함수라고 함

예를 들어, 아래 함수에 들어가는 x, y  두 파라미터 중 하나는 고정하고 싶다면,

자리표시자를 사용한 부분에만 파라미터를 받을 수 있게 만들면 됨

 

def factorOf(x:Int, y:Int) = y%x == 0

val multipleOf3 = factorOf(3, _:Int)

사용 : factorOf(3, 10), multipleOf3(10)

 

아래처럼 부분 적용 함수를 만들어서 val 에 넣을 수 있음

 

val f = factorOf _

val f = factorOf(_, _)

val f = factorOf(3, _)

val f = factorOF(3, _:Int)

 

def factorOf(x:Int)(y:Int) = y%x == 0

val multipleOf3 = factorOf(3) _

사용 : multipleOf3(4), multipleOf3 {val a=2; a*2}

 

 

 

 

 

 

함수에 "이름에 의한 매개변수(call by name)"를 사용하면

리터럴 값이 와도 되고, 함수가 와도 됨

예를 들어 f 라는 함수의 매개변수로 이름에 의한 매개변수를 넣음

def doubles(x: => Int) = { print(s"got ${x} from doubles"); x*2 }

 

이 doubles 함수에 넣을 파라미터로 2, 5 등의 리터럴 값이 들어갈 수 있음

doubles(2) //결과 got 2 from doubles, 4

doubles(5) //결과 got 5 from doubles, 10

 

또한 함수 자체가 들어갈 수 있음

def f(a:Int) = {println(s"got ${a} from f"); a}

doubles(f(4)) //결과 got 4 from f, got 4 from doubles, for 4 from f, 8

 

이렇게 들어간 f 는 doubles 내부에서 사용 될 때마다 호출됨

위 출력 결과에도 from f 가 두 번 떴음, 왜냐하면 doubles 내부에 x 가 두 번 사용되기 때문

 

 

 

 

case 를 추가하여 함수에 파라미터를 특정지어 처리할 수 있음

 

선언 :

val caseFunc : Int=>String = {

  case 1 => "one"

  case -1 => "minus one"

  case _ => "the others"

}

사용 : caseFunc(1), caseFunc(-1), caseFunc(3)

 

 

 

 

 

 

 

 

 

Scala 의 class 는 Java 와 마찬가지로 new 로 생성할 수 있음

 

선언 : class User

생성 : val user = new User

확인 : user.isInstanceOf[User]  //true

user.isInstanceOf[AnyRef]  //true

user.isInstanceOf[Any]  //true

 

선언 : 

class User {

  val name : String = "eyeballs"

  def greet : String = s"hello ${name}"

  override def toString = s"user name is ${name}"

}

생성 : val user = new User

사용 : user.greet, new User().greet

 

아래처럼 생성자를 넣을 수 있으며, 이를 클래스 매개변수라고 함

클래스 매개변수 n 은 메소드(greet, toString 등) 내부에서 사용 불가능함

클래스 매개변수n 은 단지 필드를 초기화하거나 메소드에 전달되는 용도로만 사용됨

 

class User(n: String) {

  val name : String = n

  def greet : String = s"hello ${name}"

  override def toString = s"user name is ${name}"

}

 

클래스 매개변수 n 앞에 val, var 를 붙이면

n 은 클래스의 필드가 되기 때문에, 내부 메소드에서도 사용 가능

 

class User(val n: String) {

  def greet : String = s"hello ${n}"

  override def toString = s"user name is ${n}"

}

 

class A {

  override

}

class B extends A

class C extends B {

  override def toString = "C : "+getClass.getName

}

val a:A = new B

bal b:B = new A //에러남. 자식은 부모를 받아줄 수 없음

 

선언 : 

class Car( val make: String, var reserved: Boolean ) {

  def reserve(r: Boolean): Unit = {reserved = r}

}

생성 : val t = new Car("eyeballs company", false), val t = new Car(make = "eyeballs company", reserved = false)

사용 : t.reserve(true)

 

선언 :

class Car( val make: String = "eyeballs company", var reserved: Boolean = true, val year: Int = 2024) {

  def reserve(r: Boolean): Unit = {reserved = r}

}

생성 : val t = new Car(), val t = new Car(year=2025)

 

선언 :

class myClass[A](element: A) {

  val a:A = element

  print(a.isInstanceOf[A])

}

생성 : 아래 모두 true 를 출력

val myClass = new myClass(1)

val myClass = new myClass[Int](1)
val myClass = new myClass("eyeballs")

val myClass = new myClass[String]("eyeballs")

 

 

추상 클래스는 abstract 를 사용하여 선언 가능하며

자기 자신은 인스턴스를 생성하지 않고 오로지 다른 클래스에 의해 상속되어지기만 하는 클래스임

 

선언 : 

abstract class Car {

  val year: Int

  val color: String

}

사용 : 

class myCar extends Car {

  val year = 2024

  val color = "Red"

}

class myCar(val year:Int = 2024) extends Car {

  val color = "Red"

}

 

추상 클래스를 상속하지 않고도, 생성함과 동시에 내용을 구현하는 것으로 사용 가능함

 

선언 : abstract class Car (val year:Int) { def show }

생성 : val myCar = new Car(2024) {

  def show { println(s"this car is ${year}" years old) }

  }

사용 : myCar.show

혹은

new Car(2024) { def show { println(s"this car is ${year}" years old) } }.show

 

class 내 오버로딩도 가능함

class MyClass {

  def print(a:String) = println(a)

  def print(a:Int) = println(a)

  def print(a:String, b:Int) = println(a+" "+b)

}

 

apply 라는 메소드를 구현하면, 사용할 때 메소드 이름을 사용하지 않고 생성한 클래스 이름 그대로 사용 가능

 

선언 : 

class Multiple(factor: Int){

  def apply (input: Int): Int = input * factor

}

생성 : val triple = new Multiple(3)

사용 : triple(7), triple.apply(7)   //둘 다 결과 21

 

필드에 lazy 를 사용하면, 그 필드가 인스턴스 될 때만 생성(구현)되도록 할 수 있음

즉, lazy 필드에 처음 접근 할 때 초기화(initial) 됨

 

선언 :

class Lazy {

  val x = { println("now initial"); 1 }

  lazy val y = { println("lazy initial"); 2 }

}

생성 :

val l = new Lazy()  // 여기서 "now initial" 이 출력됨

println(l.y)   // 여기서 lazy val y 가 사용되었으므로, "lazy initial" 이 출력됨

 

 

 

 

 

 

 

기본적으로 Scala 는 프라이버시 제어를 추가하지 않음

우리가 작성한 모든 클래스는 누구나 인스턴스를 생성할 수 있고

클래스 내부 필드와 메소드에 접근 가능

 

하지만 원한다면 프라이버시 제어를 추가할 수 있음

바로 필드 메소드 앞에 protected 혹은 private 을 추가하는 것임

 

protected 가 붙은 필드와 메소드는 동일 클래스 혹은 그 클래스의 자식 클래스에서만 접근 가능하게 됨

 

선언 :

class User { protected val password = "12345" }

class CheckUser extends User { def isValid = ! password.isEmpty }

사용 : 

new User().password   // 접근 불가 에러

new CheckUser().isValid   // 결과 true. 자식 클래스인 CheckUser 에서는 User 의 password 에 접근 가능

 

private 이 붙은 필드와 메소드는 이를 정의한 클래스에서만 접근 가능하게 됨

 

선언 :

class User { private val password = "12345" }

사용 : 

new User().password   // 접근 불가 에러

class CheckUser extends User { def isValid = ! password.isEmpty }   // 자식 클래스에서 접근 불가하여 선언 에러가 발생

 

 

패키지 단위로 접근을 제어할 수 있도록 할 수 있음

 

선언 : 

package com.eyeballs {

  private[eyeballs] class Config {   // com.eyeballs 패키지 내부에서만 접근 가능

    val url = "eyeballs.tistory.com"

  }

  class Test { println(new Config().url) }

}

 

사용 : 

new com.eyeballs.Test   // 결과 : eyeballs.tistory.com

new com.eyeballs.Config   // com.eyeballs 가 아닌 외부 패키지에서 Config 에 접근 불가하기 때문에 에러 발생

 

 

final 을 이용하여, 어떤 클래스의 자식 클래스를 만들지 못하도록 하거나

자식이 부모의 필드, 메소드를 재정의 할 수 없도록 할 수 있음

 

final class A

class B extends A   // A 의 자식을 만들 수 없어 에러 발생

 

class A { final val a = "a" }

class B extends A { val a = "b" }   // 부모 클래스의 필드인 A.a 를 재정의 할 수 없어 에러 발생

 

 

 

 

 

 

 

 

class 와 비슷하지만, 용도가 다른 object, case class 에 대해 설명함

 

object 는 하나 이상의 인스턴스를 가질 수 없는 형태의 class

singleton 이 적용된 class 라고 보면 됨

object 는 new 키워드로 인스턴스를 생성하지 않음

대신 이름으로 직접 해당 객체에 접근함

object 에 최초로 접근할 때 (JVM 내에서) 자동으로 인스턴스화 됨

인스턴스화는 자동으로 생성되므로, 초기화를 위한 매개변수는 갖지 않음 (대신 apply 메소드에 넣을 매개변수는 갖을 수 있음)

 

object 는 다른 class 를 상속받을 수 있음

하지만 다른 class 가 object 를 상속받을 수 없음

왜냐면 object 의 필드, 메소드는 전역에서 접근 가능하므로, 자식 클래스를 만들 이유가 없기 때문

 

선언 : object Hi { println("call Hi"); def hi = "hi" }

사용 : println(Hi.hi)

결과 : call Hi, hi

 

Hi.hi 를 여러번 사용시, 최초 생성된 인스턴스가 재사용됨

singleton 성격을 갖기 때문에, 순수 함수를 구현하거나 DB 를 사용하는 I/O 함수, sparkSession 을 설정하는 용도 등으로 사용

 

class 와 이름이 같은 object 를 동반 객체 라고 부름

동반 객체에서는 class 의 private, protected 필드 및 메소드에 접근 가능함

 

선언 : 

class Multiplier(val x: Int) { def product(y:Int) = x*y }

object Multiplier { def apply(x: Int) = new Multiplier(x) }

 

사용 : 

val tripler = Multiplier(3)   // object 사용

val result = tripler.product(10)   // 결과 30

 

 

 

 

 

 

 

 

case class 는 자동으로 생성된 메소드 몇 가지를 갖은 상태로 (인스턴스가) 생성되는 클래스

case class 는 동반 객체도 자동으로 생성하며, 이 동반 객체도 자신만의 메소드를 자동으로 생성해둠

case class 는 주로 데이터를 저장하고 전송하는 역할로 사용되며

계층적인 클래스 구조를 위해 사용되지 않는 편

왜냐하면 자동으로 만든다는 그 메소드들은 상속받은 필드들은 고려하지 않기 때문

 

자동으로 만들어진다는 메소드들은 다음과 같음

 

이름 위치 설명
apply object (동반 객체) case class 를 인스턴스화하는 팩토리 메소드
copy class 요청받은 변경사항이 반영된 인스턴스의 사본을 반환. 매개변수는 현재 필드값으로 설정된 기본값을 갖는 클래스의 필드들
equals class 다른 인스턴스의 모든 필드가 이 인스턴스의 모든 필드와 일치하면 true 반환. 연산자 == 로도 호출 가능
hashCode class 인스턴스의 필드들의 해시 코드를 반환. 해시 기반의 컬렉션에 유용..
toString class 클래스명과 필드들을 모아 String 으로 반환
unapply object (동반 객체) 인스턴스를 그 인스턴스의 필드들의 튜플로 추출하여 패턴 매칭에 케이스 클래스 인스턴스를 사용할 수 있도록 함

 

선언 : case class Character (name: String, age: Int)

사용 : 

val a = Character ("AA", 2)

val b = a.copy(name="BB")

a == b   // false

 

 

 

 

 

 

 

 

리스트(List). 한 번 생성되면 내부 값을 바꿀 수 없음

내부에서 Linked List 로 구현되어 있음

 

val myList = List()

val myList = List(1,2,3)

val myList = List("a", "b", 1, 2)

myList(0)   // "a"

myList(1)   // "b"

myList(-1)   // error

myList(10)   // error

myList.size  // 4

myList.isEmpty  // false

myList == Nil   // false. 여기서 Nil 은 빈 값을 갖는 리스트인 List() 의 싱글톤 인스턴스

Nil == List()   // true

myList.head   // "a"

val tailList = myList.tail   // "b", 1, 2

for ( l <- myList ) { print(l+" ") }   // a b 1 2

 

foreach 는 함수를 취하고, 그 함수를 리스트의 모든 항목으로 호출함

myList.foreach( l => print(l+" ") )   // a b 1 2

 

map 은 단일 리스트 요소를 다른 값이나 타입으로 전환하는 함수를 취함

val newList = myList.map(l => "["+l+"]")   // [a], [b], [1], [2]

 

reduce 는 리스트 요소들을 앞에서부터 차례대로 두 개씩 선택한 후, 단일 항목으로 결합하는 함수를 취함

val combination = myList.reduce((a,b) => a+" "+b)   // "a b 1 2"

 

리스트를 생성하는 또 다른 방법은 :: 를 사용하는 것

val myList = 1 :: 2 :: 3 :: Nil

val newList = 0 :: myList   //0,1,2,3

 

혹은 두 리스트를 ::: 로 붙이는 것

val twoList = List(1,2) ::: List(3,4)   // 1,2,3,4

 

리스트에 ++ 를 사용하여 다른 컬렉션(이를테면 Set)을 붙일 수 있음

val twoCollections = List(1,2) ++ Set(3,3,3)  // List(1,2,3)

 

:+, +: 를 사용하여 간단하게 List 에 요소를 늘릴 수 있음

List(1,2,3) :+ 4   // List(1,2,3,4). Linked List 의 마지막까지 도달해야하기 때문에 성능 이슈 발생 가능

1 +: List(2,3,4)   // List(1,2,3,4)

 

== 를 사용하여 컬렉션(리스트, 집합.. 등) 간 비교 가능. 두 컬렉션의 타입과 내용이 같으면 true

List(1,2) == List(1,3)   // false

 

drop 으로 List 에서 처음의 n 개 요소를 제외함

val droppedList = List(1,2,3,4) drop 2   //List(3,4)

 

dropRight 로 List 에서 마지막의 n 개 요소를 제외함. Linked List 의 마지막 요소까지 순회해야하므로, 성능 이슈 발생 가능

val droppedList = List(1,2,3,4) dropRight 2   //List(1,2)

 

List 에서 distinct 로 중복 제거

List(1,2,1,2).distinct   // 1, 2

 

List 에 filter 추가하여 true 인 것만 남길 수 있음

val filteredList = List(1,2,3,4,5) filter (_>2)   // 3,4,5

 

nested List 가 포함된 경우, flatten 을 이용하여 내부 요소들을 모두 포함하는 단일 리스트를 만들 수 있음

List(List(1,2), List(3)).flatten   //1,2,3

 

근데 List 가 아닌 리터럴 값이 포함되어 있으면 에러가 발생함

List(List(1,2), List(3), 4).flatten   // error

 

partition 을 사용하여 조건의 참에 해당하는 리스트와 거짓에 해당하는 리스트 두 개를 만듦 (결과는 튜플이 됨)

val part = List(1,2,3,4,5) partition (_<3)

part._1 은 List(1,2)  // true 인 값들

part._2 는 List(3,4,5)   // false 인 값들

 

splitAt 을 사용하여 인덱스 기준으로 List 를 좌우로 쪼갬. 결과는 튜플이 됨

val split = List(1,2,3,4) splitAt 2

split._1 은  List(1,2)

split._2 는 List(3,4)

 

reverse 를 사용하여 List 요소의 순서를 뒤집음

List(1,2,3).reverse   // List(3,2,1)

 

slice 를 사용하여 List 요소의 단편만 가져옴. <=_<

List(1,2,3,4,5) slice (0,0)   // List()

List(1,2,3,4,5) slice (0,1)   // List(1)

List(1,2,3,4,5) slice (0,2)   // List(1,2)

List(1,2,3,4,5) slice (1,2)   // List(2)

List(1,2,3,4,5) slice (2,1)   // List()

 

take 를 사용하여 List 의 처음 n 개 요소만 추출함

List(1,2,3) take 2   // List(1,2)

 

takeRight 를 사용하여 List 의 마지막 n 개 요소만 추출함. Linked List 마지막 요소까지 순회해야 하므로 성능 이슈 발생 가능

List(1,2,3) takeRight 2   // List(2,3)

 

sorted 를 사용하여 List 요소를 정렬함. 사전 순 혹은 오름차순

List(3,2,1).sorted   // List(1,2,3)

List('c','b','a').sorted   // List('a','b','c')

 

sortBy 를 사용하여 원하는 기준으로 List 요소를 정렬함

List("abc","de","f") sortBy (_.size)   // List("f", "de", "abc")

 

zip 을 사용하면, 두 List 를 각 인덱스에 해당하는 요소들끼리 묶인 튜플의 리스트로 만들 수 있음

val z = List(1,2) zip List('a','b')

z 는 List( (1,a), (2,b) )

 

collect 와 case 를 사용하면, List 안의 요소들을 case 에 매칭된 것만 남기고, case 의 내용대로 변환함

List("a", "b", "c") collect {

  case "a" => "A"

}

결과 : List("A")

 

List("a", "b", "c") collect {

  case "a" => "A"

  case "b" => "B"

  case _ => "nothing"

}

결과 : List("A", "B", "nothing")

 

map 을 사용하면, List 안의 모든 요소들에 특정 함수를 적용한 결과값으로 치환함

List("a", "b", "c").map(_.toUpperCase)

결과 : List("A", "B", "C")

 

flatMap 을 사용하면, map 처럼 List 안의 각 요소들에 특정 함수를 적용한 결괏값으로 치환하지만, 

map 과 다르게 모든 결과를 평탄화하여 하나의 List 로 만들어줌

 

List("a,b,c","d,e,f").flatMap(_.split(","))

결과 : List("a","b","c","d","e","f")

 

List("a,b,c","d,e,f").map(_.split(","))

결과 : List(Array("a","b","c"), Array("d","e","f"))

 

List(1,2,3).max  // 3 최댓값

List(1,2,3).min  // 1 최솟값

List(1,2,3).product  // 6 모두 곱하기

List(1,2,3).sum  // 6 모두 더하기

 

contains 를 사용하여 List 내 요소를 포함하고 있는지 확인 가능

List(1,2,3) contains 2   // true

 

exists 를 사용하여 List 내 최소 하나의 요소가 조건자에 성립하는지 확인 가능

List(1,2,3).exists(_<2)   // true

List(1,2,3).exists(_<1)   // false

 

forall 을 사용하여 List 내 모든 요소가 조건자에 성립하는지 확인 가능

List(1,2,3).exists(_<2)   // false

List(1,2,3).exists(_<=3)   // true

 

startsWith 를 사용하여 List 의 처음 요소들이 특정 값을 갖는 List 로 시작하는지 확인 가능

List(1,2,3) startsWith List(1)   // true

List(1,2,3) startsWith List(1,2)   // true

List(1,2,3)startsWith List(1,3)   // false

 

endsWith 를 사용하여 List 의 마지막 요소들이 특정 값을 갖는 List 로 끝나는지 확인 가능

List(1,2,3) endsWith List(3)   // true

List(1,2,3) endsWith List(2,3)   // true

List(1,2,3) endsWith List(1,3)   // false

 

아래서부터 reduce 처럼 List의 값을 하나로 축소하는 함수에 대해 설명함

 

foldLeft 를 사용하여, List 를 주어진 시작값과 함께 왼쪽에서부터 축소

List(1,2,3).foldLeft(0)(_-_)   // -6

이유 :

0 - 1 = -1

-1 - 2 = -3

-3 - 3 = -6

List(1,2,3).foldLeft(1)(_-_)   // -5

List(1,2,3).foldLeft(2)(_-_)   // -4

 

foldRight 를 사용하여, List 를 주어진 시작값과 함께 오른쪽에서부터 축소

List(1,2,3).foldRight(0)(_-_)   // 2

이유 : 

3 - 0 = 3

2 - 3 = -1

1 - -1 = 2

 

List(1,2).foldRight(0)(_-_)   // -1

이유 : 

2 - 0 = 2

1 - 2 = -1

 

reduceLeft 를 사용하여 List 를 첫번째 요소값과 함께 왼쪽에서부터 축소

List(1,2,3).reduceLeft(_-_)   // -4

이유 : 

1 - 2 = -1

-1 - 3 = -4

 

reduceRight 를 사용하여 List 를 마지막 요소값과 함께 오른쪽에서부터 축소

List(1,2,3).reduceRight(_-_)   // 2

이유 : 

2 - 3 = -1

1 - -1 = 2

 

scanLeft 를 사용하여 List 를 주어진 시작값과 함께 왼쪽에서부터 처리한 각 누곗값의 List 를 반환

List(1,2,3).scanLeft(0)(_-_)   // List(0, -1, -3, -6)

이유 : 

처음 주어진 값 = 0 

0 - 1 = -1

-1 - 2 = -3

-3 - 3 = -6

 

scanRight 를 사용하여 List 를 주어진 시작값과 함께 오른쪽에서부터 처리한 각 누곗값의 List 를 반환

List(1,2,3).scanRight(0)(_-_)   // List(2, -1, 3, 0)

이유 : 

처음 주어진 값 = 0

3 - 0 = 3

2 - 3 = -1

1 - -1 = 2

 

reduceLeft, reduceRight 처럼 방향성이 있는 것과

그냥 reduce 처럼 방향성이 없는 것에 차이가 존재함

이를테면, 아래와 같은 연산을 진행할 때

방향이 존재하는 foldLeft, foldRight 는 실행 가능하고 

방향이 존재하지 않는 fold 는 실행이 불가능함

List(1,2,3).foldLeft(false) {(a,b) => if(a) a else b==2}   // true

List(1,2,3).foldLeft(false) {(a,b) => if(a) a else b==4}   // false

List(1,2,3).foldRight(false) {(a,b) => if(b) b else a==2}   // true

List(1,2,3).foldRight(false) {(a,b) => if(b) b else a==4}   // false

List(1,2,3).fold(false) {(a,b) => if(a) a else b==2}   // error

 

 

 

 

 

 

 

 

 

 

집합(Set). 한 번 생성되면 내부 값을 바꿀 수 없음 

 

val mySet = Set()

val mySet = Set(1,2,3)

val mySet = Set("a","b",1,1,2,2)   //"a", "b", 1, 2

 

 

 

 

 

 

 

 

 

 

 

Map (Java 의 HashMap, Python 의 dictionary). 이 역시 생성된 후 내부 값 변경이 불가능

 

val myMap = Map()

val myMap = Map(1->"a", 2->"b")

myMap(1)   // "a"

myMap(2)   // "b"

myMap.contains(1)   // true

myMap.contains(3)   // false

for ( pairs <- myMap ) {

  val key = pairs._1

  val value = pairs._2

  println(key+" "+value)

}

결과 : 1 "a", 2 "b"

 

 

 

 

 

collection 간 전환은 아래와 같이 가능함

 

mkString 를 사용하여 collection 을 구분자로 구분된 String 으로 전환

List(1,2,3).mkString("-")   //"1-2-3"

Set(1,2,3).mkString("-")   //"1-2-3"

 

toBuffer 를 사용하여 collection 을 가변의 List 로 전환

List(1,2,3).toBuffer   // Buffer(1,2,3)

Map(1->1, 2->2, 3->3).toBuffer   // Buffer((1,1), (2,2), (3,3))

 

toList 를 사용하여 collection 을 불변의 List 로 전환

Set(1,2,3).toList   // List(1,2,3)

 

toMap 을 사용하여 튜플이 담긴 collection 을 Map 으로 전환

Set((1,1), (2,2), (3,3)).toMap   // Map(1->1, 2->2, 3->3)

 

toSet 을 사용하여 collection 을 Set 으로 전환

List(1,1,2,2).toSet   // Set(1,2)

 

toString 을 사용하여, collection 의 타입과 내용을 String 으로 전환

List(1,2,3).toString   // "List(1,2,3)"

Set(1,2,3).toString   // "Set(1,2,3)"

 

 

JVM 을 사용하는 Scala 와 Java 간 collection 은 기본적으로 서로 호환되지 않지만

asJava, asScala 를 통해 호환되도록 만들 수 있음

 

import collection.JavaConverters._

val scalaList = List(1,2,3)

scalaList: List[Int] = List(1,2,3)

val javaList = scalaList.asJava

javaList: java.util.List[Int] = [1, 2, 3]

val scalaListAgain = javaList.asScala

scalaListAgain: scala.collection.mutable.Buffer[Int] = Buffer(1,2,3)

 

 

 

 

 

 

collection 을 match 표현식에 사용하는 방법 예제

 

val myList = List(1,2,3)

 

myList(0) match {

  case 1 => "A"

  case _ => "B"

}

결과 : "A"

 

myList(0) match {

  case x if x > 0 => "A"

  case _ => "B"

}

결과 : "A"

 

myList match {

  case x if x contains (2) => "A"

  case _ => "B"

}

결과 : "A"

 

myList match {

  case List(1,

 

 

 

 

 

 

 

call by name, call by ref 차이는?

 

 

still remember I said I'was strong and wan't tired after swimming.

but, turns out, It was really wrong.

after finishing my work and dinner at 7:30, I don't have any energy to do something.

Well, I thought 1 hour between dinner and swimming class was enough to study English or Computer Science.

Now I just lay down on my sofa and watch youtube. thats all.

since Thursday, I didn't want to go to the swimming class....

9PM class ruins my schedule and sucks my energy a lot.

but while taking the class It's very fun to swim.

Thank God It's Friday. really.

during the weekend I don't need to take the class and make time to study English.

Now I'm at a cafe to review this weekend and rearrange my life's dirention.

 

 

 

this is the result chatgpt reviewed.

 

I still remember saying I was strong and wasn't tired after swimming.

It turns out I was really wrong.

After finishing my work and dinner at 7:30PM, I don't have any energy to do anything.

I thought one hour between dinner and my swimming class would be enough to study English or Computer Science.

But now, I just lay down on my sofa and watch youtube. That's all.

Since Thursday, I haven't wanted to go to the swimming class.

The 9PM class ruins my schedule and drains my energy.

But, while taking the class, It's very fun to swim.

Thank God It's Friday. Really.

During the weekend, I don't need to take the class and can make time to study English.

Now, I'm at a cafe to review this weekend and rearrange my life's direction.

 

'English' 카테고리의 다른 글

개발 영어 공부  (0) 2025.02.09
Study English 24.06.29-07.02  (0) 2024.07.02
Study English 24.06.28  (0) 2024.06.29
Study English 24.06.27  (0) 2024.06.27
Study English 24.06.26  (0) 2024.06.26

+ Recent posts