데이터 과학자를위한 3 가지 훌륭한 디자인 패턴

더 나은 데이터 과학 코드를 작성하는 방법을 배우고 싶으십니까? 디자인 패턴을 사용하여 깨끗하고 유지 관리 가능하며 테스트 가능한 코드를 작성하십시오.

소개

데이터 과학자로서 코드를 작성할 때 목표는 작업을 빠르게 작성하여 너무 멀어지기 전에 어떤 것이 좋은 아이디어인지 여부를 확인할 수 있도록하는 것입니다. 아무도 그것이 쓰레기라는 것을 알기 위해 프로젝트에 몇 달을 보내는 것을 좋아하지 않습니다.

따라서 프로토 타이핑시 가능한 한 빨리 코드를 작성합니다. 하지만 지금 당장 작동하는 코드가 더 이상 코드를 자르지 않고 코드가 더 강력하고 유지 관리 가능해야 할 때 어떻게 될까요? 이것은 디자인 패턴이 유용한 곳입니다.

게시물 이미지
Mematic을 사용하여 만든 Richie Frost의 Meme

디자인 패턴이란 무엇입니까?

내가 말하면 마에 단순히 디자인 패턴은 소프트웨어를 작성하는 일반적인 문제에 대한 일반적인 솔루션입니다. 그것들을 그렇게 훌륭하게 만드는 것은 그것들이 보편적으로 적용 가능하다는 것입니다. 그러나 그것들을 어떻게 적용해야하는지 알아야합니다. 여기서 몇 가지 일반적인 디자인 패턴에 대해 자세히 알아볼 수 있습니다 .

디자인 패턴을 사용하는 이유는 무엇입니까?

나는 그들을 사용하는 것을 좋아하는 몇 가지 이유를 생각할 수 있습니다.

  • 코딩하는 동안인지 부하 감소
  • 디버깅 시간 단축
  • 코드는 훨씬 더 테스트 가능합니다.
  • 재사용 가능한 도구를 더 쉽게 구축

따라서 더 이상 고민하지 않고 데이터 과학 워크 플로를위한 3 가지 훌륭한 디자인 패턴을 살펴 보겠습니다.

1. 빌더 패턴

빌더 패턴은 복잡한 개체를 만드는 유연한 방법입니다. 특히 이러한 개체가 많은 유사점을 공유하지만 많은 선택적 매개 변수가있는 경우에 그렇습니다. 빌더 패턴은 객체 자체에서 객체 생성 로직을 가져와 대신에 종종 메소드 체인 기술 을 사용하여 즉석에서 객체에 대한 관련 속성을 생성 합니다. 메서드 체인을 활성화하는 핵심은 원하는 개체를 빌드하는 데 사용 된 메서드에서 개체 자체를 반환하여 연결된 메서드가 동일한 개체를 수정할 수 있도록하는 것입니다.

저는 매일 수많은 SQL 쿼리를 작성했으며 대부분의 쿼리와 구조가 유사하다는 것을 발견했습니다. 그러나 수동으로 작성하면 오류가 발생하기 쉽고 많은 중복 코드가 생성됩니다. 따라서 수십 개의 개별 쿼리를 작성하는 대신 빌더 패턴을 사용하여 쿼리를 생성합니다. 이것은 또한 중첩 된 select 문과 여러 조인을 사용하여 크고 불쾌한 쿼리를 작성할 때 유용합니다.이 경우 잡초에서 길을 잃고 쿼리를 직접 작성할 때 실수를하기 쉽습니다. 이 방법은 쉽게 테스트 할 수 있지만 SQL 쿼리를 직접 작성하는 것은 테스트하기가 더 어렵습니다.

이 패턴이 어떻게 유용 할 수 있는지 설명하기 위해 간단한 쿼리 작성기를 작성해 보겠습니다.

먼저 튜플을 선택할 기본 테이블로 빌더를 초기화합니다. 그런 다음 필요에 따라 선택할 열, '그룹 별'절, 조인 및 '위치'절을 추가 할 수 있습니다. 이것은 단순한“SELECT * FROM foo”유형의 쿼리에는 과잉이지만 이러한 빌딩 블록을 사용하면 점점 더 복잡한 쿼리를 쉽게 작성할 수 있습니다.

다음은 빌더 패턴을 사용하여 간단한 SQL 쿼리 생성기를 만드는 예입니다.

class QueryBuilder:
     def __init__(self):
          self.select_value = ''
          self.from_table_name = ''
          self.where_value = ''
          self.groupby_value = ''

     def select(self, select_arg):
          self.select_value = select_arg
          return self

     def from_table(self, from_arg):
          self.from_table_name = from_arg
          return self
          
     def where(self, where_arg):
          self.where_value = where_arg
          return self
          
     def groupby(self, groupby_args):
          self.groupby_value = groupby_args
          return self
          
     def build(self):
          if self.where_value:
               where_clause = f'WHERE {self.where_value}'
          if self.groupby_value:
               groupby_clause = f'GROUP BY {self.groupby_value}'

          return f"""
               SELECT {self.select_value}
               FROM {self.from_table_name}
               {where_clause}
               {groupby_clause}
          """

# Simple builder pattern example for building queries
query = QueryBuilder()
query.select('Customer, Region, SUM(SaleValue)') \
     .from_table('Sales') \
     
     # Optional - add where and groupby clauses. 
     # Object construction can easily be either simple or complex
query.where('DATEDIFF(day, TimeStamp, CURRENT_TIMESTAMP) < 180') \
     .groupby('Customer, Region')

# Builder pattern collects the optional arguments and builds the actual SQL text
query_text = query.build()

"""
query_text value:
SELECT Customer, Region, SUM(SaleValue)
FROM Sales
WHERE DATEDIFF(day, TimeStamp, CURRENT_TIMESTAMP) < 180
GROUP BY Customer, Region
"""
빌더 패턴을 사용하여 SQL 쿼리 생성

2. 의존성 주입

가장 간단한 형태로 의존성 주입은 의존하는 것을 인수로 삽입하는 것입니다. 사용할 데이터베이스 클래스를 모르십니까? 함수는 데이터베이스 클래스가 작동하는 방식을 알 필요가 없습니다. 데이터베이스 클래스 인스턴스를 인수로 전달하면 유지 관리가 더 쉬워집니다. 동일한 인터페이스를 따르는 모든 종류의 데이터베이스 클래스를 사용할 수 있습니다.

종속성 주입을 사용하지 않으면 데이터베이스 클래스와 같은 중요한 인프라를 유지 관리하는 데 훨씬 더 많은 시간이 걸립니다.

의존성 주입을 사용하는 또 다른 큰 이점은 코드가 테스트를 작성하기가 훨씬 쉽다는 것입니다. 예를 들어 HTTP 요청을 실행하고 테스트 속도를 늦추는 코드를 사용하지 않고 모의 클래스 (예 : 모의 데이터베이스 클래스)를 작성하고 테스트에서 사용하십시오.

우리 팀은 SQL Server와 Cosmos DB 및 기타 데이터 원본을 모두 사용합니다. 데이터베이스 클래스를 인수로 전달하면 서로 다른 데이터베이스를 서로 다른 아이디어로 쉽게 교체 할 수 있으며 데이터베이스 클래스는 모의하기 쉽기 때문에 테스트 가능한 코드를 훨씬 쉽게 작성할 수 있습니다.

다음은 종속성 주입을 사용하는 간단한 예입니다.

# Don't do this
def get_data_bad(query_text):
    db = SQLDB()
    return db.get(query_text)
    
# What if you need to use a DocDB instance? Or a DynamoDB instance?
# Do this instead
def get_data(db, query_text):
    return db.get(query_text)
    
# Example
sqldb = SQLDB()
query = 'SELECT * FROM Foo'
data = get_data(sqldb, query)

# Or, if you need to use DocDB instead, you don't need to change your original get_data method
docdb = DocDB()
query = 'SELECT c.* FROM c'
data = get_data(docdb, query)
종속성 주입 패턴을 사용하여 데이터베이스에서 데이터 가져 오기

3. 데코레이터 패턴

데코레이터 패턴은 함수 전후에 무언가를하고 싶지만 함수 자체를 수정하고 싶지 않을 때 유용합니다. 기본적으로 여러분이하고있는 일은 함수가 실행되기 전에 어떤 상태를 캡처 한 다음 완료된 후 어떤 상태를 캡처하는 것입니다. 같은 방식으로 수정할 함수가 수십 개 있지만 개별적으로 변경할 여유가 없을 때 이는 매우 분명해집니다.

내가 유용하다고 생각한 것은 함수가 실행되는 시간, 함수의 이름, 때로는 출력에 대한 다른 기능입니다. 고맙게도 Python 함수는 객체이므로이 패턴에 '@'데코레이터 구문을 사용할 수 있습니다. 내부 함수를 래핑하는 함수를 생성 한 다음 데코레이션하려는 함수 앞에 @my_decorator_name 데코레이터를 배치하기 만하면됩니다.

평범한 영어로 설명하는 것보다 예를 보는 것이 더 쉽습니다. :)

파이썬에서 데코레이터가 작동하는 방식에 대해 너무 깊이 이해하지는 않겠지 만 RealPython은 입문서로 강력히 추천 하는 훌륭한 기사를 가지고 있습니다.

종속성 주입 예제의 일부 코드를 재사용하면 데이터베이스 트랜잭션에 걸리는 시간을 측정 할 수 있습니다.

from time import time

def log_time(func):
    """Logs the time it took for func to execute"""
    def wrapper(*args, **kwargs):
        start = time()
        val = func(*args, **kwargs)
        end = time()
        duration = end - start
        print(f'{func.__name__} took {duration} seconds to run')
        return val
    return wrapper
  
# Example usage
@log_time
def get_data(db, query):
    """Gets data from a SQL-based database"""
    data = db.get(query)
    return data
  
if __name__ == '__main__':
    # Decorated function will print 'get_data took X seconds to run'
    db = SQLDB()
    query = 'SELECT * FROM foo'
    data = get_data(db, query)
데코레이터 패턴을 사용하여 함수 시간 측정

합치기

디자인 패턴은 매우 재사용 가능한 코드를 만들고, 데이터 과학자로서 작업을 훨씬 쉽게 만들기 위해 빌딩 블록과 같은 조각을 모을 수 있습니다. 예를 들어, 이러한 세 가지 패턴을 모두 결합하여 데이터베이스에 쿼리를 작성하고 최적화가 필요한지 확인하기 위해 쿼리에 소요 된 시간을 확인합니다.

결론

이 기사에서는보다 강력하고 유지 관리가 쉬운 코드를 위해 데이터 과학자로서 디자인 패턴을 사용하는 세 가지 방법을 보여주었습니다. 데이터 과학에서 디자인 패턴을 사용하면 코드 품질이 향상되고 유지 관리가 쉬워지며 결과를 재현하고 공유하기가 더 쉬워집니다.

 

[출처] https://towardsdatascience.com/3-great-design-patterns-for-data-science-workflows-d3bf162d74e6

 

3 Great Design Patterns for Data Scientists

Want to learn how to write better data science code? Use design patterns to write clean, maintainable, testable code.

Introduction

When writing code as a data scientist, your goal is often to write things quickly so that you can vet whether or not something is a good idea before you get too far down the road. Nobody likes to spend months working on a project only to find out that it’s garbage.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

So you write your code as quickly as possible when prototyping. But what happens when your just-get-it-working-for-now code isn’t cutting it anymore, and your code needs to be more robust and maintainable? This is where design patterns come in handy.

Image for post
Meme by Richie Frost, created using Mematic

What are design patterns?

To put it simply, design patterns are common solutions to common problems when writing software. What makes them so great is that they’re so universally applicable, but you have to know how to apply them. You can learn more in-depth about some common design patterns here.

Why use design patterns?

I can think of a couple of reasons that I love using them.

  • Reduce cognitive load while coding
  • Spend less time debugging
  • Code is much more testable
  • Easier to build reusable tools

So, without further ado, let’s get into 3 great design patterns for data science workflows.

1. The Builder Pattern

The builder pattern is a flexible way of creating complex objects, especially when these objects share a lot of similarities but have a lot of optional parameters. The builder pattern takes the object construction logic out of the object itself, and instead creates relevant properties for the object on the fly — often by using the method chaining technique. The key to enabling method chaining is to return the object itself from methods used to build the object you want, so that chained methods can modify the same object.

I write a ton of SQL queries day to day, and found that there’s a lot of similarity in structure to most of my queries. However, writing them by hand is a fairly error-prone process and creates a lot of duplicated code. So rather than writing dozens of individual queries, I use the builder pattern to generate queries for me. This also comes in handy a lot when I write big, nasty queries with nested select statements and multiple joins, where it’s easy to get lost in the weeds and make mistakes when writing queries by hand. Not to mention this method is easily testable, whereas writing SQL queries by hand is harder to test!

Let’s write a simple query builder to illustrate how this pattern can be useful.

I first initialize the builder with the base table from which I’ll be selecting tuples. Then I can add columns to select, ‘group by’ clauses, joins, and ‘where’ clauses as I need them. This is overkill for a simple “SELECT * FROM foo” type of query, but these building blocks make it easier to build more and more complex queries.

Here’s an example of using the builder pattern to make a simple SQL query generator:

class QueryBuilder:
     def __init__(self):
          self.select_value = ''
          self.from_table_name = ''
          self.where_value = ''
          self.groupby_value = ''

     def select(self, select_arg):
          self.select_value = select_arg
          return self

     def from_table(self, from_arg):
          self.from_table_name = from_arg
          return self
          
     def where(self, where_arg):
          self.where_value = where_arg
          return self
          
     def groupby(self, groupby_args):
          self.groupby_value = groupby_args
          return self
          
     def build(self):
          if self.where_value:
               where_clause = f'WHERE {self.where_value}'
          if self.groupby_value:
               groupby_clause = f'GROUP BY {self.groupby_value}'

          return f"""
               SELECT {self.select_value}
               FROM {self.from_table_name}
               {where_clause}
               {groupby_clause}
          """

# Simple builder pattern example for building queries
query = QueryBuilder()
query.select('Customer, Region, SUM(SaleValue)') \
     .from_table('Sales') \
     
     # Optional - add where and groupby clauses. 
     # Object construction can easily be either simple or complex
query.where('DATEDIFF(day, TimeStamp, CURRENT_TIMESTAMP) < 180') \
     .groupby('Customer, Region')

# Builder pattern collects the optional arguments and builds the actual SQL text
query_text = query.build()

"""
query_text value:
SELECT Customer, Region, SUM(SaleValue)
FROM Sales
WHERE DATEDIFF(day, TimeStamp, CURRENT_TIMESTAMP) < 180
GROUP BY Customer, Region
"""
Using the builder pattern to generate SQL queries

2. Dependency injection

In its simplest form, dependency injection is when you insert the thing you’re depending on as an argument. Don’t know which database class to use? Your function doesn’t need to know how the database class works, just that it does. Passing in the database class instance as an argument makes it easier to maintain — you can use any kind of database class that follows the same interface.

Without using dependency injection, you’ll have a much harder time maintaining critical infrastructure like database classes.

One other great benefit of using dependency injection is that your code is much easier to write tests for. Just write a mock class (i.e. a mock database class) and use that in your tests, rather than having to use code that runs HTTP requests and slows down tests, for example.

My team uses both SQL Server and Cosmos DB, as well as other data sources. Passing in the database class as an argument makes it easy to swap out different databases for different ideas, and makes writing testable code a lot easier, since database classes are easy to mock.

Here’s a simple example of using dependency injection:

# Don't do this
def get_data_bad(query_text):
    db = SQLDB()
    return db.get(query_text)
    
# What if you need to use a DocDB instance? Or a DynamoDB instance?
# Do this instead
def get_data(db, query_text):
    return db.get(query_text)
    
# Example
sqldb = SQLDB()
query = 'SELECT * FROM Foo'
data = get_data(sqldb, query)

# Or, if you need to use DocDB instead, you don't need to change your original get_data method
docdb = DocDB()
query = 'SELECT c.* FROM c'
data = get_data(docdb, query)
Using the dependency injection pattern to get data from a database

3. The Decorator Pattern

The decorator pattern is useful when you want to do something before and/or after a function, but don’t want to modify the function itself. Essentially, what you’re doing is capturing some state before your function runs, then capturing some state after it’s done. This becomes very apparent when you have dozens of functions to modify in the same way, but can’t afford to change them individually.

Things that I’ve found useful are how long the function runs, the function’s name, and sometimes different features about the output. Thankfully, Python functions are objects, so you can use the ‘@’ decorator syntax for this pattern. All you need to do is create a function that wraps an inner function, then place the @my_decorator_name decorator before the function you want to decorate.

It’s easier to see an example than to explain it with plain English :)

I won’t get too deep into how decorators work in Python, but RealPython has a great article I highly recommend as a primer.

Reusing some of the code from the dependency injection example, we can time how long our database transaction would take:

from time import time

def log_time(func):
    """Logs the time it took for func to execute"""
    def wrapper(*args, **kwargs):
        start = time()
        val = func(*args, **kwargs)
        end = time()
        duration = end - start
        print(f'{func.__name__} took {duration} seconds to run')
        return val
    return wrapper
  
# Example usage
@log_time
def get_data(db, query):
    """Gets data from a SQL-based database"""
    data = db.get(query)
    return data
  
if __name__ == '__main__':
    # Decorated function will print 'get_data took X seconds to run'
    db = SQLDB()
    query = 'SELECT * FROM foo'
    data = get_data(db, query)
Using the decorator pattern to time a function

Putting it together

Design patterns make for very reusable code, and you can put pieces together like building blocks to make your work a lot easier as a data scientist. For example, I’ll often combine all three of these patterns to write queries to a database and see how long the query took in order to know if I need to optimize.

Conclusion

In this article, I’ve shown three ways to use design patterns as a data scientist for more robust, maintainable code. When you use design patterns in data science, your code quality goes up, your maintenance is easier, and your results are easier to reproduce and share.

 

 

 

 

 

 

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
공지 오라클 기본 샘플 데이터베이스 졸리운_곰 2014.01.02 86153
공지 [SQL컨셉] 서적 "SQL컨셉"의 샘플 데이타 베이스 SAMPLE DATABASE of ORACLE 가을의 곰을... 2013.02.10 78648
공지 [G_SQL] Sample Database 가을의 곰을... 2012.05.20 95383
9 [암호화폐] [파이썬] 암호화폐 자동매매(1): 변동성 전략 +상승장 졸리운_곰 2025.03.13 1809
8 [암호화폐] Solana 토큰 만들기 — MeMe Coin file 졸리운_곰 2024.11.15 1244
7 [암호화폐] 솔리디티를 이용해 이더리움 스마트 계약 시작하기 file 졸리운_곰 2024.04.05 1736
6 암호화폐 (비트코인, cryptocurrency, bitcoin) 파이썬을 이용한 가상화폐 시세 분석 file 졸리운_곰 2024.03.28 1987
5 암호화폐 (비트코인, cryptocurrency, bitcoin) Solidity 이더리움 Solidity Tutorial: How to build and deploy a smart contract to send Ether from one account to another file 졸리운_곰 2024.01.23 1182
4 암호화폐 (비트코인, cryptocurrency, bitcoin) Solidity 이더리움 Cheatsheet 졸리운_곰 2024.01.23 1761
3 암호화폐 (비트코인, cryptocurrency, bitcoin) [Ethereum] Remix 를 이용하여 이더리움 솔리디티(Solidity) 개발 연습 하기! file 졸리운_곰 2021.10.19 1388
2 암호화폐 (비트코인, cryptocurrency, bitcoin) [Ethereum] Remix IDE를 이용한 Solidity 프로그래밍 file 졸리운_곰 2021.10.17 1794
1 암호화폐 (비트코인, cryptocurrency, bitcoin) [Ethereum] 스마트 컨트렉트로 "Hello, World"를 출력하자.​ file 졸리운_곰 2021.10.09 2095
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED